了解如何使用隐式或显式渲染方法将 Turnstile 小组件添加到您的网页。
Turnstile 提供了两种将小组件添加到页面中的方式。隐式渲染 (Implicit rendering) 在页面加载时自动扫描 HTML 以查找小组件容器。显式渲染 (Explicit rendering) 允许您使用 JavaScript 在任何时间以编程方式控制创建小组件。对于在页面加载时已存在表单的静态页面,请使用隐式渲染。对于在初始页面加载后才创建表单的动态内容和单页应用 (SPA),请使用显式渲染。
| 功能 | 隐式渲染 | 显式渲染 |
|---|---|---|
| 设置难度 | 简单,极简的代码 | 需要额外的 JavaScript |
| 时机控制 | 页面加载时自动渲染 | 完全控制渲染时机 |
| 用例 | 静态内容 | 动态或交互式内容 |
| 自定义 | 仅限于 HTML 属性 | 可通过 JavaScript API 进行广泛自定义 |
开始之前,您必须具备:
- 一个 Cloudflare 账户
- 一个 Turnstile 小组件(带有 sitekey)
- 编辑您网站 HTML 的权限
- HTML 和 JavaScript 的基础知识
- 页面加载:加载 Turnstile 脚本并扫描元素,或等待编程调用。
- 小组件渲染:创建小组件并开始运行质询。
- 令牌生成:完成质询时生成令牌。
- 表单集成:通过回调或隐藏表单字段使令牌可用。
- 服务器验证:您的服务器接收令牌并使用 Siteverify API 进行验证。
隐式渲染自动扫描您的 HTML 以查找带有 cf-turnstile 类的元素,并在无需额外 JavaScript 代码的情况下渲染小组件。此设置非常适合您希望在页面加载时立即加载小组件的静态页面。
Cloudflare 建议在以下场景中使用隐式渲染:
- 实现简单,希望快速集成。
- 具有简单表单的静态网站。
- 希望小组件在页面加载时立即出现。
- 不需要以编程方式控制小组件。
引入 Turnstile 脚本:在您的 HTML 文件的 <head> 部分或紧接在闭合的 </body> 标签之前,添加 Turnstile JavaScript API。
<script
src="https://challenges.cloudflare.com/turnstile/v0/api.js"
async
defer
></script>添加资源提示 (resource hints) 以通过尽早建立与 Cloudflare 服务器的连接来提高加载性能。将此 <link> 标签放置在 Turnstile 脚本之前的 HTML <head> 部分。
<link rel="preconnect" href="https://challenges.cloudflare.com" />在您希望质询出现在网站上的位置添加小组件容器。
<div class="cf-turnstile" data-sitekey="<YOUR-SITE-KEY>"></div>使用 data 属性自定义您的小组件。在您希望小组件出现的地方插入一个 div 元素。
<div
class="cf-turnstile"
data-sitekey="<YOUR-SITE-KEY>"
data-theme="light"
data-size="normal"
data-callback="onSuccess"
></div>一旦成功解决质询,令牌将被传递给成功回调函数。该令牌必须在我们的 Siteverify 端点进行验证。
基础登录表单
Turnstile 通常用于保护网站上的表单,例如登录表单或联系人表单。您可以将小组件嵌入在 <form> 标签中。
<!DOCTYPE html>
<html>
<head>
<title>登录表单</title>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
</head>
<body>
<form action="/login" method="POST">
<input type="text" name="username" placeholder="用户名" autocomplete="username" required />
<input type="password" name="password" placeholder="密码" autocomplete="current-password" required />
<!-- 带有基础配置的 Turnstile 小组件 -->
<div class="cf-turnstile" data-sitekey="<YOUR-SITE-KEY>"></div>
<button type="submit">登录</button>
</form>
</body>
</html>一个名称为 cf-turnstile-response 的不可见输入框将被添加,并随其他字段一同发送到服务器。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>使用 Cloudflare Turnstile 的隐式渲染</title>
<script
src="https://challenges.cloudflare.com/turnstile/v0/api.js"
async
defer
></script>
</head>
<body>
<h1>联系我们</h1>
<form action="/submit" method="POST">
<label for="name">姓名:</label><br />
<input type="text" id="name" name="name" required /><br />
<label for="email">邮箱:</label><br />
<input type="email" id="email" name="email" required /><br />
<!-- Turnstile 小组件 -->
<div class="cf-turnstile" data-sitekey="<YOUR-SITE-KEY>"></div>
<br />
<button type="submit">提交</button>
</form>
</body>
</html>带有回调的高级表单
<form action="/contact" method="POST" id="contact-form">
<input type="email" name="email" placeholder="邮箱" required />
<textarea name="message" placeholder="留言内容" required></textarea>
<!-- 带有回调和自定义配置的小组件 -->
<div
class="cf-turnstile"
data-sitekey="<YOUR-SITE-KEY>"
data-theme="auto"
data-size="flexible"
data-callback="onTurnstileSuccess"
data-error-callback="onTurnstileError"
data-expired-callback="onTurnstileExpired"
></div>
<button type="submit" id="submit-btn" disabled>发送留言</button>
</form>
<script>
function onTurnstileSuccess(token) {
console.log("Turnstile 验证成功,令牌:", token);
document.getElementById("submit-btn").disabled = false;
}
function onTurnstileError(errorCode) {
console.error("Turnstile 错误:", errorCode);
document.getElementById("submit-btn").disabled = true;
}
function onTurnstileExpired() {
console.warn("Turnstile 令牌已过期");
document.getElementById("submit-btn").disabled = true;
}
</script>具有不同配置的多个小组件
<!-- 用于订阅简报的紧凑尺寸小组件 -->
<form action="/newsletter" method="POST">
<input type="email" name="email" placeholder="邮箱" />
<div
class="cf-turnstile"
data-sitekey="<YOUR-SITE-KEY>"
data-size="compact"
data-action="newsletter"
></div>
<button type="submit">订阅</button>
</form>
<!-- 用于联系表单的普通尺寸小组件 -->
<form action="/contact" method="POST">
<input type="text" name="name" placeholder="姓名" />
<input type="email" name="email" placeholder="邮箱" />
<textarea name="message" placeholder="留言内容"></textarea>
<div
class="cf-turnstile"
data-sitekey="<YOUR-SITE-KEY>"
data-action="contact"
data-theme="dark"
></div>
<button type="submit">发送</button>
</form>自动表单集成
当您在 <form> 元素内嵌入 Turnstile 小组件时,会自动创建一个名为 cf-turnstile-response 的不可见输入字段。此字段包含验证令牌并与您的其他表单数据一起提交。
<form action="/submit" method="POST">
<input type="text" name="data" />
<div class="cf-turnstile" data-sitekey="<YOUR-SITE-KEY>"></div>
<!-- 自动添加的隐藏字段: -->
<!-- <input type="hidden" name="cf-turnstile-response" value="TOKEN_VALUE" /> -->
<button type="submit">提交</button>
</form>显式渲染允许您使用 JavaScript 函数对何时何地显示小组件以及如何创建小组件进行编程控制。此方法适用于动态内容、单页应用 (SPA) 或基于用户交互的条件渲染。
Cloudflare 建议在以下场景中使用显式渲染:
- 动态网站和单页应用 (SPA)。
- 需要控制小组件创建的时机。
- 希望根据访问者的交互有条件地渲染小组件。
- 需要具有不同配置的多个小组件。
- 具有需要管理小组件生命周期的复杂应用。
<script
src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit"
defer
></script>创建不带有 cf-turnstile 类的容器。
<div id="turnstile-container"></div>当您准备好创建小组件时,调用 turnstile.render()。
const widgetId = turnstile.render("#turnstile-container", {
sitekey: "<YOUR-SITE-KEY>",
callback: function (token) {
console.log("成功:", token);
},
});在显式渲染 Turnstile 小组件之后,您可能需要根据应用的需求与其进行交互。请参阅以下部分来管理小组件的状态。
如果给定的小组件超时或过期,您可以使用该函数进行重置:
turnstile.reset(widgetId);随时检索当前的响应令牌:
const responseToken = turnstile.getResponse(widgetId);当不再需要某个小组件时,可以使用以下方法将其从页面中移除:
turnstile.remove(widgetId);这不会调用任何回调,且会移除所有相关的 DOM 元素。
基础显式实现
<!DOCTYPE html>
<html>
<head>
<title>显式渲染</title>
<script
src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit"
defer
></script>
</head>
<body>
<form id="login-form">
<input
type="text"
name="username"
placeholder="用户名"
autocomplete="username"
/>
<input
type="password"
name="password"
placeholder="密码"
autocomplete="current-password"
/>
<div id="turnstile-widget"></div>
<button type="submit">登录</button>
</form>
<script>
window.onload = function () {
turnstile.render("#turnstile-widget", {
sitekey: "<YOUR-SITE-KEY>",
callback: function (token) {
console.log("Turnstile 令牌:", token);
// 处理成功验证
},
"error-callback": function (errorCode) {
console.error("Turnstile 错误:", errorCode);
},
});
};
</script>
</body>
</html>使用 onload 回调
<script
src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit&onload=onTurnstileLoad"
defer
></script>
<div id="widget-container"></div>
<script>
function onTurnstileLoad() {
turnstile.render("#widget-container", {
sitekey: "<YOUR-SITE-KEY>",
theme: "light",
callback: function (token) {
console.log("质询已完成:", token);
},
});
}
</script>高级 SPA 实现
<div id="dynamic-form-container"></div>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit"></script>
<script>
class TurnstileManager {
constructor() {
this.widgets = new Map();
}
createWidget(containerId, config) {
// 等待 Turnstile 准备就绪
turnstile.ready(() => {
const widgetId = turnstile.render(containerId, {
sitekey: config.sitekey,
theme: config.theme || "auto",
size: config.size || "normal",
callback: (token) => {
console.log(`小组件 ${widgetId} 已完成:`, token);
if (config.onSuccess) config.onSuccess(token, widgetId);
},
"error-callback": (error) => {
console.error(`小组件 ${widgetId} 错误:`, error);
if (config.onError) config.onError(error, widgetId);
},
});
this.widgets.set(containerId, widgetId);
return widgetId;
});
}
removeWidget(containerId) {
const widgetId = this.widgets.get(containerId);
if (widgetId) {
turnstile.remove(widgetId);
this.widgets.delete(containerId);
}
}
resetWidget(containerId) {
const widgetId = this.widgets.get(containerId);
if (widgetId) {
turnstile.reset(widgetId);
}
}
}
// 使用方法
const manager = new TurnstileManager();
// 在用户点击按钮时创建 widget
document.getElementById("show-form-btn").addEventListener("click", () => {
document.getElementById("dynamic-form-container").innerHTML = `
<form>
<input type="email" placeholder="邮箱" />
<div id="turnstile-widget"></div>
<button type="submit">提交</button>
</form>
`;
manager.createWidget("#turnstile-widget", {
sitekey: "<YOUR-SITE-KEY>",
theme: "dark",
onSuccess: (token) => {
// 处理验证成功
console.log("表单已准备好提交");
},
});
});
</script>显式渲染提供了对小组件生命周期的完全控制。
// 渲染 widget
const widgetId = turnstile.render("#container", {
sitekey: "<YOUR-SITE-KEY>",
callback: handleSuccess,
});
// 获取当前的令牌
const token = turnstile.getResponse(widgetId);
// 检查 widget 是否已过期
const isExpired = turnstile.isExpired(widgetId);
// 重置 widget(清除当前状态)
turnstile.reset(widgetId);
// 完全移除 widget
turnstile.remove(widgetId);使用执行模式控制质询何时运行。
// 渲染 widget,但先不运行质询
const widgetId = turnstile.render("#container", {
sitekey: "<YOUR-SITE-KEY>",
execution: "execute", // 不自动执行
});
// 稍后在需要时运行质询
turnstile.execute("#container");Cloudflare 建议您在访问者进入页面时尽早执行 Turnstile 脚本,以便在访问者尝试在页面上执行操作时已经完成验证并且可以使用交互。
隐式和显式渲染方法均支持相同的配置选项。请参阅下表以了解最常用的配置。
| 选项 | 描述 | 值 |
|---|---|---|
sitekey |
您的小组件 sitekey | 必填的字符串 |
theme |
视觉主题 | auto、light、dark |
size |
Widget 尺寸 | normal、flexible、compact |
callback |
成功回调函数 | 函数 |
error-callback |
错误回调函数 | 函数 |
execution |
何时运行质询 | render、execute |
appearance |
小组件何时可见 | always、execute、interaction-only |
有关配置选项的完整列表,请参考 小组件配置。
您可以使用测试站点密钥在网页上测试您的 Turnstile 小组件,而不会触发实际的 Cloudflare 质询。
有关更多信息,请参阅测试。
Turnstile 设计为仅在运行 http:// 或 https:// URI 协议的页面上运行。不支持在其他协议(例如 file://)上嵌入小组件。
-
必须进行服务器端验证。使用 Siteverify API 强制执行 Turnstile 令牌至关重要。Turnstile 令牌可能是无效的、已过期的或已被兑换的。如果不验证令牌,将在您的实现中留下重大安全漏洞。您必须调用 Siteverify 来完成您的 Turnstile 配置。否则,配置将是不完整的,并且在 Turnstile Analytics 中查看您的指标时,令牌验证结果将显示为零。
-
令牌在 300 秒(5 分钟)后过期。每个令牌只能验证一次。过期或已使用的令牌必须替换为新的质询。