import { Component, OnInit, ViewChild, Renderer, EventEmitter, Input, Output } from '@angular/core'; // 定义坐标点 function Point(x, y, value?) { this.x = x; this.y = y; // 对应的圆圈坐标:1~9 this.value = value || -1; } // 判断给定点是否在数组中 function isStored(point, pointList) { for (var i in pointList) { if (pointList[i] == point) return true; } return false; } @Component({ selector: 'gesture-unlock', template: `
` }) export class GestureUnlockComponent implements OnInit{ option pointList overviewList = [] ongoing ImageData touching tempPwd $canvas ctx @Input() status // 手势锁的状态 0: 第一次设置密码 1: 第二次输入密码 2: 等待验证密码 @Output() success = new EventEmitter() // 成功设置 @Output() check = new EventEmitter() // 验证事件 DEFAULT = { width: 300, // 绘图区宽度 height: 300, // 绘图区高度 radius: 30, // 圆圈半径 space: 40, // 圆圈间距 lineWidth: 1, // 线条宽度 ratio: 2, // 放大比率,提升清晰度 normalFillColor: '#ffffff', // 圆圈填充色 normalStrokeColor: '#D0AF68', // 圆圈轮廓填充色 activeRadius:15, touchFillColor:'#ffffff', activeFillColor: '#D0AF68', // 选中状态下圆圈填充色 activeStrokeColor: '#D0AF68', // 选中状态下圆圈轮廓填充色 lineColor: '#D0AF68', // 连线颜色 bgColor: 'transparent' // 背景色 } MESSAGE = { tooShort: '密码太短,至少需要4个点', notSame: '两次输入的不一致', setOk: '密码设置成功', pwdError: '验证失败!', pwdSuccess: '验证通过!', tips: ['绘制解锁图案', '再次绘制解锁答案'], noPwd: ['请先设置密码'] } @ViewChild('gesture') gesture @ViewChild('tips') tips constructor(public renderer:Renderer) { // 配置参数 this.option = Object.assign({},this.DEFAULT,this.option); this.option.width *= this.option.ratio; this.option.height *= this.option.ratio; this.option.radius *= this.option.ratio; this.option.space *= this.option.ratio; this.option.lineWidth *= this.option.ratio; // 保存所有的圆圈坐标 this.pointList = []; // 保存用户的手势 this.ongoing = []; // 保存绘制好九宫格后的画布数据 this.ImageData = {}; // 是否滑动 this.touching = false; //this.status = this.status || 0; // 用于保存第一次输入的密码 this.tempPwd = '' } ngOnInit (){ this.$canvas = this.gesture.nativeElement; this.renderer.setElementStyle(this.$canvas, 'display', 'block'); this.renderer.setElementStyle(this.$canvas, 'margin', '0 auto'); this.renderer.setElementStyle(this.$canvas, 'background-color', this.option.bgColor); setImmediate(()=>{ // 获取渲染上下文 this.ctx = this.$canvas.getContext('2d'); this.drawPlate(); if(this.status == 0) this.setTips(this.MESSAGE.tips[0],'gray'); }); } // 绘制九个圆圈 drawPlate = () => { const radius = this.option.radius; const space = this.option.space; const padding = Math.floor((this.option.width - 6*radius - 2*space)/2); // 左右留白 const margin = Math.floor((this.option.height - 6*radius - 2*space)/2); // 上下留白 for (var i = 0; i < 3; i++) { // 绘制3*3的圈圈节点 for (var j = 0; j < 3; j++) { var x = padding + radius + j * (2 * radius + space); var y = margin + radius + i * (2 * radius + space); var point = new Point(x, y, i*3+j+1); this.drawCirle(point); this.pointList.push(point); } } this.ImageData = this.ctx.getImageData(0, 0, this.option.width, this.option.height); } // 绘制一个圆 drawCirle (point, fillColor?, strokeColor?, radius?) { this.ctx.fillStyle = fillColor || this.option.normalFillColor; this.ctx.strokeStyle = strokeColor || this.option.normalStrokeColor; this.ctx.lineWidth = this.option.lineWidth; this.ctx.beginPath(); this.ctx.arc(point.x, point.y, radius || this.option.radius, 0, 2*Math.PI); this.ctx.stroke(); this.ctx.fill(); }; // 绘制连线 drawLine = (point?) => { this.reset(); // 重新绘制按钮区 for (let i in this.ongoing) { this.drawCirle(this.ongoing[i], this.option.activeFillColor, this.option.activeStrokeColor,this.option.activeRadius); } this.ctx.strokeStyle = this.option.lineColor; this.ctx.lineWidth = this.option.lineWidth; this.ctx.beginPath(); // 重新绘制连接线,Move到第一个节点,再通过lineTo连接其他节点 for (let j in this.ongoing) { if (parseInt(j) == 0) { this.ctx.moveTo(this.ongoing[j].x, this.ongoing[j].y); } else { this.ctx.lineTo(this.ongoing[j].x, this.ongoing[j].y); } } // 连接到最近触摸点 if (point) this.ctx.lineTo(point.x, point.y); this.ctx.stroke(); } // 重置为起始状态 reset = () => { // 清空绘图 this.ctx.clearRect(0, 0, this.option.width, this.option.height); this.ctx.putImageData(this.ImageData, 0, 0); } // 判断坐标点是否在按钮区 findPoint = (x,y) => { const squareRadius = Math.pow(this.option.radius, 2); for (let i in this.pointList) { let distance = Math.pow(this.pointList[i].x - x, 2) + Math.pow(this.pointList[i].y - y, 2); if (distance < squareRadius) { return this.pointList[i]; } } return null; } // 触摸屏幕,进入解锁状态 handleStart = (evt) => { this.ongoing = []; // 清空之前存储的轨迹 var touchPoint = this.getTouchPoint(evt); var point = this.findPoint(touchPoint.x, touchPoint.y); if (point) { // 点亮触摸的按钮区,并加入轨迹数组 this.drawCirle(point, this.option.touchFillColor); this.ongoing.push(point); this.touching = true; // 开始滑动标识 } } // 触摸移动,绘制连接线 handleMove = (evt) => { if (this.touching) { var touchPoint = this.getTouchPoint(evt); var point = this.findPoint(touchPoint.x, touchPoint.y); // 点亮触摸的按钮区,并加入轨迹数组 if (point && !isStored(point, this.ongoing)) { this.ongoing.push(point); } this.drawLine(touchPoint); } } // 手指离开,轨迹清空 handleEnd = () => { if (!this.touching) return; this.touching = false; this.drawLine(); const curPwd = this.getCurPwd(); // 半秒后清除绘图 setTimeout(() => { this.reset(); }, 300); if(this.status == 0){ // 设置密码 if (curPwd.length < 4) { this.setTips(this.MESSAGE.tooShort); } else { this.tempPwd = curPwd; this.setTips(this.MESSAGE.tips[1],'gray'); this.status = 1; this.setOverview(); // 设置密码预览 } }else if(this.status == 1){ // 第二次输入密码 if (this.tempPwd == curPwd) { this.setTips(this.MESSAGE.setOk, 'green'); this.success.emit(curPwd); // 设置成功并发送密码 } else { this.setTips(this.MESSAGE.notSame); this.setOverview(true); } this.status = 0; }else if(this.status==2){ // 验证密码 this.setOverview(); this.check.emit(curPwd); } } // 设置密码预览 setOverview = (clear?) => { this.overviewList = [false,false,false,false,false,false,false,false,false,false]; if(clear) return; for (var i in this.ongoing) { let num = this.ongoing[i].value; this.overviewList[num] = true; } } setTips = (message, type='red') => { this.tips.nativeElement.innerHTML = '' + message + ''; } // 获取mouse或者touch的触摸点 getTouchPoint = (evt) => { evt.preventDefault(); var x = evt.changedTouches[0].pageX - this.$canvas.offsetLeft; var y = evt.changedTouches[0].pageY - this.$canvas.offsetTop; x *= this.option.ratio; y *= this.option.ratio; return new Point(x, y); } // 获取当前轨迹下的坐标,即输入密码 getCurPwd = () => { var pwd = []; for (var i in this.ongoing) { let num = this.ongoing[i].value; pwd.push(num); } return pwd.join(''); } }