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('');
}
}