视觉对焦验证 (FocusCaptcha)
基于图像处理的交互式验证组件。初始状态下图片是模糊的,用户需要拖动滑块寻找图片最清晰的焦点位置。系统通过分析用户寻找焦点的轨迹平滑度、速度曲线以及最终停留位置来判定人机。
交互流程
- 组件加载一张模糊的背景图片。
- 用户拖动下方滑块,图片模糊度随滑块位置动态变化。
- 用户在认为图片最清晰的位置松开滑块。
- 系统自动提交验证。
基础用法
html
<div id="captcha-container"></div>
<script>
// 创建 FocusCaptcha 实例
const focus = new WlzShield.FocusCaptcha({
siteKey: 'your-site-key', // 必填:网站唯一标识
containerId: '#captcha-container', // 必填:挂载容器ID
imageSrc: 'https://example.com/your-image.jpg' // 可选:自定义背景图片
});
// 可选:监听验证结果
focus.config.onSuccess = function (data) {
console.log('验证成功:', data);
// 将验证凭证提交到您的后端
// data.wsToken 可用于后续请求的验证
};
</script>在线体验
配置参数 (API)
基础配置
| 参数名 | 类型 | 默认值 | 是否必填 | 说明 |
|---|---|---|---|---|
siteKey | string | - | 必填 | 网站唯一标识,用于区分不同站点的验证请求 |
containerId | string | - | 必填 | DOM 容器 ID 选择器 (如 #captcha-box) |
challengeUrl | string | /api/challenge | 可选 | 后端验证接口地址 |
width | number | string | '100%' | 可选 | 组件宽度,支持数字(px)或CSS字符串(如 '100%', '320px') |
lang | string | zh | 可选 | 语言设置:'zh' (中文) 或 'en' (英文) |
mode | 'embed' | 'popup' | 'embed' | 可选 | 展示模式:'embed' (嵌入式) 或 'popup' (弹窗式) |
timeout | number | 10000 | 可选 | 请求超时时间(毫秒) |
imageSrc | string | Unsplash示例图片 | 可选 | 验证所使用的背景图片 URL,建议尺寸300-400px宽度 |
高级配置
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
customTexts | Record<string, string> | {} | 自定义文案,覆盖默认的提示信息等 |
customStyles | Partial<CSSStyleDeclaration> | {} | 自定义主容器样式,应用于整个验证码组件 |
popupStyles | Partial<CSSStyleDeclaration> | {} | 仅对popup模式有效,自定义弹窗样式 |
回调函数
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
onReady | () => void | undefined | 组件加载完成时触发 |
onOpen | () => void | undefined | 仅popup模式有效,弹窗打开时触发 |
onClose | () => void | undefined | 仅popup模式有效,弹窗关闭时触发 |
onSuccess | (data: any) => void | undefined | 验证通过时触发 |
onFailure | (data: any) => void | undefined | 验证失败时触发(逻辑拒绝,如轨迹异常) |
onError | (error: Error) => void | undefined | 系统/网络错误时触发(如 404, 500, 网络断开) |
详细配置说明
自定义背景图片 (imageSrc)
您可以自定义验证码的背景图片:
javascript
const focus = new WlzShield.FocusCaptcha({
siteKey: 'your-site-key',
containerId: '#captcha-container',
// 使用自己的图片
imageSrc: 'https://your-domain.com/images/verification-bg.jpg',
// 或者使用数据URL
// imageSrc: 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ...'
});
// 动态更换图片
function changeBackground(imageUrl) {
// 可以通过重置验证码来更换图片
focus.reset();
// 注意:imageSrc在初始化后不能直接修改,需要重新创建实例
}自定义文案 (customTexts)
您可以覆盖以下默认文案:
javascript
const focus = new WlzShield.FocusCaptcha({
siteKey: 'your-site-key',
containerId: '#captcha-container',
customTexts: {
// 覆盖描述文本(支持HTML)
desc: "请拖动滑块,让图片<b>变得清晰</b>",
// 覆盖成功文本
success: "✅ 验证通过",
// 覆盖失败文本
fail: "验证失败",
// 覆盖重试文本
retry: "点击重试",
// 弹窗模式下的按钮文本(仅popup模式有效)
buttonText: "开始验证",
// 弹窗标题(仅popup模式有效)
popupTitle: "视觉验证"
}
});自定义样式 (customStyles)
您可以调整验证码组件的外观样式:
javascript
const focus = new WlzShield.FocusCaptcha({
siteKey: 'your-site-key',
containerId: '#captcha-container',
mode: 'embed',
customStyles: {
// 修改背景色
backgroundColor: '#f8fafc',
// 添加边框
border: '1px solid #e2e8f0',
// 调整圆角
borderRadius: '10px',
// 调整内边距
padding: '20px',
// 修改字体
fontFamily: 'system-ui, -apple-system, sans-serif'
}
});滑块样式自定义
滑块使用HTML5原生range input,可以通过CSS自定义:
css
/* 自定义滑块样式 */
input[type="range"] {
-webkit-appearance: none;
width: 100%;
height: 8px;
background: #e2e8f0;
border-radius: 4px;
outline: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 20px;
height: 20px;
background: #3b82f6;
border-radius: 50%;
cursor: pointer;
}
input[type="range"]::-moz-range-thumb {
width: 20px;
height: 20px;
background: #3b82f6;
border-radius: 50%;
cursor: pointer;
border: none;
}完整示例
示例1:表单验证集成(嵌入式模式)
html
<!DOCTYPE html>
<html>
<head>
<title>视觉对焦验证示例</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 40px;
max-width: 500px;
margin: 0 auto;
}
.feedback-form {
background: #f8fafc;
padding: 30px;
border-radius: 12px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
}
textarea {
width: 100%;
height: 120px;
padding: 12px;
border: 1px solid #cbd5e1;
border-radius: 6px;
}
button {
padding: 12px 24px;
background: #3b82f6;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
}
button:disabled {
background: #94a3b8;
cursor: not-allowed;
}
#captcha-box {
margin: 20px 0;
}
.hint {
font-size: 12px;
color: #64748b;
margin-top: 5px;
}
</style>
<script src="https://captcha.wangluozhe.com/static/js/vendors.min.js"></script>
<script src="https://captcha.wangluozhe.com/static/js/wlzshield.min.js"></script>
</head>
<body>
<div class="feedback-form">
<h2>意见反馈</h2>
<form id="feedbackForm">
<div class="form-group">
<label>您的反馈</label>
<textarea id="feedback" placeholder="请输入您的意见和建议..." required></textarea>
</div>
<div class="form-group">
<label>安全验证</label>
<div id="captcha-box"></div>
<div class="hint">请拖动滑块,让图片变得最清晰</div>
</div>
<button type="submit" id="submitBtn" disabled>提交反馈</button>
</form>
</div>
<script>
let captchaInstance = null;
let isVerified = false;
let wsToken = '';
// 初始化验证码
captchaInstance = new WlzShield.FocusCaptcha({
siteKey: 'your-site-key',
containerId: '#captcha-box',
mode: 'embed',
width: '100%',
// 使用自定义图片
imageSrc: 'https://images.unsplash.com/photo-1506744038136-46273834b3fb',
customTexts: {
desc: "拖动滑块,寻找图片<b>最清晰</b>的点"
},
onSuccess: function (data) {
console.log('验证成功,服务器响应:', data);
isVerified = true;
wsToken = data.wsToken || '';
document.getElementById('submitBtn').disabled = false;
document.getElementById('submitBtn').innerHTML = '✓ 已验证,点击提交';
},
onFailure: function (data) {
console.warn('验证失败:', data);
isVerified = false;
wsToken = '';
document.getElementById('submitBtn').disabled = true;
document.getElementById('submitBtn').innerHTML = '提交反馈';
// 显示错误信息
alert('验证失败: ' + (data.msg || '请重试'));
},
onError: function (error) {
console.error('系统错误:', error);
alert('验证系统错误,请刷新页面重试');
}
});
// 表单提交处理
document.getElementById('feedbackForm').addEventListener('submit', function (e) {
e.preventDefault();
if (!isVerified) {
alert('请先完成安全验证');
return;
}
// 收集表单数据
const formData = {
feedback: document.getElementById('feedback').value,
wsToken: wsToken, // 包含验证凭证
timestamp: Date.now()
};
console.log('提交反馈数据:', formData);
// 这里发送到您的后端进行验证
// 后端应使用wsToken验证请求的合法性
// 模拟提交
document.getElementById('submitBtn').innerHTML = '提交中...';
document.getElementById('submitBtn').disabled = true;
setTimeout(() => {
alert('反馈提交成功!感谢您的意见。');
// 重置表单和验证码
document.getElementById('feedbackForm').reset();
captchaInstance.reset();
isVerified = false;
wsToken = '';
document.getElementById('submitBtn').innerHTML = '提交反馈';
document.getElementById('submitBtn').disabled = true;
}, 1000);
});
</script>
</body>
</html>示例2:弹窗模式应用
html
<!DOCTYPE html>
<html>
<head>
<title>弹窗验证示例</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 40px;
}
.post-form {
max-width: 600px;
margin: 0 auto;
}
input[type="text"], textarea {
width: 100%;
padding: 12px;
margin-bottom: 15px;
border: 1px solid #ddd;
border-radius: 6px;
}
.toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20px;
}
#captcha-trigger {
background: #f1f5f9;
color: #475569;
border: 1px solid #cbd5e1;
padding: 10px 20px;
border-radius: 6px;
cursor: pointer;
}
#submitPost {
background: #3b82f6;
color: white;
border: none;
padding: 12px 24px;
border-radius: 6px;
cursor: pointer;
}
#submitPost:disabled {
background: #94a3b8;
cursor: not-allowed;
}
</style>
<script src="https://captcha.wangluozhe.com/static/js/vendors.min.js"></script>
<script src="https://captcha.wangluozhe.com/static/js/wlzshield.min.js"></script>
</head>
<body>
<div class="post-form">
<h2>发布文章</h2>
<input type="text" id="title" placeholder="文章标题">
<textarea id="content" rows="10" placeholder="文章内容..."></textarea>
<div class="toolbar">
<div id="captcha-trigger">验证身份</div>
<button id="submitPost" disabled>发布文章</button>
</div>
</div>
<!-- 验证码容器(隐藏,用于弹窗模式) -->
<div id="captcha-container" style="display: none;"></div>
<script>
let captchaInstance = null;
let isVerified = false;
let wsToken = '';
// 初始化验证码(弹窗模式)
captchaInstance = new WlzShield.FocusCaptcha({
siteKey: 'your-site-key',
containerId: '#captcha-container',
mode: 'popup',
imageSrc: 'https://images.unsplash.com/photo-1519681393784-d120267933ba',
customTexts: {
buttonText: '视觉验证',
popupTitle: '安全验证',
desc: "拖动滑块,让图片变得最清晰"
},
onSuccess: function (data) {
console.log('验证成功:', data);
isVerified = true;
wsToken = data.wsToken || '';
document.getElementById('captcha-trigger').innerHTML = '✓ 已验证';
document.getElementById('captcha-trigger').style.background = '#dcfce7';
document.getElementById('captcha-trigger').style.color = '#16a34a';
document.getElementById('captcha-trigger').style.borderColor = '#86efac';
document.getElementById('submitPost').disabled = false;
},
onFailure: function (data) {
console.warn('验证失败:', data);
isVerified = false;
wsToken = '';
alert('验证失败: ' + (data.msg || '请重试'));
},
onOpen: function () {
console.log('验证弹窗已打开');
},
onClose: function () {
console.log('验证弹窗已关闭');
}
});
// 触发验证按钮
document.getElementById('captcha-trigger').addEventListener('click', function () {
if (!isVerified) {
// 弹窗模式会自动显示验证窗口
} else {
alert('您已经通过验证');
}
});
// 提交文章
document.getElementById('submitPost').addEventListener('click', function () {
if (!isVerified) {
alert('请先完成身份验证');
return;
}
const title = document.getElementById('title').value;
const content = document.getElementById('content').value;
if (!title.trim() || !content.trim()) {
alert('请填写标题和内容');
return;
}
// 构建请求数据
const postData = {
title: title,
content: content,
wsToken: wsToken
};
console.log('发布文章:', postData);
// 发送到后端,后端应验证wsToken
// 清空表单
document.getElementById('title').value = '';
document.getElementById('content').value = '';
document.getElementById('captcha-trigger').innerHTML = '验证身份';
document.getElementById('captcha-trigger').style.background = '#f1f5f9';
document.getElementById('captcha-trigger').style.color = '#475569';
document.getElementById('captcha-trigger').style.borderColor = '#cbd5e1';
document.getElementById('submitPost').disabled = true;
isVerified = false;
wsToken = '';
alert('文章发布成功!');
});
</script>
</body>
</html>实例方法
reset()
重置验证码到初始状态。该方法会重新生成随机的"清晰点位置"和"初始滑块位置",并重置UI状态。
javascript
// 1. 保存实例引用
const myCaptcha = new WlzShield.FocusCaptcha({
siteKey: 'your-site-key',
containerId: '#captcha-box'
});
// 2. 在需要的时候调用
function handleFormError() {
// 重置验证码
myCaptcha.reset();
// 重置表单状态
document.getElementById('submitBtn').disabled = true;
document.getElementById('submitBtn').innerText = '提交';
}openModal() 和 closeModal()
仅适用于popup模式:手动打开或关闭验证弹窗。
javascript
const popupCaptcha = new WlzShield.FocusCaptcha({
siteKey: 'your-site-key',
containerId: '#captcha-box',
mode: 'popup'
});
// 手动打开弹窗
document.getElementById('custom-trigger').addEventListener('click', () => {
popupCaptcha.openModal();
});
// 手动关闭弹窗
document.getElementById('cancel-btn').addEventListener('click', () => {
popupCaptcha.closeModal();
});事件回调详解
onSuccess
验证成功时触发,返回服务器响应数据。
javascript
new WlzShield.FocusCaptcha({
// ... 其他配置
onSuccess: function (data) {
// 服务器返回的数据结构
// {
// "status": true,
// "msg": "认证成功",
// "wsToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
// }
console.log('验证成功,服务器响应:', data);
// 获取wsToken
const wsToken = data.wsToken;
// 将wsToken存储到隐藏字段或全局变量中
document.getElementById('captcha-token').value = wsToken;
}
});onFailure
验证失败时触发,返回服务器响应数据。注意:wsToken字段为空字符串。
javascript
new WlzShield.FocusCaptcha({
// ... 其他配置
onFailure: function (data) {
// 服务器返回的数据结构
// {
// "status": false,
// "msg": "轨迹异常",
// "wsToken": ""
// }
console.warn('验证失败,服务器响应:', data);
// wsToken为空字符串
const wsToken = data.wsToken; // 空字符串
// 显示错误信息给用户
const errorMsg = data.msg || '验证失败,请重试';
alert(errorMsg);
// 根据错误类型提供不同的处理
if (data.msg?.includes('轨迹')) {
console.log('轨迹异常,可能是操作过快或机器行为');
} else if (data.msg?.includes('超时')) {
console.log('验证超时');
}
// 自动重置验证码
setTimeout(() => {
this.reset();
}, 1500);
}
});onError
系统错误时触发,通常是网络问题或配置错误。
javascript
new WlzShield.FocusCaptcha({
// ... 其他配置
onError: function (error) {
console.error('验证系统错误:', error);
// 根据错误类型提供不同的处理
if (error.message.includes('Network')) {
alert('网络连接失败,请检查网络设置');
} else if (error.message.includes('Timeout')) {
alert('请求超时,请重试');
} else if (error.message.includes('siteKey')) {
alert('验证配置错误,请联系网站管理员');
} else {
alert('验证系统异常,请刷新页面');
}
}
});onReady, onOpen, onClose
组件生命周期回调:
javascript
new WlzShield.FocusCaptcha({
// ... 其他配置
onReady: function () {
console.log('视觉对焦验证码已加载完成,可以开始验证');
// 可以在这里显示加载完成的提示或启用界面元素
},
onOpen: function () {
console.log('验证弹窗已打开');
// 弹窗打开时,可以暂停页面上的其他交互
document.body.style.overflow = 'hidden';
},
onClose: function () {
console.log('验证弹窗已关闭');
// 弹窗关闭时,恢复页面交互
document.body.style.overflow = '';
// 如果未验证成功,显示提示
if (!this.isVerified) {
console.log('验证未完成,需要继续验证');
}
}
});注意事项
轨迹分析:组件记录用户拖动滑块的完整轨迹,包括时间戳、位置和速度,用于后端的人机分析。
速度敏感模糊:图片模糊度不仅取决于距离目标的远近,还取决于拖动速度,快速拖动会导致图片更模糊。
图片建议:建议使用300-400px宽度的图片,避免使用文字过多的图片,以免影响用户体验。
移动端适配:组件完全支持触摸操作,在移动设备上表现良好。
故障排查
常见问题
滑块无法拖动
- 检查容器是否正确初始化
- 查看控制台是否有JavaScript错误
- 确保没有其他JavaScript库干扰事件监听
图片不显示或加载慢
- 检查
imageSrcURL是否可访问 - 确保图片服务器支持跨域请求(CORS)
- 考虑使用CDN图片或本地图片
- 检查
验证总是失败
- 确认
siteKey是否正确 - 检查网络连接,确保能访问验证接口
- 查看后端验证服务是否正常运行
- 确认
弹窗模式不显示
- 确认
mode设置为'popup' - 检查容器是否隐藏(弹窗模式容器通常隐藏)
- 确保没有CSS样式覆盖弹窗显示
- 确认
调试技巧
javascript
// 启用详细日志
localStorage.setItem('wlz-debug', 'true');
// 创建实例时捕获错误
try {
const captcha = new WlzShield.FocusCaptcha({
// 配置
});
} catch (error) {
console.error('初始化失败:', error);
// 显示友好的错误提示
document.getElementById('captcha-container').innerHTML =
'<div class="error">验证码加载失败,请刷新页面或联系管理员</div>';
}
// 监听所有可能的事件
const captcha = new WlzShield.FocusCaptcha({
// ... 配置
onReady: () => console.log('组件就绪'),
onOpen: () => console.log('弹窗打开'),
onClose: () => console.log('弹窗关闭'),
onSuccess: (data) => console.log('验证成功', data),
onFailure: (data) => console.log('验证失败', data),
onError: (error) => console.log('系统错误', error)
});
// 检查wsToken格式
captcha.config.onSuccess = function (data) {
console.log('wsToken长度:', data.wsToken?.length || 0);
console.log('wsToken前50字符:', data.wsToken?.substring(0, 50) || '无');
};浏览器兼容性
- Chrome 60+
- Firefox 63+
- Safari 12+
- Edge 79+
- iOS Safari 12+
- Android Chrome 60+
组件使用现代Web API,对于不支持某些API的旧浏览器会自动降级处理。
通过以上详细文档,您可以全面了解FocusCaptcha的使用方法和配置选项。视觉对焦验证提供了独特的图像交互体验,在保证用户体验的同时提供了强大的安全防护能力。如有进一步问题,请参考源码或联系技术支持。