前言
该组件的ui参照了招商银行app的手势登录设置和手势登录验证页面,用canvas实现了设置手势密码的功能和验证手势密码的功能,绘制手势时按顺序记录所有经过的九宫格的点,作为手势密码。
其中,绘制手势时,连接不能少于4个点,每个点最多经过一次,不限制设置密码的长度:
- 设置手势密码时,第一次绘制时,上方小九宫格会记录密码经过的点,作为再次绘制解锁图案时的提示,第二次绘制图案与第一次手势相符时,则绘制成功,否则重新绘制;
- 验证手势密码时,绘制手势密码后验证通过,则解锁成功,否则提示密码错误;
1、绘制九宫格界面
由于首次进入页面就应该展示九宫格界面,所以将绘制图案的初始化写在componentDidMount()生命周期函数中,建两个canvas对象:用于提示的手势图案ctxhow和用于绘制手势的ctx,并且初始化九宫格解锁界面;
//初始化界面
function init() {
ctx.clearRect(0, 0, width, height); //清空画布
ctxshow.clearRect(0, 0, widthShow, heightShow);
pointerArr = []; //清除绘制路径
for (var i = 0; i < arr.length; i++) {
arr[i].state = 0; //清除绘制状态
arr[i].stateShow = 0;
drawPointer(i);
}
}
2、绘制路径
- 在九宫格界面所在的dom元素上绑定点击滑动等事件,绘制路径;
- 用isPointInPath判断九宫格的点(x, y)是否在当前滑动的路径中,如过滑到了坐标点且该点未被选中过,则保存该点至路径数组中,并且改变该坐标点的样式;
- 绘制当前滑动位置到坐标点的线
3、结束绘制后的事件
-
验证手势密码时:
-
(1)密码小于4位。提示'至少连接4个点,请重新绘制',且清空所有九宫格;
-
(2)密码大于4位。判断密码是否正确,正确则提示"密码解锁成功",错误则提示"密码错误!请重新输入"。清空所有九宫格;
-
设置手势密码:
-
(1)密码小于4位。提示'至少连接4个点,请重新绘制',判断是否第二次绘制,是则清空所有九宫格;否则是第一次绘制,只清空绘制九宫格,上部分的展示九宫格留着;
-
(2)密码大于4位。判断是否第二次绘制:
-
① 是则判断第二次绘制是否与第一次绘制图案一致,一致的话,提示"手势密码设置成功"后清空九宫格;不一致的话提示'两次绘制图案不一致,请重新绘制'后清空九宫格,延时两秒后回到第一次绘制;
-
② 否则是第一次绘制,保存绘制图案,只清空绘制九宫格,上部分的展示九宫格留着;
具体代码实现如下: PatternLock.jsx
import React, { Component } from "react";
import { observer, inject } from "mobx-react";
import { withRouter } from "react-router";
import MainPage from "../EmptyContainer/EmptyContainer";
import { SwipeAction, List } from 'antd-mobile';
import Navbar from '../Components/Navbar/Navbar'
const navBarStyle = { background: '#fff', color: '#333' }
const leftIcon = require('../../static/images/SettingBack.png')
let pointerArr = []; // 绘制路径
let startX, startY; //线条起始点
let puts = []; //经过的九个点的数组
let currentPointer; //当前点是否已经连接
let pwd = [1,2,4,5,7]; //密码
let confirmPwd = []; //确认密码
let unlockFlag = false; //是否解锁的标志
let isMouseDown = false;
let arr = []; //九个点的坐标数组
let canvas, ctx, width, height;
let canvasShow, ctxshow, widthShow, heightShow;
import "./PatternLock.less";
import deviceStore from "../../store/deviceStore";
@inject("doorStore", "deviceStore")
@withRouter
@observer
class PatternLock extends Component {
constructor(props) {
super(props);
this.state = {
patternPassWord: [],
setOrCheck: true, // 设置或验证手势密码页面 设置true 获取false
tooEasy: false, // 至少连接4个点
setAgain: false, // 再次设置手势
isFit: true, // 第二次设置手势是否与第一次一致
}
}
componentDidMount() {
canvas = document.getElementById('canvas'); // 绘制手势画布
ctx = canvas.getContext('2d'); // 得到画布的上下文对象
canvas.width = this.refs["drawPattern"].clientWidth;
canvas.height = this.refs["drawPattern"].clientHeight;
width = canvas.width;
height = canvas.height; //画布的宽高
canvasShow = document.getElementById('canvasShow'); // 手势展示的画布
ctxshow = canvasShow.getContext('2d');
canvasShow.width = this.refs["showPattern"].clientWidth;
canvasShow.height = this.refs["showPattern"].clientHeight;
widthShow = canvasShow.width;
heightShow = canvasShow.height; //画布的宽高
console.log(canvas.offsetTop, width, height)
//九宫格中9个点的坐标对象
let lockCicle = {
x: 0, //x坐标
y: 0, //y坐标
showX: 0, // 手势展示的点坐标
showY: 0,
color: "#999999",
state: "1", // 当前点状态,是否已经被链接过
stateShow: "1", // 手势展示的点状态
};
let offset = (width - height) / 2; //计算偏移量
arr = []; //九个点的坐标数组
//计算九个点坐标
for (let i = 1; i <= 3; i++) {
for (let j = 1; j <= 3; j++) {
let lockCicle = {};
if (offset > 0) { //横屏
lockCicle.x = (height / 4) * j + Math.abs(offset);
lockCicle.y = (height / 4) * i - height / 5;
lockCicle.state = 0;
lockCicle.stateShow = 0;
} else { //竖屏
lockCicle.x = (width / 4) * j;
lockCicle.y = (width / 4) * i + Math.abs(offset) - height / 5;
lockCicle.state = 0;
lockCicle.stateShow = 0;
}
lockCicle.showX = (heightShow / 4) * j;
lockCicle.showY = (heightShow / 4) * i;
arr.push(lockCicle);
}
}
//初始化界面
function init() {
ctx.clearRect(0, 0, width, height); //清空画布
ctxshow.clearRect(0, 0, widthShow, heightShow);
pointerArr = []; //清除绘制路径
for (var i = 0; i < arr.length; i++) {
arr[i].state = 0; //清除绘制状态
arr[i].stateShow = 0;
drawPointer(i);
}
}
//初始化界面
init();
// *****
// 绘制九宫格解锁界面
// *****
function drawPointer(i) {
ctx.save();
ctxshow.save();
let radius = width / 12;
let _fillStyle = "#ccc";
let _strokeStyle = "#ccc";
let _strokeStyleShow = "#ccc";
if (arr[i].state == 1) { //不同状态显示不同颜色
_strokeStyle = "#0286fa";
_fillStyle = "#0286fa";
}
if(arr[i].stateShow == 1) {
_strokeStyleShow = "#0286fa";
}
//绘制原点
ctx.beginPath(); // 起始一条路径
ctx.fillStyle = _fillStyle; // 填充颜色
ctx.arc(arr[i].x, arr[i].y, 6, 0, Math.PI * 2, false); // 创建曲线 false顺时针
ctx.fill();
ctx.closePath(); // 创建从当前点回到起始点的路径
//绘制手势展示的原点
ctxshow.beginPath();
ctxshow.fillStyle = _strokeStyleShow;
ctxshow.arc(arr[i].showX, arr[i].showY, 4, 0, Math.PI * 2, false);
ctxshow.fill();
ctxshow.closePath();
//绘制圆圈
ctx.beginPath();
ctx.strokeStyle = _strokeStyle;
ctx.lineWidth = 0.3;
ctx.arc(arr[i].x, arr[i].y, radius, 0, Math.PI * 2, false);
ctx.stroke();
ctx.closePath();
ctx.restore(); // 返回之前保存过的路径状态和属性
}
}
//初始化界面
init = (flag) => { // flag:true表示需要格式化展示用的九宫格路径,false表示不需要
ctx.clearRect(0, 0, width, height); //清空画布
if(flag){
ctxshow.clearRect(0, 0, widthShow, heightShow);
}
pointerArr = []; //清除绘制路径
for (var i = 0; i < arr.length; i++) {
arr[i].state = 0; //清除绘制状态
if(flag) {
arr[i].stateShow = 0;
}
this.drawPointer(i, flag);
}
}
drawPointer = (i, flag) => {
let radius = width / 12;
let _fillStyle = "#ccc";
let _strokeStyle = "#ccc";
let _strokeStyleShow = "#ccc";
if (arr[i].state == 1) { //不同状态显示不同颜色
_strokeStyle = "#0286fa";
_fillStyle = "#0286fa";
}
if(arr[i].stateShow == 1) {
_strokeStyleShow = "#0286fa";
}
//绘制原点
ctx.save();
ctx.beginPath(); // 起始一条路径
ctx.fillStyle = _fillStyle; // 填充颜色
ctx.arc(arr[i].x, arr[i].y, 6, 0, Math.PI * 2, false); // 创建曲线 false顺时针
ctx.fill();
ctx.closePath(); // 创建从当前点回到起始点的路径
//绘制圆圈
ctx.beginPath();
ctx.strokeStyle = _strokeStyle;
ctx.lineWidth = 0.3;
ctx.arc(arr[i].x, arr[i].y, radius, 0, Math.PI * 2, false);
ctx.stroke();
ctx.closePath();
ctx.restore(); // 返回之前保存过的路径状态和属性
if(flag) {
//绘制手势展示的原点
ctxshow.save();
ctxshow.beginPath();
ctxshow.fillStyle = _strokeStyleShow;
ctxshow.arc(arr[i].showX, arr[i].showY, 4, 0, Math.PI * 2, false);
ctxshow.fill();
ctxshow.closePath();
}
}
// *****
// 绘制连接线的方法,将坐标数组中的点绘制在canvas画布中
// *****
drawLinePointer = (x, y, flag) => {
// 绘制点到点的线
ctx.clearRect(0, 0, width, height); // 清空画布
ctx.save(); // 保存当前环境的状态
ctx.beginPath();
ctx.strokeStyle = "#0286fa";
ctx.lineWidth = 6;
ctx.lineCap = "round"; // 设置或返回线条的结束端点样式
ctx.lineJoin = "round"; // 相交时的拐角类型
for (var i = 0; i < pointerArr.length; i++) {
if (i == 0) {
ctx.moveTo(pointerArr[i].x, pointerArr[i].y);
} else {
ctx.lineTo(pointerArr[i].x, pointerArr[i].y);
}
}
ctx.stroke();
ctx.closePath();
ctx.restore();
// 保存经过的点
for (var i = 0; i < arr.length; i++) {
this.drawPointer(i,true); //绘制圆圈和原点
//isPointInPath判断点(x, y)是否在路径中,有则返回true,否则返回false;同时判断该点是否已经经过
if (ctx.isPointInPath(x, y) && currentPointer != i && puts.indexOf(i + 1) < 0) {
pointerArr.push({
x: arr[i].x,
y: arr[i].y
});
currentPointer = i;
puts.push(i + 1); // 保存该坐标点到路径数组中
startX = arr[i].x;
startY = arr[i].y;
arr[i].state = 1;
if(!this.state.setAgain) { // 第二次设置手势,小九宫格不再做相应改变
arr[i].stateShow = 1;
}
}
}
// 绘制点到当前鼠标坐标的线
if (flag) {
ctx.save();
ctx.beginPath();
ctx.globalCompositeOperation = "destination-over"; // 在源图像上方显示目标图像
ctx.strokeStyle = "#e2e0e0";
ctx.lineWidth = 6;
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.moveTo(startX, startY);
ctx.lineTo(x, y);
ctx.stroke();
ctx.beginPath();
ctx.restore();
}
}
canvasTouchStart = (e) => {
isMouseDown = true;
let x1 = e.targetTouches[0].pageX;
let y1 = e.targetTouches[0].pageY - canvas.offsetTop;
this.drawLinePointer(x1, y1, false);
}
canvasTouchMove = (e) => {
if (isMouseDown) {
let x1 = e.targetTouches[0].pageX;
let y1 = e.targetTouches[0].pageY - canvas.offsetTop;
this.drawLinePointer(x1, y1, true);
}
}
canvasTouchEnd = (e) => {
this.drawLinePointer(0, 0, false);
isMouseDown = false;
pointerArr = [];
if(this.state.setOrCheck) { // 设置手势密码页面
if (puts.length >= 4) {
this.setState({
tooEasy: false
})
if(this.state.setAgain) { // 第二次设置手势
if(JSON.stringify(puts)==JSON.stringify(this.state.patternPassWord)) {
this.setState({
setAgain: false,
isFit: true
})
alert("手势密码设置成功")
} else {
console.log("两次绘制图案不一致,请重新绘制")
this.setState({
setAgain: false,
isFit: false
}, ()=>{
setTimeout(()=>{this.setState({isFit:true})},2000) // 两次绘制不一致,延时两秒后重新绘制
})
}
console.log(puts, this.state.patternPassWord,JSON.stringify(puts)==JSON.stringify(this.state.patternPassWord),'第二次设置手势')
this.init(true);
} else { // 第一次设置手势
this.setState({
setAgain: true,
patternPassWord: puts,
})
this.init(false);
}
console.log("你的图案密码是: [ " + puts.join(" > ") + " ]");
this.init(false);
} else {
if (puts.length >= 1) {
console.log("图案密码太简单了~~~");
this.setState({tooEasy:true})
if(this.state.setAgain) {
this.init(false);
} else {
this.init(true);
}
}
}
} else { // 验证手势页面
if (puts.length >= 4) {
this.setState({
tooEasy: false
})
if(JSON.stringify(puts)==JSON.stringify(pwd)) {
alert("密码解锁成功")
} else {
alert("密码错误!请重新输入")
}
console.log('你输入的密码:',JSON.stringify(puts),'实际密码:', JSON.stringify(pwd))
this.init(true);
} else {
if (puts.length >= 1) {
console.log("图案密码太简单了~~~");
this.setState({tooEasy:true})
this.init(true);
}
}
}
puts = [];
}
render() {
return (
<div className="PLcontainer">
<Navbar navBarStyle={navBarStyle} backKey={1} pageTitle={this.state.setOrCheck ? '设置手势密码' : '验证手势密码'} leftIcon={leftIcon} />
<div className="PatternLock">
<div className="showPattern" ref="showPattern">
<canvas id="canvasShow"></canvas>
</div>
<div className="tips">
<div className="inputTips">
<p>{this.state.setAgain?'请再次绘制解锁图案':'请绘制解锁图案'}</p>
<div className="guide">
<p className={`guideTips${this.state.tooEasy||!this.state.isFit?'':' hide'}`}>
{this.state.isFit?'至少连接4个点,请重新绘制':'两次绘制图案不一致,请重新绘制'}
</p>
</div>
</div>
</div>
<div className="drawPattern" ref="drawPattern">
<canvas
id="canvas"
onTouchStart={this.canvasTouchStart}
onTouchMove={this.canvasTouchMove}
onTouchEnd={this.canvasTouchEnd}
></canvas>
</div>
</div>
</div>
);
}
}
export default PatternLock;
PatternLock.less
.PLcontainer {
width: 100%;
height: 100%;
background: #ffffff;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1;
min-height: 1%;
overflow-x: hidden;
overflow-y: hidden;
.PatternLock {
width: 100%;
height: 100%;
padding-top: 100px;
.showPattern {
width: 110px;
height: 110px;
margin: 0 auto;
}
.tips {
width: 100%;
.inputTips {
margin-top: 50px;
margin-bottom: 40px;
font-size: 36px;
text-align: center;
.guide {
height: 28px;
line-height: 28px;
margin-top: 26px;
.guideTips {
font-size: 26px;
color: #e91919;
&.hide {
display: none;
}
}
}
}
}
.drawPattern {
width: 100%;
height: 958px;
}
}
}
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!