用 canvas 实现 Web 手势解锁

来源:http://www.sh-fengwen.com 作者: 营养排行 人气:188 发布时间:2019-11-26
摘要:用 canvas 实现 Web 手势解锁 2017/04/04 · HTML5 ·Canvas 原文出处: songjz    最近参加 360 暑假的前端星计划,有一个在线作业,截止日期是 3 月 30号,让手动实现一个 H5 手势解锁,具体的效

用 canvas 实现 Web 手势解锁

2017/04/04 · HTML5 · Canvas

原文出处: songjz   

最近参加 360 暑假的前端星计划,有一个在线作业,截止日期是 3 月 30 号,让手动实现一个 H5 手势解锁,具体的效果就像原生手机的九宫格解锁那样。

图片 1

实现的最终效果就像下面这张图这样:

图片 2

基本要求是这样的:将密码保存到 localStorage 里,开始的时候会从本地读取密码,如果没有就让用户设置密码,密码最少为五位数,少于五位要提示错误。需要对第一次输入的密码进行验证,两次一样才能保持,然后是验证密码,能够对用户输入的密码进行验证。

支付宝登陆界面(手势解锁的实现),手势解锁

 //1.下面是实现的步骤,基本上下面的注释应该都写明白了,多谢大牛们指点,如果需要素材和源工程文件,可以索要,谢谢支持 ☺

 //2.在最下面附有效果图

#import "ViewController.h"

#import "FFFGestureView.h"

@interface ViewController ()

@property (weak, nonatomic) IBOutlet UIImageView *smallView;

@property (weak, nonatomic) IBOutlet FFFGestureView *gestureView;

@end

@implementation ViewController

- (void)viewDidLoad {

    [super viewDidLoad];

    self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"Home_refresh_bg"]];

    self.gestureView.myblock = ^(UIImage *image,NSString *pass){

        NSString *turePass = @"012";

        if([pass isEqualToString:turePass]){

            self.smallView.image = nil;

            return YES;

        }else{

            self.smallView.image = image;

            return NO;

        }

    };

}

***************************************************************************

#import <UIKit/UIKit.h>

@interface FFFGestureView : UIView

@property (nonatomic,copy) BOOL(^myblock)(UIImage *,NSString *);

@end

***************************************************************************

#import "FFFGestureView.h"

#import "SVProgressHUD.h"

#define SUMCOUNT 9

@interface FFFGestureView ()

//定义可变数组加载需要的button

@property (nonatomic,strong) NSArray *buttons;

//设置数组接收画的线

@property (nonatomic,strong) NSMutableArray *lineButton;

//定义一个点,保存手指当前的位置

@property(nonatomic,assign) CGPoint currentPoint;

@end

 

@implementation FFFGestureView

 

-(NSMutableArray *)lineButton{

    if(_lineButton==nil){

        _lineButton = [NSMutableArray array];

    }

    return _lineButton;

}

 

//懒加载button

-(NSArray *)buttons{

    if(_buttons==nil){

        NSMutableArray *arrayM = [NSMutableArray array];

        for(int i=0;i<SUMCOUNT;i++){

            

            UIButton *button = [[UIButton alloc] init];

            button.tag = i;

//            button.backgroundColor = [UIColor redColor];

            [button setUserInteractionEnabled:NO];

            

            [button setBackgroundImage:[UIImage imageNamed:@"gesture_node_normal"] forState:UIControlStateNormal];

            [button setBackgroundImage:[UIImage imageNamed:@"gesture_node_highlighted"] forState:UIControlStateHighlighted];

            [button setBackgroundImage:[UIImage imageNamed:@"gesture_node_error"] forState:UIControlStateSelected];

            

            [self addSubview:button];

            [arrayM addObject:button];

        }

        _buttons = [arrayM copy];

    }

    return _buttons;

}

 

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{

 

//    获取touch对象

    UITouch *touch = [touches anyObject];

//    获取点击的点

    CGPoint point = [touch locationInView:touch.view];

    

//    遍历所有的按钮

    for(int i=0;i<self.buttons.count;i++){

    

        UIButton *button = self.buttons[i];

//        按钮的frame是否包含了点击的点

        if(CGRectContainsPoint(button.frame, point)){

//        开始高亮状态

            button.highlighted = YES;

            

//            判断这个按钮是不是已经添加到了数组当中,如果没有在添加

            if(![self.lineButton containsObject:button]){

            

                [self.lineButton addObject:button];

            }

        }

    }

}

 

-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{

    //    获取touch对象

    UITouch *touch = [touches anyObject];

    //    获取点击的点

    CGPoint point = [touch locationInView:touch.view];

    

    //    获取移动的时候手指位置

    self.currentPoint = point;

 

    //    遍历所有的按钮

    for(int i=0;i<self.buttons.count;i++){

        

        UIButton *button = self.buttons[i];

        //        按钮的frame是否包含了点击的点

        if(CGRectContainsPoint(button.frame, point)){

            //        开始高亮状态

            button.highlighted = YES;

//            判断这个按钮是不是已经添加到了数组当中,如果没有在添加

            if(![self.lineButton containsObject:button]){

                

                [self.lineButton addObject:button];

            }

        }

    }

    [self setNeedsDisplay];

}

 

-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{

 

//    解决错误的时候,最后手指的位置不连接

    self.currentPoint = [[self.lineButton lastObject] center];

    [self setNeedsDisplay];

    

    for (int i=0; i<self.lineButton.count; i++) {

        UIButton *button = self.lineButton[i];

        button.selected = YES;

        button.highlighted = NO;

    }

//    在恢复之前不能进行连线

    [self setUserInteractionEnabled:NO];

    

    NSString *passWord = @"";

    for (int i=0; i<self.lineButton.count; i++) {

        //        拼接按钮的tag

        passWord = [passWord stringByAppendingString:[NSString stringWithFormat:@"%ld",[self.lineButton[i] tag]]];

    }

    

//    输出当前VIew作为image

    UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0);

//    获取上下文

    CGContextRef ctx = UIGraphicsGetCurrentContext();

//    渲染

    [self.layer renderInContext:ctx];

//    通过上下文获取图片

    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();

//    关闭上下文

    UIGraphicsEndImageContext();

    

    if(self.myblock){

        if(self.myblock(image,passWord)){

            [SVProgressHUD showSuccessWithStatus:@"密码正确"];

        }else{

            [SVProgressHUD showErrorWithStatus:@"密码错误"];

        }

    }

//    显示错误的样式 1秒钟

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

//        恢复之后再把用户交互打开

        [self setUserInteractionEnabled:YES];

        [self clearScreen];

    });

}

 

-(void)clearScreen{

    [self.lineButton removeAllObjects];

    for (int i=0; i<self.buttons.count ; i++) {

        UIButton *button = self.buttons[i];

        button.highlighted = NO;

        button.selected = NO;

    }

//    恢复原始状态

    [self setNeedsDisplay];

}

-(void)drawRect:(CGRect)rect{

 

//    创建路径对象

    UIBezierPath *path = [UIBezierPath bezierPath];

    

    for(int i=0;i<self.lineButton.count;i++){

        if(i==0){

            [path moveToPoint:[self.lineButton[i] center]];

        }else{

            [path addLineToPoint:[self.lineButton[i] center]];

        }

    }

    if(self.lineButton.count){

//     连接到手指的位置

        [path addLineToPoint:self.currentPoint];

    }

//    设置颜色

    [[UIColor redColor] set];

    

//    设置线宽

    path.lineWidth = 10;

    

//    设置连接处的样式

    [path setLineJoinStyle:kCGLineJoinRound];

    

//    设置头尾的样式

    [path setLineCapStyle:kCGLineCapRound];

    

//    渲染

    [path stroke];

}

-(void)layoutSubviews{

    

    [super layoutSubviews];

    

    CGFloat w = 74;

    CGFloat h = w;

    CGFloat margin = (self.frame.size.width-3*w)/4;

    

    for(int i=0;i<self.buttons.count;i++){

    

        UIButton *button = self.buttons[i];

        CGFloat row = i % 3;

        CGFloat col = i / 3;

        CGFloat x = row * (margin + w) + margin;

        CGFloat y = col * (margin + h) + margin;

        button.frame = CGRectMake(x, y, w, h);

    }

}

@end

 

图片 3

 

图片 4

//1.下面是实现的步骤,基本上下面的注释应该都写明白了,多谢大牛们指点,如果需要素材...

H5 手势解锁

扫码在线查看:

图片 5

或者点击查看手机版。

项目 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 的 touchstarttouchmove、和 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 手势解锁

关键词:

上一篇:JavaScript 深入之new的模拟实现

下一篇:没有了

最火资讯