最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • react中用canvas实现手势图案解锁设置

    正文概述 掘金(珺就是我)   2020-12-30   572

    前言

    该组件的ui参照了招商银行app的手势登录设置和手势登录验证页面,用canvas实现了设置手势密码的功能和验证手势密码的功能,绘制手势时按顺序记录所有经过的九宫格的点,作为手势密码。

    react中用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;
        }
      }
    }
    

    起源地下载网 » react中用canvas实现手势图案解锁设置

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元