如果你曾经构建过联系表单,并看着用户点击"提交"后整个页面重新加载,你就知道这种体验有多糟糕。使用HTML表单提交fetch JavaScript技术,你可以在后台将表单数据发送到后端接口,让用户停留在同一页面,并显示简洁的成功或错误消息,无需任何页面刷新。本教程将带你了解每个步骤,从编写基本HTML到将fetch()调用指向Sendform接口,让你的提交内容直接发送到邮箱或连接的工作流程。
核心要点:
- Fetch API让你无需页面重载即可提交表单数据,为用户提供更流畅的体验。
- 捕获
submit事件并调用preventDefault()是任何Ajax表单提交模式的基础。 - Sendform提供现成的接口URL,无需任何后端代码即可接收和存储提交内容。
- 正确的响应处理(显示成功或错误反馈)与正确发送数据同样重要。
为什么使用fetch()而不是默认表单提交
浏览器的默认表单提交行为只做一件事:序列化表单字段,向actionURL发送POST(或GET)请求,然后加载服务器返回的任何响应。这意味着用户会看到白屏闪烁,失去滚动位置,并等待新页面渲染。在网络连接较慢时,这会让人觉得网站坏了。
Fetch API通过在JavaScript中程序化地发起HTTP请求解决了这个问题,无需跳转页面。这实现了表单提交无页面重载的模式,让用户保持专注,并让你完全控制用户体验的每个方面,包括加载状态、内联验证反馈和动画成功横幅。
选择fetch()的其他实用原因:
- 你可以附加自定义头部(例如CSRF令牌或授权密钥),这是普通HTML表单无法发送的。
- 你可以根据接口期望将数据序列化为JSON、
FormData或URL编码字符串。 - 错误处理是明确的。你决定什么是"失败"以及如何传达给用户。
- 它适用于任何静态网站,包括托管在GitHub Pages、Netlify或CDN上的网站,因为逻辑完全在浏览器中运行。
注意:如果你使用Webflow、WordPress或Hugo等网站构建器,同样的fetch()方法也适用。查看我们关于如何将Sendform与你的网站构建器集成的指南,了解特定平台的技巧。
基本HTML表单设置
在编写任何JavaScript代码之前,你需要一个结构良好的HTML表单。这里的关键细节是你不需要指向任何地方的action属性,因为JavaScript将处理提交。但是,你确实需要在每个输入上使用有意义的name属性 - 这些将成为提交载荷中的字段键。
<form id="contact-form" novalidate>
<div>
<label for="name">您的姓名</label>
<input type="text" id="name" name="name" required placeholder="张三">
</div>
<div>
<label for="email">电子邮箱</label>
<input type="email" id="email" name="email" required placeholder="[email protected]">
</div>
<div>
<label for="message">留言</label>
<textarea id="message" name="message" rows="5" required></textarea>
</div>
<button type="submit">发送消息</button>
<!-- 反馈区域 -->
<div id="form-feedback" aria-live="polite"></div>
</form>这个标记中值得注意的几点:
- 表单元素上的
novalidate禁用原生浏览器验证气泡,让你在JavaScript中完全控制错误消息。 - 带有
aria-live="polite"的id="form-feedback"div是成功和错误消息将出现的地方。ARIA属性确保屏幕阅读器自动宣布反馈。 - 每个输入都有
id(用于标签关联)和name(用于表单载荷)。
捕获提交事件
任何JavaScript表单提交的第一步是拦截浏览器的默认行为。你通过监听表单元素上的submit事件并立即调用event.preventDefault()来实现这一点。
const form = document.getElementById('contact-form');
form.addEventListener('submit', async function (event) {
event.preventDefault(); // 阻止默认页面导航
// 基本客户端验证
const name = form.elements['name'].value.trim();
const email = form.elements['email'].value.trim();
const message = form.elements['message'].value.trim();
if (!name || !email || !message) {
showFeedback('请填写所有字段。', 'error');
return;
}
// 继续发送数据(下一节)
await submitForm({ name, email, message });
});通过将验证逻辑与网络调用分离,你可以保持代码的可读性和易于扩展。事件处理器上的async关键字让你在其中使用await,这使得Fetch API表单数据调用看起来是同步的,避免了深度嵌套的Promise链。
将fetch()指向Sendform接口
这是真正工作发生的地方。与其构建自己的服务器来接收、存储和转发表单提交,你可以使用Sendform作为你的后端。在Sendform仪表板中创建表单后,你会获得一个唯一的接口URL。这个URL就是你所需要的全部。
下面的提交函数使用FormData API来构建载荷,Sendform原生支持:
async function submitForm(data) {
// 将此URL替换为你实际的Sendform接口
const SENDFORM_ENDPOINT = 'https://sendform.net/zh/YOUR_FORM_ID';
const formData = new FormData();
formData.append('name', data.name);
formData.append('email', data.email);
formData.append('message', data.message);
try {
const response = await fetch(SENDFORM_ENDPOINT, {
method: 'POST',
body: formData,
});
if (response.ok) {
showFeedback('谢谢!您的消息已发送。', 'success');
form.reset();
} else {
const errorData = await response.json().catch(() => ({}));
const errorMsg = errorData.message || '出现问题。请重试。';
showFeedback(errorMsg, 'error');
}
} catch (networkError) {
showFeedback('网络错误。请检查连接后重试。', 'error');
}
}此代码中做出的关键决策:
- 没有手动设置
Content-Type头部。当你将FormData对象作为主体传递时,浏览器会自动设置正确的multipart/form-data边界。 response.ok检查涵盖所有2xx HTTP状态码,而不仅仅是200。这比比较response.status === 200更健壮。- 外层
try/catch捕获网络级别的失败(DNS错误、离线状态),这些是response.ok永远不会看到的。
处理响应 - 成功和错误消息
发送数据只是工作的一半。用户需要即时、清晰的反馈。上面引用的showFeedback()辅助函数将消息写入你添加到HTML中的反馈div:
function showFeedback(message, type) {
const feedbackEl = document.getElementById('form-feedback');
feedbackEl.textContent = message;
feedbackEl.className = type === 'success' ? 'feedback-success' : 'feedback-error';
}这是故意保持简洁的。在实际项目中,你可能会用动画组件或toast通知库替换textContent,但模式保持不变:根据fetch()调用的结果更新DOM。
对于更高级的场景,如重定向到自定义感谢页面或在提交后触发下游自动化,查看我们关于如何使用webhook、Zapier和API自动化表单工作流程的文章。
最佳实践和技巧
让代码工作是一回事。以能在生产环境中稳定运行的方式发布它是另一回事。以下是需要记住的最重要提示:
- 在请求期间禁用提交按钮。在调用
fetch()之前设置button.disabled = true,并在finally块中重新启用它。这可以防止用户多次点击时的重复提交。 - 显示加载状态。在请求进行时将按钮文本更改为"发送中..."或添加旋转器类。看不到反馈的用户通常认为什么都没发生,会再次点击。
- 也要在服务器端验证。客户端验证是为了用户体验。Sendform和任何后端服务都应该将所有传入数据视为不可信的。
- 到处使用HTTPS。通过普通HTTP发送表单数据会在传输过程中暴露用户输入。Sendform接口默认使用HTTPS,但确保你自己的页面也通过HTTPS提供。
- 添加垃圾邮件保护。蜜罐字段或CAPTCHA集成可以显著减少垃圾提交。要深入了解这个主题,请参阅我们关于表单垃圾邮件保护最佳实践的指南。
- 故意测试错误路径。临时将接口URL更改为无效的内容,确认你的错误消息出现。大多数开发者只测试成功路径。
- 将接口URL排除在版本控制之外。如果你的项目是开源的,将Sendform接口存储在环境变量或列在
.gitignore中的配置文件中。
静态网站用户:如果你的项目是Hugo、Eleventy或没有服务器的纯HTML网站,这里描述的fetch()方法是推荐的方法。在我们关于静态网站无服务器表单处理的指南中了解更多。
总结
用fetch()调用替换默认表单提交是你可以对任何联系或潜在客户捕获表单进行的最高影响的改进之一。结果是更快、更专业的体验,让用户留在你的页面上,并让你完全控制反馈消息。将其与Sendform接口配对,你完全无需任何服务器端代码。你的表单上线了,你的提交到达你的收件箱,你的用户永远不会看到令人不快的页面重载。立即创建你的免费Sendform接口,几分钟内就能收到第一个提交。
常见问题
可以。因为fetch()完全在浏览器中运行,它适用于静态HTML网站、JAMstack项目和任何提供HTML的平台。你不需要自己的服务器。唯一的要求是一个可以接收POST请求的接口(如Sendform URL)。
fetch()是XMLHttpRequest的现代替代品。它使用Promise,支持async/await,并具有更清洁的API。对于新项目,总是首选fetch()。两者都能实现相同的Ajax表单提交结果,但fetch()需要的样板代码要少得多。
不需要。当你将FormData对象作为body传递时,浏览器会自动将Content-Type设置为multipart/form-data并包含正确的边界字符串。手动设置它实际上会因为省略边界值而破坏请求。
在Sendform注册,在仪表板中创建新表单,然后复制生成的接口URL。将该URL粘贴为fetch()调用中的目标。提交内容将立即转发到你配置的电子邮件地址。
网络失败会导致fetch() Promise被拒绝,这会被示例代码中的外层try/catch块捕获。用户会看到你定义的"网络错误"消息。提交不会自动排队;用户必须在恢复连接后重试。