h5使用canvas画布实现手势解锁


Posted in HTML / CSS onJanuary 04, 2019

前言

最近做的一个app项目使用的 apicloud 来实现跨平台开发,现在需要为这个 app 添加手势(九宫格)解锁的功能,apicloud 已经有一些第三方的原生实现的手势解锁插件,因为是原生的性能也比较好,调用也比较方便,但是都不能对它们的样式做修改,所以就打算自己来实现这个功能。这篇文章将实现过程整理分享出来,希望有需要的可以了解。分享出来的代码只实现了最基本的 设置密码功能解锁功能比较密码 的功能等,一些高级功能例如:不能限制一个点最多经过多少次、限制用户设置密码的长度。

h5使用canvas画布实现手势解锁

原生实现还是其它方式实现?

1、使用 android 和 ios 对应的平台通过原生代码来写手势解锁插件。体验好,但是开发周期长,需要处理各平台的兼容性问题,并且需要学习apicloud平台插件编写方法。(放弃)

2、使用 html5 的 canvas 画布来实现。开发周期短,不需要过多的处理兼容性问题,体验好。(选择)

原理分析

h5使用canvas画布实现手势解锁

手势解锁

通过手指将屏幕上的九个点依次连接起来形成一个图案,所以叫图案解锁。如上图每一个解锁圆圈后面其实都是一个数字,每次比较的并不是是用户画出来的图案,而是每次手指经过图案时串联起来的圆圈下的数字组成的密码字符串,本质上我们比较的还是字符串的密码,只不过站在用户的角度看是绘制出来的图案。图案的记忆远比数字字符串记的牢固。

实现步骤

绘制密码盘

密码盘的绘制比较简单,唯一需要注意需要通过动态计算使九个点围成的正方式始终在屏幕的中间位置,在手机上还需要减去状态栏的高度。

var width = $(document).width();
var height = $(document).height() - 40; //减去手机状态栏的高度

//九宫格其实就是九个点,9个点的坐标对象
var lockCicle = {
    x: 0, //x坐标
    y: 0, //y坐标
    color: "#999999",
    state: "1" //状态当前点是否已经被链接过
};

var offset = (width - height) / 2; //计算偏移量
var arr = []; //九个点的坐标数组

//计算九个点坐标的方法
for (var i = 1; i <= 3; i++) {
    //每一行
    for (var j = 1; j <= 3; j++) {
        //每一行的每一个
        var lockCicle = {};
        //横屏
        if (offset > 0) {
            lockCicle.x = (height / 4) * j + Math.abs(offset);
            lockCicle.y = (height / 4) * i;
            lockCicle.state = 0;
            //竖屏
        } else {
            lockCicle.x = (width / 4) * j;
            lockCicle.y = (width / 4) * i + Math.abs(offset);
            lockCicle.state = 0;
        }
        arr.push(lockCicle);
    }
}

//初始化界面的方法
function init() {
    ctx.clearRect(0, 0, width, height); //清空画布
    pointerArr = []; //清楚绘制路径
    for (var i = 0; i < arr.length; i++) {
        arr[i].state = 0; //清除绘制状态
        drawPointer(i);
    }
}

//绘制九宫格解锁界面
function drawPointer(i) {
    ctx.save();
    var radius = 0;
    if (hastouch) {
        radius = width / 12;
    } else {
        radius = 24;
    }
    var _fillStyle = "#dd514c";
    var _strokeStyle = "#dd514c";
    //不同状态显示不同颜色
    if (arr[i].state == 1) {
        _strokeStyle = "#1bd6c5";
    }
    //绘制原点
    ctx.beginPath();
    ctx.fillStyle = _fillStyle;
    ctx.arc(arr[i].x, arr[i].y, 6, 0, Math.PI * 2, false);
    ctx.fill();
    ctx.closePath();
    //绘制圆圈
    ctx.beginPath();
    ctx.strokeStyle = _strokeStyle;
    ctx.lineWidth = 0.3;
    ctx.lineCap = "round";
    ctx.lineJoin = "round";
    ctx.arc(arr[i].x, arr[i].y, radius, 0, Math.PI * 2, false);
    ctx.stroke();
    ctx.closePath();
    ctx.restore();
}

//初始化界面
init();

绘制连线

绘制连线的方法

var pointerArr = []; //连接线点的坐标数组
var startX, startY; //线条起始点
var puts = []; //经过的九个点的数组
var currentPointer; //当前点是否已经连接
var pwd = []; //密码
var confirmPwd = []; //确认密码
var unlockFlag = false; //是否解锁的标志

/**
 ** 绘制链接线的方法,将坐标数组中的点绘制在canvas画布中
 **/
function drawLinePointer(x, y, flag) {
    ctx.clearRect(0, 0, width, height);
    ctx.save();
    ctx.beginPath();
    ctx.strokeStyle = "#1bd6c5";
    ctx.lineWidth = 5;
    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++) {
        drawPointer(i); //绘制圆圈和原点
        if (ctx.isPointInPath(x, y) && currentPointer != i) {
            //判断鼠标点击是否在圆中
            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 (flag) {
        ctx.save();
        ctx.beginPath();
        ctx.globalCompositeOperation = "destination-over";
        ctx.strokeStyle = "#e2e0e0";
        ctx.lineWidth = 5;
        ctx.lineCap = "round";
        ctx.lineJoin = "round";
        ctx.moveTo(startX, startY);
        ctx.lineTo(x, y);
        ctx.stroke();
        ctx.beginPath();
        ctx.restore();
    }
}

绑定事件

连线的过程就是将 3 个 touch(移动端) 事件组合起来获取当前位置的坐标放入数组中,然后将这些坐标渲染到界面上的过程。

  • touchstart (mousedown) 当手指(鼠标)按下时设置 isMouseDown=true,同时将该点的坐标保存到线条数组中,并将数组中的点绘制出来。
  • touchmove (mousemove) 当 isMouseDown=true 时 将手指(鼠标)移动过程中所有的坐标点都保存到萧条数组中,并将数组中的点绘制出来。
  • mouseup (mouseup) 当手指(鼠标)松开后设置 isMouseDown=fasle.将数组中的所有点绘制出来,清空 pointerArr 数组,然后比较连接的点的数量如果小于 6(自己设置,一般密码 6 位以上)给一个密码长度不够的提示,清空 puts 数组,重新调用 init 方法初始化界面,如果大于等于 6 则密码设置成功。
//兼容移动触摸的事件写法
var hastouch = "ontouchstart" in window ? true : false,
    tapstart = hastouch ? "touchstart" : "mousedown",
    tapmove = hastouch ? "touchmove" : "mousemove",
    tapend = hastouch ? "touchend" : "mouseup";

//绑定按下事件
lockCnavs.addEventListener(tapstart, function(e) {
    isMouseDown = true;
    var x1 = hastouch
        ? e.targetTouches[0].pageX
        : e.clientX - canvas.offsetLeft;
    var y1 = hastouch ? e.targetTouches[0].pageY : e.clientY - canvas.offsetTop;
    drawLinePointer(x1, y1, true);
});

//移动时候,将经过的坐标点全部保存起来
lockCnavs.addEventListener(tapmove, function(e) {
    if (isMouseDown) {
        var x1 = hastouch
            ? e.targetTouches[0].pageX
            : e.clientX - canvas.offsetLeft;
        var y1 = hastouch
            ? e.targetTouches[0].pageY
            : e.clientY - canvas.offsetTop;
        drawLinePointer(x1, y1, true);
    }
});

//取消
lockCnavs.addEventListener(tapend, function(e) {
    drawLinePointer(0, 0, false);
    isMouseDown = false;
    pointerArr = [];
    if (puts.length >= 6) {
        alert("你的图案密码是: [   " + puts.join("    >   ") + "   ]");
        if (unlockFlag) {
            //解锁
            unlock();
        } else {
            //设置解锁密码
            settingUnlockPwd();
        }
    } else {
        if (puts.length >= 1) {
            alert("你的图案密码太简单了~~~");
            init();
        }
    }
    puts = [];
});

实现解锁逻辑

通过上面几步的操作,九宫格解锁每一次绘图之后的数据和显示效果都有了,现在只需要在关键地方添加相应逻辑代码就可以了,这里主要介绍它的实现逻辑就不对代码做封装了。

相关代码

//设置解锁密码和解锁测试
function settingUnlockPwd() {
    if (pwd.length <= 0) {
        pwd = puts;
        init();
        $("header").text("再次绘制解锁图案");
    } else if (confirmPwd.length <= 0) {
        confirmPwd = puts;
    }
    console.log(pwd + "  " + confirmPwd);
    //笔记两次密码是否正确
    if (pwd.length > 0 && confirmPwd.length > 0) {
        if (compareArr(pwd, confirmPwd)) {
            $("header").text("解锁图案绘制成功");
            init();
        } else {
            $("header").text("两次绘制的解锁图案不一致");
            init();
            confirmPwd = [];
        }
    }
}
//解锁
function unlock() {
    console.log("解锁密码:" + puts + "  " + confirmPwd);
    if (compareArr(puts, confirmPwd)) {
        $("header").text("解锁成功!页面跳转中......");
    } else {
        $("header").text("解锁图案不正确!!!");
        init();
    }
}
$("footer").click(function() {
    if ($(this).text() === "解锁") {
        unlockFlag = true;
        init();
        $("header").text("绘制解锁图案");
    }
});
//比较两个数组(Number)是否相等
function compareArr(arr1, arr2) {
    return arr1.toString() === arr2.toString();
}

后记

本文完整 demo 在线演示地址

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

HTML / CSS 相关文章推荐
CSS3美化表单控件全集
Jun 29 HTML / CSS
css背景图片的背景裁切、背景透明度、背景变换等效果运用
Dec 24 HTML / CSS
CSS3制作文字半透明倒影效果的两种实现方式
Aug 08 HTML / CSS
纯CSS3代码实现文字描边
Apr 25 HTML / CSS
input file上传文件样式支持html5的浏览器解决方案
Nov 14 HTML / CSS
html5中canvas学习笔记1-画板的尺寸与实际显示尺寸
Jan 06 HTML / CSS
html5 Canvas画图教程(5)—canvas里画曲线之arc方法
Jan 09 HTML / CSS
HTML5 Canvas——用路径描画线条实例介绍
Jun 09 HTML / CSS
Html5实现二维码扫描并解析
Jan 20 HTML / CSS
用html5绘制折线图的实例代码
Mar 25 HTML / CSS
HTML5离线应用与客户端存储的实现
May 03 HTML / CSS
Canvas引入跨域的图片导致toDataURL()报错的问题的解决
Sep 19 HTML / CSS
canvas中普通动效与粒子动效的实现代码示例
Jan 03 #HTML / CSS
详解webapp页面滚动卡顿的解决办法
Dec 26 #HTML / CSS
Html5调用手机摄像头并实现人脸识别的实现
Dec 21 #HTML / CSS
HTML5的postMessage的使用手册
Dec 19 #HTML / CSS
使用html2canvas.js实现页面截图并显示或上传的示例代码
Dec 18 #HTML / CSS
Canvas globalCompositeOperation
Dec 18 #HTML / CSS
深入理解HTML5定时器requestAnimationFrame的使用
Dec 12 #HTML / CSS
You might like
使用PHP实现二分查找算法代码分享
2011/06/24 PHP
php 5.6版本中编写一个PHP扩展的简单示例
2015/01/20 PHP
Netbeans 8.2将支持PHP7 更精彩
2016/06/13 PHP
Linux平台php命令行程序处理管道数据的方法
2016/11/10 PHP
phpStudy配置多站点多域名方法及遇到的403错误解决方法
2017/10/19 PHP
javascript offsetX与layerX区别
2010/03/12 Javascript
jquery.cvtooltip.js 基于jquery的气泡提示插件
2010/11/19 Javascript
JQuery触发radio或checkbox的change事件
2012/12/18 Javascript
原生js拖拽(第一课 未兼容)拖拽思路
2013/03/29 Javascript
Javascript计算两个marker之间的距离(Google Map V3)
2013/04/26 Javascript
浏览器中url存储的JavaScript实现
2015/07/07 Javascript
Node.js的Express框架使用上手指南
2016/03/12 Javascript
老生常谈JavaScript 正则表达式语法
2016/08/20 Javascript
利用Node.JS实现邮件发送功能
2016/10/21 Javascript
微信小程序wx.request实现后台数据交互功能分析
2017/11/25 Javascript
分析javascript原型及原型链
2018/03/18 Javascript
js module大战
2019/04/19 Javascript
JavaScript实现的弹出遮罩层特效经典示例【基于jQuery】
2019/07/10 jQuery
Python基于动态规划算法计算单词距离
2015/07/25 Python
Windows下安装python2和python3多版本教程
2017/03/30 Python
python入门:这篇文章带你直接学会python
2018/09/14 Python
在Python中利用pickle保存变量的实例
2019/12/30 Python
Python 输出详细的异常信息(traceback)方式
2020/04/08 Python
Django获取model中的字段名和字段的verbose_name方式
2020/05/19 Python
Django视图类型总结
2021/02/17 Python
精油和天然健康美容产品:Art Naturals
2018/01/27 全球购物
Lulu & Georgia官方网站:购买地毯、家具、抱枕、壁纸、床上用品等
2018/03/19 全球购物
六十岁生日答谢词
2014/01/10 职场文书
自荐信如何制作?
2014/02/21 职场文书
2015年读书月活动总结
2015/03/26 职场文书
婚庆公司开业主持词
2015/06/30 职场文书
采购员工作总结范文
2015/08/12 职场文书
职场中的你,辞职信写对了吗?
2019/06/26 职场文书
Go语言入门exec的基本使用
2022/05/20 Golang
微软Win11 全新照片应用面向 Dev预览版推出 新版本上手体验图集
2022/09/23 数码科技
MySQL count(*)统计总数问题汇总
2022/09/23 MySQL