设计思路与之前的相同,不过为了解耦和优化DOM操作,我们分解了改组件,使用hook来完成。
代码如下:
import { useState, useEffect, useRef } from 'react';
function PicValidate(props) {
const {
width,
height,
} = props;
const initialOptions = {
// actually invalid in development but maybe useful in future
canvasId: 'verifyCanvas',
width: '250',
height: '100',
// blend (数字字母混合)
// number:(纯数字)
// letter 纯字母
type: 'blend',
code: '',
};
// no need to save data in states
// const [initialOptions, setOptions] = useState(initialOption);
if (Object.prototype.toString.call(props) === '[object Object]'){
for(let i in props) {
initialOptions[i] = props[i];
}
}
initialOptions.numArr = [0,1,2,3,4,5,6,7,8,9];
initialOptions.letterArr = getAllLetters();
const canvasItem = useRef();
useEffect(() => {
refreshIt(initialOptions, canvasItem);
},[width])
return (
<canvas
ref={canvasItem}
width={width}
height={height}
style={{cursor: 'pointer'}}
onClick={() => {
refreshIt(initialOptions, canvasItem)
}}
>您的浏览器版本不支持使用canvas</canvas>
);}
const refreshIt = (options, canvasItem) => {
options.code = '';
let canvas = canvasItem.current;
let ctx;
let txtArr;
if (canvas && canvas.getContext) {
ctx = canvas.getContext('2d');
} else {
return ;
}
ctx.textBaseline = 'middle';
// 样式设置
ctx.fillStyle = randomColor(180,240);
ctx.fillRect(0,0,options.width,options.height);
// 设置验证码对应数组范围 range
if (options.type === 'blend') {
txtArr = options.numArr.concat(options.letterArr);
} else if (options.type === 'number') {
txtArr = options.numArr;
} else {
txtArr = options.letterArr;
}
// 绘制页面主要内容及整体样式
for (let i = 0; i < 4; i++) {
let txt = txtArr[randomNum(0,txtArr.length)];
options.code += txt;
ctx.font = randomNum(options.height / 2, options.height) + 'px SimHei';
ctx.fillStyle = randomColor(50,160); // 随机生成字体颜色
ctx.shadowOffsetX = randomNum(-2,2);
ctx.shadowOffsetY = randomNum(-2,2);
ctx.shadowBlur = randomNum(-2,2);
ctx.shadowColor = 'rgba(0,0,0,0.3)';
let x = options.width /6 * (i+1);
let y = options.height / 2;
let 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();
}
// 绘制干扰点
for (let i = 0; i <options.width / 4; 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();
}
if (options.callback) {
options.callback(options.code.toLowerCase());
}}
const getAllLetters = function(){
let letterStr = 'a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z';
return letterStr.split(',');
}
const randomNum = (min, max) => {
return Math.floor(Math.random()*(max - min) + min);
}
const randomColor = (min, max) => {
let r = randomNum(min, max);
let g = randomNum(min, max);
let b = randomNum(min, max);
return `rgb(${r},${g},${b})`
}
function Randoms(props){
const {
callback,
} = props;
const divRef = useRef();
useEffect(() => {
setOffSet([divRef.current.offsetWidth, divRef.current.offsetHeight])
}, [])
const [offset, setOffSet] = useState([0,0])
return (
<div ref={divRef}>
<PicValidate
type="blend"
width={offset[0] || '100'}
height={offset[1] || '40'}
callback={callback}
/>
</div>
)}
export default Randoms;
在父组件中直接调用即可:
<Randoms
callback={(picValidateCode) => {
this.setState({
picValidateCode,
})
}}
/>
以上。