图形验证码
可实现字母、数字、中文等多种验证码类型
该组件不负责生成验证码 只用于显示验证码
import React, { useEffect, useState } from 'react'
interface CaptchaProps {
// 父组件传过来的值 可通过接口或随机生成
content: string
// 刷新验证码的方法 必传
onClick: () => void
// 是否需要干扰点
point?: boolean
}
const Captcha = (props: CaptchaProps) => {
const { content, onClick, point } = props
const [options, setOptions] = useState({
id: 'verifyCode', // 容器Id
canvasId: 'verifyCanvas', // canvas的ID
width: 150, // 默认canvas宽度
height: 47, // 默认canvas高度
code: '',
})
useEffect(() => {
_init()
}, [])
useEffect(() => {
refresh()
}, [content])
const _init = () => {
const con = document.getElementById(options.id)!
const canvas: HTMLCanvasElement = document.createElement('canvas')
options.width = con.offsetWidth > 0 ? con.offsetWidth : 150
options.height = con.offsetHeight > 0 ? con.offsetHeight : 47
canvas.id = options.canvasId
canvas.width = options.width
canvas.height = options.height
canvas.style.cursor = 'pointer'
canvas.innerHTML = '您的浏览器版本不支持canvas'
con.appendChild(canvas)
// 可调用刷新验证码的方法
con.onclick = onClick
}
const refresh = () => {
let txtArr
options.code = ''
const canvas: any = document.getElementById(options.canvasId)
let ctx = null
if (canvas.getContext) {
ctx = canvas.getContext('2d')
} else {
return
}
ctx.clearRect(0, 0, options.width, options.height)
ctx.textBaseline = 'middle'
ctx.fillStyle = randomColor(180, 240)
ctx.fillStyle = 'rgba(206, 244, 196)' // 背景色
ctx.fillRect(0, 0, options.width, options.height)
txtArr = content
for (let i = 0; i < content.length; i++) {
const txt = txtArr[i]
options.code += txt
ctx.font = randomNum(options.height / 2, options.height) + 'px SimHei' // 随机生成字体大小
ctx.fillStyle = randomColor(50, 160) // 随机生成字体颜色
ctx.shadowOffsetX = randomNum(-3, 3)
ctx.shadowOffsetY = randomNum(-3, 3)
ctx.shadowBlur = randomNum(-3, 3)
ctx.shadowColor = 'rgba(0, 0, 0, 0.3)'
const x = getX(i)
const y = options.height / 2
const deg = randomNum(-30, 30)
/** 设置旋转角度和坐标原点**/
ctx.translate(x, y)
ctx.rotate((deg * Math.PI) / 180)
ctx.fillText(txt, 0, 0)
/** 恢复旋转角度和坐标原点**/
ctx.rotate((-deg * Math.PI) / 180)
ctx.translate(-x, -y)
}
/** 绘制干扰线**/
for (let i = 0; i < 4; i++) {
ctx.strokeStyle = randomColor(40, 180)
ctx.beginPath()
ctx.moveTo(randomNum(0, options.width), randomNum(0, options.height))
ctx.lineTo(randomNum(0, options.width), randomNum(0, options.height))
ctx.stroke()
}
// 干扰点
if (point) {
for (let i = 0; i < 100; i++) {
ctx.fillStyle = randomColor(0, 255)
ctx.beginPath()
ctx.arc(randomNum(0, options.width), randomNum(0, options.height), 1, 0, 2 * Math.PI)
ctx.fill()
}
}
}
/** 生成一个随机数**/
const randomNum = (min: number, max: number) => {
return Math.floor(Math.random() * (max - min) + min)
}
/** 获取每个字符的x位置 使验证码在图片中均匀分布 */
const getX = (i: number) => {
// 计算平均每个字符的宽度
const avgWidth = (options.width - 10) / content.length
// 计算当前字符的x位置
return avgWidth * i + avgWidth / 2
}
/** 生成一个随机色**/
const randomColor = (min: number, max: number) => {
const r = randomNum(min, max)
const g = randomNum(min, max)
const b = randomNum(min, max)
return 'rgb(' + r + ',' + g + ',' + b + ')'
}
return (
<div
id="verifyCode"
className="w-full h-10 inline-block top-0 right-0"
/>
)
}
export default Captcha
验证码的div采用tailwindcss, 如果项目中没有使用tailwindcss, 可以自行修改样式
将w-full h-10 inline-block top-0 right-0
转为style:
return (
<div
id="verifyCode"
style={{ width: '100%', height: '40px', display: 'inline-block', top: 0, right: 0 }}
/>
)
效果如下