> 技术文档 > 【前端登录】实现一个带图形验证码的登录页组件(Vue3 + React)_前端图形验证码

【前端登录】实现一个带图形验证码的登录页组件(Vue3 + React)_前端图形验证码


🔐 带图形验证码的登录页组件实现(Vue3 + React)

【前端登录】业务方式全解析:从传统到现代,涵盖 Vue、React、Angular、jQuery 与原生 JS 最佳实践
我们将基于登录业务流程,封装一个 带图形验证码的完整登录页组件,分别用 Vue 3React 实现。


🧠 功能需求与思路流程

✅ 核心功能

  1. 用户名/邮箱输入框
  2. 密码输入框
  3. 图形验证码生成与刷新
  4. 登录按钮
  5. 表单验证逻辑
  6. 提交处理

📦 技术选型

  • Vue 3:使用 Composition API + 语法
  • React:使用 Hooks(useState, useEffect)
  • 验证码生成:HTML5 Canvas 绘制

下面老曹将 详细讲解该组件中每个核心功能模块的设计与算法思路,包括:

  • 验证码生成算法
  • 表单验证流程
  • 数据绑定机制
  • 事件响应逻辑

🔐 带图形验证码登录组件 —— 核心算法详解

🧠 一、整体结构概览

组件主要由以下部分构成:

模块 功能 表单输入 用户名、密码、验证码输入框 图形验证码 Canvas 绘制动态验证码 提交按钮 触发登录逻辑 状态管理 输入数据和验证码状态同步

🟢 二、图形验证码生成算法详解(关键步骤)

验证码绘制的核心是通过 HTML5 实现,使用随机字符 + 干扰元素增强安全性。

✅ 步骤 1:生成随机字符

function generateRandomChar() { const chars = \'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\'; return chars.charAt(Math.floor(Math.random() * chars.length));}
  • chars 定义可用字符集
  • 使用 Math.random() 从字符集中随机选择字符
  • 返回单个字符用于拼接验证码字符串

示例输出:\'A\', \'k\', \'3\'


✅ 步骤 2:组合四位验证码

let captcha = \'\';for (let i = 0; i < 4; i++) { captcha += generateRandomChar();}
  • 循环 4 次,生成一个 4 位验证码字符串
  • 存储在 localStorage 或组件状态中用于后续校验

示例输出:\'aX9k\'


✅ 步骤 3:清空画布并设置背景色

ctx.clearRect(0, 0, canvas.width, canvas.height);ctx.fillStyle = \'#f0f0f0\';ctx.fillRect(0, 0, canvas.width, canvas.height);
  • 清除画布内容,避免旧图像残留
  • 设置背景颜色为浅灰色,提高可读性

✅ 步骤 4:添加干扰线(防识别)

for (let i = 0; i < 4; i++) { ctx.beginPath(); ctx.moveTo(Math.random() * canvas.width, Math.random() * canvas.height); ctx.lineTo(Math.random() * canvas.width, Math.random() * canvas.height); ctx.strokeStyle = randomColor(150, 200); ctx.stroke();}
  • 画 4 条随机位置的线段
  • 颜色使用 randomColor 函数生成偏暗的颜色,防止干扰文字辨识

✅ 步骤 5:添加干扰点(进一步防识别)

for (let i = 0; i < 30; i++) { ctx.beginPath(); ctx.arc( Math.random() * canvas.width, Math.random() * canvas.height, 1, 0, Math.PI * 2 ); ctx.fillStyle = randomColor(150, 200); ctx.fill();}
  • 在画布上绘制 30 个随机位置的小圆点
  • 用作视觉干扰,增加 OCR 识别难度

✅ 步骤 6:绘制验证码字符(含样式变换)

ctx.font = \'bold 20px Arial\';ctx.textAlign = \'center\';ctx.textBaseline = \'middle\';for (let i = 0; i < captchaText.length; i++) { ctx.fillStyle = randomColor(50, 150); ctx.save(); ctx.translate(20 + i * 20, 20); ctx.rotate((Math.random() * 20 - 10) * Math.PI / 180); ctx.fillText(captchaText[i], 0, 0); ctx.restore();}
  • 对每个字符单独绘制
  • 添加随机旋转角度(-10° ~ +10°)
  • 设置不同颜色以增强视觉混淆
  • 使用 save/restore 保证每次变换不影响其他字符

✅ 步骤 7:保存验证码用于校验

localStorage.setItem(\'captcha\', captcha);
  • 将当前验证码存储在浏览器本地
  • 登录时比对用户输入与该值是否一致

🔵 三、表单验证逻辑详解

✅ 步骤 1:监听提交事件

handleSubmit() { // ...}
  • 当用户点击“登录”按钮时触发

✅ 步骤 2:检查字段是否为空

if (!formData.username || !formData.password || !formData.captcha) { alert(\'请填写所有字段\'); return;}
  • 判断用户名、密码、验证码是否都已填写
  • 若任意为空,则提示并中断流程

✅ 步骤 3:比较验证码

const storedCaptcha = localStorage.getItem(\'captcha\');if (formData.captcha.toLowerCase() !== storedCaptcha?.toLowerCase()) { alert(\'验证码错误,请重试\'); generateCaptcha(); // 刷新验证码 return;}
  • 获取之前生成的验证码
  • 忽略大小写进行比对
  • 如果不匹配,提示错误并刷新验证码

✅ 步骤 4:模拟登录成功

alert(\'登录成功!\');// 这里可以发送请求到后端进行登录验证
  • 显示成功提示(真实项目中应调用 API)
  • 可在此处跳转页面或设置登录状态

🟡 四、数据绑定与交互逻辑详解(Vue3/React)

✅ Vue3 中的数据绑定(Composition API)

const formData = reactive({ username: \'\', password: \'\', captcha: \'\'});
  • 使用 reactive 创建响应式对象
  • 模板中通过 v-model 自动绑定输入框值

✅ React 中的数据绑定(useState)

const [formData, setFormData] = useState({ username: \'\', password: \'\', captcha: \'\'});
  • 使用 useState 管理表单状态
  • 输入框通过 onChange 更新状态

✅ 输入框变更处理(React)

const handleChange = (e) => { setFormData({ ...formData, [e.target.name]: e.target.value });};
  • 使用动态键名更新对应字段
  • 实现双向绑定效果

🟣 五、完整算法流程图解(逻辑顺序)

开始│├── 初始化│ └── 页面加载时调用 generateCaptcha()│├── 用户操作│ ├── 输入用户名│ ├── 输入密码│ ├── 输入验证码│ └── 点击刷新验证码图标重新生成│├── 提交登录│ ├── 检查字段是否为空│ ├── 检查验证码是否正确│ │ └── 不正确 → 提示并刷新验证码│ └── 验证通过 → 提示登录成功(或调用 API)│└── 结束

✅ 六、关键技术点总结

技术点 说明 Canvas 绘图 使用 HTML5 Canvas 实现图形验证码绘制 字符干扰 添加旋转、颜色变化增强识别难度 干扰线 & 点 防止自动化识别工具识别验证码 验证码存储 使用 localStorage 临时保存用于校验 表单验证 实现字段非空校验和验证码一致性校验 响应式绑定 Vue3 使用 reactive,React 使用 useState 事件驱动 所有操作基于事件(点击、输入)驱动流程

📌 七、扩展建议(进阶方向)

方向 描述 后端接口对接 调用真实 API 接口完成登录 表单验证库 引入 Vuelidate / Yup / Formik 提升验证能力 多语言支持 支持国际化文案切换 暗黑模式 通过 CSS 变量切换主题色 动画过渡 添加验证码刷新动画提升用户体验 验证码过期机制 设置验证码有效期(如 5 分钟) 无障碍优化 添加 ARIA 属性,提升可访问性

再给大家来个双版本的登录最佳实践! 👇

🟢 Vue 3 实现版本

📄 文件结构:

components/└── LoginWithCaptcha.vue

💻 完整代码:

<template> <div class=\"login-container\">  <div class=\"login-form\"> <h2>登录</h2>  <div class=\"form-group\"> <label>用户名</label> <input v-model=\"formData.username\" type=\"text\" placeholder=\"请输入用户名\" /> </div>  <div class=\"form-group\"> <label>密码</label> <input v-model=\"formData.password\" type=\"password\" placeholder=\"请输入密码\" /> </div>  <div class=\"form-group captcha-row\"> <label>验证码</label> <div class=\"captcha-wrapper\">  <canvas ref=\"canvasRef\" width=\"100\" height=\"40\" @click=\"generateCaptcha\"></canvas>  <span class=\"refresh-icon\" @click=\"generateCaptcha\">🔄</span> </div> <input v-model=\"formData.captcha\" type=\"text\" placeholder=\"输入验证码\" /> </div>  <button class=\"login-btn\" @click=\"handleSubmit\">登录</button> </div> </div></template><script setup>import { ref, reactive, onMounted } from \'vue\';// 表单数据对象const formData = reactive({ username: \'\', password: \'\', captcha: \'\'});// canvas 元素引用const canvasRef = ref(null);// 生成随机验证码字符function generateRandomChar() { const chars = \'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\'; return chars.charAt(Math.floor(Math.random() * chars.length));}// 渲染验证码图像function drawCaptcha(captchaText) { const canvas = canvasRef.value; const ctx = canvas.getContext(\'2d\'); // 清空画布 ctx.clearRect(0, 0, canvas.width, canvas.height); // 设置背景颜色 ctx.fillStyle = \'#f0f0f0\'; ctx.fillRect(0, 0, canvas.width, canvas.height); // 添加干扰线 for (let i = 0; i < 4; i++) { ctx.beginPath(); ctx.moveTo(Math.random() * canvas.width, Math.random() * canvas.height); ctx.lineTo(Math.random() * canvas.width, Math.random() * canvas.height); ctx.strokeStyle = randomColor(150, 200); ctx.stroke(); } // 添加干扰点 for (let i = 0; i < 30; i++) { ctx.beginPath(); ctx.arc( Math.random() * canvas.width, Math.random() * canvas.height, 1, 0, Math.PI * 2 ); ctx.fillStyle = randomColor(150, 200); ctx.fill(); } // 绘制文字 ctx.font = \'bold 20px Arial\'; ctx.textAlign = \'center\'; ctx.textBaseline = \'middle\'; // 每个字符单独绘制并添加偏移和旋转 for (let i = 0; i < captchaText.length; i++) { ctx.fillStyle = randomColor(50, 150); ctx.save(); ctx.translate(20 + i * 20, 20); ctx.rotate((Math.random() * 20 - 10) * Math.PI / 180); ctx.fillText(captchaText[i], 0, 0); ctx.restore(); }}// 生成随机颜色function randomColor(min = 0, max = 255) { const r = Math.floor(Math.random() * (max - min + 1)) + min; const g = Math.floor(Math.random() * (max - min + 1)) + min; const b = Math.floor(Math.random() * (max - min + 1)) + min; return `rgb(${r},${g},${b})`;}// 生成新验证码function generateCaptcha() { let captcha = \'\'; for (let i = 0; i < 4; i++) { captcha += generateRandomChar(); } drawCaptcha(captcha); localStorage.setItem(\'captcha\', captcha); // 存储用于校验}// 表单提交处理function handleSubmit() { const storedCaptcha = localStorage.getItem(\'captcha\'); if (!formData.username || !formData.password || !formData.captcha) { alert(\'请填写所有字段\'); return; } if (formData.captcha.toLowerCase() !== storedCaptcha?.toLowerCase()) { alert(\'验证码错误,请重试\'); generateCaptcha(); return; } alert(\'登录成功!\'); // 这里可以发送请求到后端进行登录验证}// 页面加载时生成初始验证码onMounted(() => { generateCaptcha();});</script><style scoped>.login-container { display: flex; justify-content: center; align-items: center; height: 100vh; background-color: #f8f8f8;}.login-form { background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); width: 300px;}.form-group { margin-bottom: 1rem;}.form-group label { display: block; margin-bottom: 0.5rem; font-weight: bold;}.form-group input { width: 100%; padding: 0.5rem; border: 1px solid #ccc; border-radius: 4px;}.captcha-row .captcha-wrapper { display: flex; align-items: center; gap: 10px; margin-bottom: 0.5rem;}.refresh-icon { cursor: pointer; color: #007bff; font-size: 1.2em;}.login-btn { width: 100%; padding: 0.75rem; background-color: #007bff; color: white; border: none; border-radius: 4px; font-size: 1rem; cursor: pointer;}.login-btn:hover { background-color: #0056b3;}</style>

🔵 React 实现版本

📄 文件结构:

components/└── LoginWithCaptcha.jsx

💻 完整代码:

import React, { useRef, useState, useEffect } from \'react\';function LoginWithCaptcha() { // 表单状态 const [formData, setFormData] = useState({ username: \'\', password: \'\', captcha: \'\' }); // canvas 引用 const canvasRef = useRef(null); // 生成随机字符 const generateRandomChar = () => { const chars = \'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\'; return chars.charAt(Math.floor(Math.random() * chars.length)); }; // 渲染验证码图像 const drawCaptcha = (captchaText) => { const canvas = canvasRef.current; const ctx = canvas.getContext(\'2d\'); // 清空画布 ctx.clearRect(0, 0, canvas.width, canvas.height); // 设置背景颜色 ctx.fillStyle = \'#f0f0f0\'; ctx.fillRect(0, 0, canvas.width, canvas.height); // 添加干扰线 for (let i = 0; i < 4; i++) { ctx.beginPath(); ctx.moveTo(Math.random() * canvas.width, Math.random() * canvas.height); ctx.lineTo(Math.random() * canvas.width, Math.random() * canvas.height); ctx.strokeStyle = randomColor(150, 200); ctx.stroke(); } // 添加干扰点 for (let i = 0; i < 30; i++) { ctx.beginPath(); ctx.arc( Math.random() * canvas.width, Math.random() * canvas.height, 1, 0, Math.PI * 2 ); ctx.fillStyle = randomColor(150, 200); ctx.fill(); } // 绘制文字 ctx.font = \'bold 20px Arial\'; ctx.textAlign = \'center\'; ctx.textBaseline = \'middle\'; // 每个字符单独绘制并添加偏移和旋转 for (let i = 0; i < captchaText.length; i++) { ctx.fillStyle = randomColor(50, 150); ctx.save(); ctx.translate(20 + i * 20, 20); ctx.rotate((Math.random() * 20 - 10) * Math.PI / 180); ctx.fillText(captchaText[i], 0, 0); ctx.restore(); } }; // 生成随机颜色 const randomColor = (min = 0, max = 255) => { const r = Math.floor(Math.random() * (max - min + 1)) + min; const g = Math.floor(Math.random() * (max - min + 1)) + min; const b = Math.floor(Math.random() * (max - min + 1)) + min; return `rgb(${r},${g},${b})`; }; // 生成新验证码 const generateCaptcha = () => { let captcha = \'\'; for (let i = 0; i < 4; i++) { captcha += generateRandomChar(); } drawCaptcha(captcha); localStorage.setItem(\'captcha\', captcha); // 存储用于校验 }; // 表单提交处理 const handleSubmit = (e) => { e.preventDefault(); const storedCaptcha = localStorage.getItem(\'captcha\'); if (!formData.username || !formData.password || !formData.captcha) { alert(\'请填写所有字段\'); return; } if (formData.captcha.toLowerCase() !== storedCaptcha?.toLowerCase()) { alert(\'验证码错误,请重试\'); generateCaptcha(); return; } alert(\'登录成功!\'); // 这里可以发送请求到后端进行登录验证 }; // 输入框变更处理 const handleChange = (e) => { setFormData({ ...formData, [e.target.name]: e.target.value }); }; // 页面加载时生成初始验证码 useEffect(() => { generateCaptcha(); }, []); return ( <div className=\"login-container\"> <div className=\"login-form\"> <h2>登录</h2> <form onSubmit={handleSubmit}> {/* 用户名 */} <div className=\"form-group\"> <label>用户名</label> <input  name=\"username\"  value={formData.username}  onChange={handleChange}  type=\"text\"  placeholder=\"请输入用户名\" /> </div> {/* 密码 */} <div className=\"form-group\"> <label>密码</label> <input  name=\"password\"  value={formData.password}  onChange={handleChange}  type=\"password\"  placeholder=\"请输入密码\" /> </div> {/* 验证码 */} <div className=\"form-group captcha-row\"> <label>验证码</label> <div className=\"captcha-wrapper\">  <canvas ref={canvasRef} width={100} height={40} onClick={generateCaptcha} />  <span className=\"refresh-icon\" onClick={generateCaptcha}>🔄</span> </div> <input  name=\"captcha\"  value={formData.captcha}  onChange={handleChange}  type=\"text\"  placeholder=\"输入验证码\" /> </div> {/* 登录按钮 */} <button className=\"login-btn\" type=\"submit\">登录</button> </form> </div> </div> );}export default LoginWithCaptcha;

🎨 样式文件(App.css 或组件内样式):

.login-container { display: flex; justify-content: center; align-items: center; height: 100vh; background-color: #f8f8f8;}.login-form { background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); width: 300px;}.form-group { margin-bottom: 1rem;}.form-group label { display: block; margin-bottom: 0.5rem; font-weight: bold;}.form-group input { width: 100%; padding: 0.5rem; border: 1px solid #ccc; border-radius: 4px;}.captcha-row .captcha-wrapper { display: flex; align-items: center; gap: 10px; margin-bottom: 0.5rem;}.refresh-icon { cursor: pointer; color: #007bff; font-size: 1.2em;}.login-btn { width: 100%; padding: 0.75rem; background-color: #007bff; color: white; border: none; border-radius: 4px; font-size: 1rem; cursor: pointer;}.login-btn:hover { background-color: #0056b3;}

✅ 总结

功能 Vue 3 实现 React 实现 数据绑定 使用 ref, reactive 使用 useState 生命周期 使用 onMounted 使用 useEffect DOM 操作 使用 ref 使用 useRef 事件绑定 @click 等指令 onClick 等属性 表单控制 v-model 受控组件 + onChange 验证码生成 Canvas 绘图 Canvas 绘图

你可以将上述任意一种实现方式集成到你的项目中,打造一个功能完整的登录页。