用 canvas 实现 Web 手势解锁
用 canvas 实现 Web 手势解锁
原文出处: songjz
最近参加 360 暑假的前端星计划,有一个在线作业,截止日期是 3 月 30 号,让手动实现一个 H5 手势解锁,具体的效果就像原生手机的九宫格解锁那样。
实现的最终效果就像下面这张图这样:
基本要求是这样的:将密码保存到 localStorage
里,开始的时候会从本地读取密码,如果没有就让用户设置密码,密码最少为五位数,少于五位要提示错误。需要对第一次输入的密码进行验证,两次一样才能保持,然后是验证密码,能够对用户输入的密码进行验证。
HTML5实现屏幕手势解锁
2015/07/18 · HTML5 · 1 评论 · 手势解锁
原文出处: AlloyTeam
效果展示
实现原理 利用HTML5的canvas,将解锁的圈圈划出,利用touch事件解锁这些圈圈,直接看代码。
JavaScript
function createCircle() {// 创建解锁点的坐标,根据canvas的大小来平均分配半径 var n = chooseType;// 画出n*n的矩阵 lastPoint = []; arr = []; restPoint = []; r = ctx.canvas.width / (2 4 * n);// 公式计算 半径和canvas的大小有关 for (var i = 0 ; i < n ; i ) { for (var j = 0 ; j < n ; j ) { arr.push({ x: j * 4 * r 3 * r, y: i * 4 * r 3 * r }); restPoint.push({ x: j * 4 * r 3 * r, y: i * 4 * r 3 * r }); } } //return arr; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
function createCircle() {// 创建解锁点的坐标,根据canvas的大小来平均分配半径
var n = chooseType;// 画出n*n的矩阵
lastPoint = [];
arr = [];
restPoint = [];
r = ctx.canvas.width / (2 4 * n);// 公式计算 半径和canvas的大小有关
for (var i = 0 ; i < n ; i ) {
for (var j = 0 ; j < n ; j ) {
arr.push({
x: j * 4 * r 3 * r,
y: i * 4 * r 3 * r
});
restPoint.push({
x: j * 4 * r 3 * r,
y: i * 4 * r 3 * r
});
}
}
//return arr;
}
|
canvas里的圆圈画好之后可以进行事件绑定
JavaScript
function bindEvent() { can.addEventListener("touchstart", function (e) { var po = getPosition(e); console.log(po); for (var i = 0 ; i < arr.length ; i ) { if (Math.abs(po.x - arr[i].x) < r && Math.abs(po.y - arr[i].y) < r) { // 用来判断起始点是否在圈圈内部 touchFlag = true; drawPoint(arr[i].x,arr[i].y); lastPoint.push(arr[i]); restPoint.splice(i,1); break; } } }, false); can.addEventListener("touchmove", function (e) { if (touchFlag) { update(getPosition(e)); } }, false); can.addEventListener("touchend", function (e) { if (touchFlag) { touchFlag = false; storePass(lastPoint); setTimeout(function(){ init(); }, 300); } }, false); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
function bindEvent() {
can.addEventListener("touchstart", function (e) {
var po = getPosition(e);
console.log(po);
for (var i = 0 ; i < arr.length ; i ) {
if (Math.abs(po.x - arr[i].x) < r && Math.abs(po.y - arr[i].y) < r) { // 用来判断起始点是否在圈圈内部
touchFlag = true;
drawPoint(arr[i].x,arr[i].y);
lastPoint.push(arr[i]);
restPoint.splice(i,1);
break;
}
}
}, false);
can.addEventListener("touchmove", function (e) {
if (touchFlag) {
update(getPosition(e));
}
}, false);
can.addEventListener("touchend", function (e) {
if (touchFlag) {
touchFlag = false;
storePass(lastPoint);
setTimeout(function(){
init();
}, 300);
}
}, false);
}
|
接着到了最关键的步骤绘制解锁路径逻辑,通过touchmove事件的不断触发,调用canvas的moveTo方法和lineTo方法来画出折现,同时判断是否达到我们所画的圈圈里面,其中lastPoint保存正确的圈圈路径,restPoint保存全部圈圈去除正确路径之后剩余的。 Update方法:
JavaScript
function update(po) {// 核心变换方法在touchmove时候调用 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); for (var i = 0 ; i < arr.length ; i ) { // 每帧先把面板画出来 drawCle(arr[i].x, arr[i].y); } drawPoint(lastPoint);// 每帧花轨迹 drawLine(po , lastPoint);// 每帧画圆心 for (var i = 0 ; i < restPoint.length ; i ) { if (Math.abs(po.x - restPoint[i].x) < r && Math.abs(po.y - restPoint[i].y) < r) { drawPoint(restPoint[i].x, restPoint[i].y); lastPoint.push(restPoint[i]); restPoint.splice(i, 1); break; } } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
function update(po) {// 核心变换方法在touchmove时候调用
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
for (var i = 0 ; i < arr.length ; i ) { // 每帧先把面板画出来
drawCle(arr[i].x, arr[i].y);
}
drawPoint(lastPoint);// 每帧花轨迹
drawLine(po , lastPoint);// 每帧画圆心
for (var i = 0 ; i < restPoint.length ; i ) {
if (Math.abs(po.x - restPoint[i].x) < r && Math.abs(po.y - restPoint[i].y) < r) {
drawPoint(restPoint[i].x, restPoint[i].y);
lastPoint.push(restPoint[i]);
restPoint.splice(i, 1);
break;
}
}
}
|
最后就是收尾工作,把路径里面的lastPoint保存的数组变成密码存在localstorage里面,之后就用来处理解锁验证逻辑了
JavaScript
function storePass(psw) {// touchend结束之后对密码和状态的处理 if (pswObj.step == 1) { if (checkPass(pswObj.fpassword, psw)) { pswObj.step = 2; pswObj.spassword = psw; document.getElementById('title').innerHTML = '密码保存成功'; drawStatusPoint('#2CFF26'); window.localStorage.setItem('passwordx', JSON.stringify(pswObj.spassword)); window.localStorage.setItem('chooseType', chooseType); } else { document.getElementById('title').innerHTML = '两次不一致,重新输入'; drawStatusPoint('red'); delete pswObj.step; } } else if (pswObj.step == 2) { if (checkPass(pswObj.spassword, psw)) { document.getElementById('title').innerHTML = '解锁成功'; drawStatusPoint('#2CFF26'); } else { drawStatusPoint('red'); document.getElementById('title').innerHTML = '解锁失败'; } } else { pswObj.step = 1; pswObj.fpassword = psw; document.getElementById('title').innerHTML = '再次输入'; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
function storePass(psw) {// touchend结束之后对密码和状态的处理
if (pswObj.step == 1) {
if (checkPass(pswObj.fpassword, psw)) {
pswObj.step = 2;
pswObj.spassword = psw;
document.getElementById('title').innerHTML = '密码保存成功';
drawStatusPoint('#2CFF26');
window.localStorage.setItem('passwordx', JSON.stringify(pswObj.spassword));
window.localStorage.setItem('chooseType', chooseType);
} else {
document.getElementById('title').innerHTML = '两次不一致,重新输入';
drawStatusPoint('red');
delete pswObj.step;
}
} else if (pswObj.step == 2) {
if (checkPass(pswObj.spassword, psw)) {
document.getElementById('title').innerHTML = '解锁成功';
drawStatusPoint('#2CFF26');
} else {
drawStatusPoint('red');
document.getElementById('title').innerHTML = '解锁失败';
}
} else {
pswObj.step = 1;
pswObj.fpassword = psw;
document.getElementById('title').innerHTML = '再次输入';
}
}
|
解锁组件
将这个HTML5解锁写成了一个组件,放在https://github.com/lvming6816077/H5lock
二维码体验:
参考资料:http://www.nihaoshijie.com.cn/index.php/archives/537
1 赞 4 收藏 1 评论
H5 手势解锁
扫码在线查看:
或者点击查看手机版。
项目 GitHub 地址,H5HandLock。
首先,我要说明一下,对于这个项目,我是参考别人的,H5lock。
我觉得一个比较合理的解法应该是利用 canvas 来实现,不知道有没有大神用 css
来实现。如果纯用 css 的话,可以将连线先设置
display: none
,当手指划过的时候,显示出来。光设置这些应该就非常麻烦吧。
之前了解过 canvas,但没有真正的写过,下面就来介绍我这几天学习 canvas 并实现 H5 手势解锁的过程。
准备及布局设置
我这里用了一个比较常规的做法:
(function(w){ var handLock = function(option){} handLock.prototype = { init : function(){}, ... } w.handLock = handLock; })(window) // 使用 new handLock({ el: document.getElementById('id'), ... }).init();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
(function(w){
var handLock = function(option){}
handLock.prototype = {
init : function(){},
...
}
w.handLock = handLock;
})(window)
// 使用
new handLock({
el: document.getElementById('id'),
...
}).init();
|
常规方法,比较易懂和操作,弊端就是,可以被随意的修改。
传入的参数中要包含一个 dom 对象,会在这个 dom 对象內创建一个 canvas。当然还有一些其他的 dom 参数,比如 message,info 等。
关于 css 的话,懒得去新建文件了,就直接內联了。
canvas
1. 学习 canvas 并搞定画圆
MDN 上面有个简易的教程,大致浏览了一下,感觉还行。Canvas教程。
先创建一个 canvas
,然后设置其大小,并通过 getContext
方法获得绘画的上下文:
var canvas = document.createElement('canvas'); canvas.width = canvas.height = width; this.el.appendChild(canvas); this.ctx = canvas.getContext('2d');
1
2
3
4
5
|
var canvas = document.createElement('canvas');
canvas.width = canvas.height = width;
this.el.appendChild(canvas);
this.ctx = canvas.getContext('2d');
|
然后呢,先画 n*n
个圆出来:
JavaScript
createCircles: function(){ var ctx = this.ctx, drawCircle = this.drawCircle, n = this.n; this.r = ctx.canvas.width / (2 4 * n) // 这里是参考的,感觉这种画圆的方式挺合理的,方方圆圆 r = this.r; this.circles = []; // 用来存储圆心的位置 for(var i = 0; i < n; i ){ for(var j = 0; j < n; j ){ var p = { x: j * 4 * r 3 * r, y: i * 4 * r 3 * r, id: i * 3 j } this.circles.push(p); } } ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); // 为了防止重复画 this.circles.forEach(function(v){ drawCircle(ctx, v.x, v.y); // 画每个圆 }) }, drawCircle: function(ctx, x, y){ // 画圆函数 ctx.strokeStyle = '#FFFFFF'; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(x, y, this.r, 0, Math.PI * 2, true); ctx.closePath(); ctx.stroke(); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
createCircles: function(){
var ctx = this.ctx,
drawCircle = this.drawCircle,
n = this.n;
this.r = ctx.canvas.width / (2 4 * n) // 这里是参考的,感觉这种画圆的方式挺合理的,方方圆圆
r = this.r;
this.circles = []; // 用来存储圆心的位置
for(var i = 0; i < n; i ){
for(var j = 0; j < n; j ){
var p = {
x: j * 4 * r 3 * r,
y: i * 4 * r 3 * r,
id: i * 3 j
}
this.circles.push(p);
}
}
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); // 为了防止重复画
this.circles.forEach(function(v){
drawCircle(ctx, v.x, v.y); // 画每个圆
})
},
drawCircle: function(ctx, x, y){ // 画圆函数
ctx.strokeStyle = '#FFFFFF';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(x, y, this.r, 0, Math.PI * 2, true);
ctx.closePath();
ctx.stroke();
}
|
画圆函数,需要注意:如何确定圆的半径和每个圆的圆心坐标(这个我是参考的),如果以圆心为中点,每个圆上下左右各扩展一个半径的距离,同时为了防止四边太挤,四周在填充一个半径的距离。那么得到的半径就是
width / ( 4 * n 2)
,对应也可以算出每个圆所在的圆心坐标,也有一套公式,GET。
2. 画线
画线需要借助 touch event 来完成,也就是,当我们 touchstart
的时候,传入开始时的相对坐标,作为线的一端,当我们 touchmove
的时候,获得坐标,作为线的另一端,当我们 touchend
的时候,开始画线。
这只是一个测试画线功能,具体的后面再进行修改。
有两个函数,获得当前 touch 的相对坐标:
getTouchPos: function(e){ // 获得触摸点的相对位置 var rect = e.target.getBoundingClientRect(); var p = { // 相对坐标 x: e.touches[0].clientX - rect.left, y: e.touches[0].clientY - rect.top }; return p; }
1
2
3
4
5
6
7
8
|
getTouchPos: function(e){ // 获得触摸点的相对位置
var rect = e.target.getBoundingClientRect();
var p = { // 相对坐标
x: e.touches[0].clientX - rect.left,
y: e.touches[0].clientY - rect.top
};
return p;
}
|
画线:
drawLine: function(p1, p2){ // 画线 this.ctx.beginPath(); this.ctx.lineWidth = 3; this.ctx.moveTo(p1.x, p2.y); this.ctx.lineTo(p.x, p.y); this.ctx.stroke(); this.ctx.closePath(); },
1
2
3
4
5
6
7
8
|
drawLine: function(p1, p2){ // 画线
this.ctx.beginPath();
this.ctx.lineWidth = 3;
this.ctx.moveTo(p1.x, p2.y);
this.ctx.lineTo(p.x, p.y);
this.ctx.stroke();
this.ctx.closePath();
},
|
然后就是监听 canvas 的 touchstart
、touchmove
、和 touchend
事件了。
3. 画折线
所谓的画折线,就是,将已经触摸到的点连起来,可以把它看作是画折线。
首先,要用两个数组,一个数组用于已经 touch 过的点,另一个数组用于存储未 touch 的点,然后在 move 监听时候,对 touch 的相对位置进行判断,如果触到点,就把该点从未 touch 移到 touch 中,然后,画折线,思路也很简单。
JavaScript
drawLine: function(p){ // 画折线 this.ctx.beginPath(); this.ctx.lineWidth = 3; this.ctx.moveTo(this.touchCircles[0].x, this.touchCircles[0].y); for (var i = 1 ; i < this.touchCircles.length ; i ) { this.ctx.lineTo(this.touchCircles[i].x, this.touchCircles[i].y); } this.ctx.lineTo(p.x, p.y); this.ctx.stroke(); this.ctx.closePath(); },
1
2
3
4
5
6
7
8
9
10
11
|
drawLine: function(p){ // 画折线
this.ctx.beginPath();
this.ctx.lineWidth = 3;
this.ctx.moveTo(this.touchCircles[0].x, this.touchCircles[0].y);
for (var i = 1 ; i < this.touchCircles.length ; i ) {
this.ctx.lineTo(this.touchCircles[i].x, this.touchCircles[i].y);
}
this.ctx.lineTo(p.x, p.y);
this.ctx.stroke();
this.ctx.closePath();
},
|
JavaScript
judgePos: function(p){ // 判断 触点 是否在 circle 內 for(var i = 0; i < this.restCircles.length; i ){ temp = this.restCircles[i]; if(Math.abs(p.x - temp.x) < r && Math.abs(p.y - temp.y) < r){ this.touchCircles.push(temp); this.restCircles.splice(i, 1); this.touchFlag = true; break; } } }
1
2
3
4
5
6
7
8
9
10
11
|
judgePos: function(p){ // 判断 触点 是否在 circle 內
for(var i = 0; i < this.restCircles.length; i ){
temp = this.restCircles[i];
if(Math.abs(p.x - temp.x) < r && Math.abs(p.y - temp.y) < r){
this.touchCircles.push(temp);
this.restCircles.splice(i, 1);
this.touchFlag = true;
break;
}
}
}
|
4. 标记已画
前面已经说了,我们把已经 touch 的点(圆)放到数组中,这个时候需要将这些已经 touch 的点给标记一下,在圆心处画一个小实心圆:
JavaScript
drawPoints: function(){ for (var i = 0 ; i < this.touchCircles.length ; i ) { this.ctx.fillStyle = '#FFFFFF'; this.ctx.beginPath(); this.ctx.arc(this.touchCircles[i].x, this.touchCircles[i].y, this.r / 2, 0, Math.PI * 2, true); this.ctx.closePath(); this.ctx.fill(); } }
1
2
3
4
5
6
7
8
9
|
drawPoints: function(){
for (var i = 0 ; i < this.touchCircles.length ; i ) {
this.ctx.fillStyle = '#FFFFFF';
this.ctx.beginPath();
this.ctx.arc(this.touchCircles[i].x, this.touchCircles[i].y, this.r / 2, 0, Math.PI * 2, true);
this.ctx.closePath();
this.ctx.fill();
}
}
|
同时添加一个 reset 函数,当 touchend 的时候调用,400ms 调用 reset 重置 canvas。
到现在为止,一个 H5 手势解锁的简易版已经基本完成。
password
为了要实现记住和重置密码的功能,把 password 保存在 localStorage 中,但首先要添加必要的 html 和样式。
1. 添加 message 和 单选框
为了尽可能的使界面简洁(越丑越好),直接在 body 后面添加了:
XHTML
<div id="select"> <div class="message">请输入手势密码</div> <div class="radio"> <label><input type="radio" name="pass">设置手势密码</label> <label><input type="radio" name="pass">验证手势密码</label> </div> </div>
1
2
3
4
5
6
7
|
<div id="select">
<div class="message">请输入手势密码</div>
<div class="radio">
<label><input type="radio" name="pass">设置手势密码</label>
<label><input type="radio" name="pass">验证手势密码</label>
</div>
</div>
|
将添加到 dom 已 option 的形式传给 handLock:
var el = document.getElementById('handlock'), info = el.getElementsByClassName('info')[0], select = document.getElementById('select'), message = select.getElementsByClassName('message')[0], radio = select.getElementsByClassName('radio')[0], setPass = radio.children[0].children[0], checkPass = radio.children[1].children[0]; new handLock({ el: el, info: info, message: message, setPass: setPass, checkPass: checkPass, n: 3 }).init();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
var el = document.getElementById('handlock'),
info = el.getElementsByClassName('info')[0],
select = document.getElementById('select'),
message = select.getElementsByClassName('message')[0],
radio = select.getElementsByClassName('radio')[0],
setPass = radio.children[0].children[0],
checkPass = radio.children[1].children[0];
new handLock({
el: el,
info: info,
message: message,
setPass: setPass,
checkPass: checkPass,
n: 3
}).init();
|
2. info 信息显示
关于 info 信息显示,自己写了一个悬浮窗,然后默认为
display: none
,然后写了一个 showInfo
函数用来显示提示信息,直接调用:
showInfo: function(message, timer){ // 专门用来显示 info var info = this.dom.info; info.innerHTML = message; info.style.display = 'block'; setTimeout(function(){ info.style.display = ''; }, 1000) }
1
2
3
4
5
6
7
8
|
showInfo: function(message, timer){ // 专门用来显示 info
var info = this.dom.info;
info.innerHTML = message;
info.style.display = 'block';
setTimeout(function(){
info.style.display = '';
}, 1000)
}
|
关于 info 的样式,在 html 中呢。
本文由美洲杯冠军竞猜发布于计算机教程,转载请注明出处:用 canvas 实现 Web 手势解锁
关键词: