|
type ChooseType = 2 | 3 | 4 | 5; |
|
type Position = { |
|
x: number |
|
y: number |
|
} |
|
type Point = { |
|
x: number |
|
y: number |
|
index: number; |
|
} |
|
type H5lockOptions = { |
|
height: number |
|
width: number |
|
chooseType?: ChooseType |
|
container: Element | string |
|
inputEnd?: (res: string) => void |
|
} |
|
export class H5lock { |
|
height: number |
|
width: number |
|
chooseType: ChooseType = 3 |
|
container: Element | string |
|
inputEnd: (res: string) => void |
|
devicePixelRatio: number = window.devicePixelRatio || 1 |
|
constructor(obj: H5lockOptions) { |
|
this.height = obj.height; |
|
this.width = obj.width; |
|
if (obj.chooseType && isFinite(obj.chooseType)) { |
|
const chooseType = obj.chooseType | 0; |
|
if (chooseType >= 2 && chooseType <= 5) { |
|
this.chooseType = chooseType as ChooseType; |
|
} |
|
} |
|
this.container = obj.container; |
|
this.inputEnd = obj.inputEnd; |
|
|
|
this._updateFrame = this._updateFrame.bind(this); |
|
} |
|
private ctx: CanvasRenderingContext2D |
|
private r: number |
|
normal_circle_style = { |
|
gradient: true, |
|
colors: [ |
|
[0, "#42d6cb"], |
|
[0.5, "#46ced8"], |
|
[1, "#4dc0db"], |
|
], |
|
lineWidth: 2, |
|
color: '#CFE6FF' |
|
} |
|
error_circle_style = { |
|
gradient: false, |
|
colors: [], |
|
lineWidth: 2, |
|
color: 'darkred' |
|
} |
|
get circle_style() { |
|
return this.show_error ? this.error_circle_style : this.normal_circle_style; |
|
} |
|
private drawCle(x, y) { // 初始化解锁密码面板 |
|
const ctx = this.ctx; |
|
const circle_style = this.circle_style; |
|
if (circle_style.gradient) { |
|
const gradient = ctx.createLinearGradient(x, y, x, y + this.r * 2); |
|
circle_style.colors.forEach(color_args => { |
|
gradient.addColorStop(color_args[0] as number, color_args[1] as string); |
|
}); |
|
ctx.strokeStyle = gradient; |
|
} else { |
|
ctx.strokeStyle = circle_style.color; |
|
} |
|
ctx.lineWidth = 2 * this.devicePixelRatio; |
|
ctx.beginPath(); |
|
ctx.arc(x, y, this.r, 0, Math.PI * 2, true); |
|
ctx.closePath(); |
|
ctx.stroke(); |
|
} |
|
/** |
|
* 已使用的起来的轨迹点 |
|
* |
|
* @private |
|
* @type {Point[]} |
|
* @memberof H5lock |
|
*/ |
|
private lastPoints: Point[] |
|
normal_point_style = { |
|
gradient: true, |
|
colors: [ |
|
[0, "#42d6cb"], |
|
[0.5, "#46ced8"], |
|
[1, "#4dc0db"], |
|
], |
|
color: '#CFE6FF' |
|
} |
|
error_point_style = { |
|
gradient: false, |
|
colors: [], |
|
color: 'darkred' |
|
} |
|
get point_style() { |
|
return this.show_error ? this.error_point_style : this.normal_point_style; |
|
} |
|
/** |
|
* 初始化圆心 |
|
* |
|
* @memberof H5lock |
|
*/ |
|
drawPoints() { // 初始化圆心 |
|
const ctx = this.ctx; |
|
const point_style = this.point_style; |
|
const lastPoints = this.lastPoints; |
|
|
|
if (point_style.gradient) { |
|
const gradient = ctx.createLinearGradient(0, 0, this.canvas.width, this.canvas.height); |
|
point_style.colors.forEach(color_args => { |
|
gradient.addColorStop(color_args[0] as number, color_args[1] as string); |
|
}); |
|
ctx.fillStyle = gradient; |
|
} else { |
|
ctx.fillStyle = point_style.color; |
|
} |
|
for (let point of lastPoints) { |
|
const r = this.r / 2; |
|
// if (point_style.gradient) { |
|
// const gradient = ctx.createLinearGradient(point.x, point.y, point.x, point.y + r * 2); |
|
// point_style.colors.forEach(color_args => { |
|
// gradient.addColorStop(color_args[0] as number, color_args[1] as string); |
|
// }); |
|
// ctx.fillStyle = gradient; |
|
// } else { |
|
// ctx.fillStyle = point_style.color; |
|
// } |
|
ctx.beginPath(); |
|
ctx.arc(point.x, point.y, r, 0, Math.PI * 2, true); |
|
ctx.closePath(); |
|
ctx.fill(); |
|
} |
|
} |
|
normal_line_style = { |
|
gradient: true, |
|
colors: [ |
|
[0, "#42d6cb"], |
|
[0.5, "#46ced8"], |
|
[1, "#4dc0db"], |
|
], |
|
lineWidth: 3, |
|
color: '#CFE6FF' |
|
} |
|
error_line_style = { |
|
gradient: false, |
|
colors: [], |
|
lineWidth: 3, |
|
color: 'darkred' |
|
} |
|
get line_style() { |
|
return this.show_error ? this.error_line_style : this.normal_line_style; |
|
} |
|
/** |
|
* 解锁轨迹 |
|
* |
|
* @param {any} po |
|
* @param {any} lastPoints |
|
* @memberof H5lock |
|
*/ |
|
drawLines(po?: Position) { |
|
const ctx = this.ctx; |
|
const line_style = this.line_style; |
|
const lastPoints = this.lastPoints; |
|
if (!lastPoints.length) { |
|
return; |
|
} |
|
ctx.beginPath(); |
|
ctx.lineWidth = line_style.lineWidth * this.devicePixelRatio; |
|
|
|
if (line_style.gradient) { |
|
const gradient = ctx.createLinearGradient(0, 0, this.canvas.width, this.canvas.height); |
|
line_style.colors.forEach(color_args => { |
|
gradient.addColorStop(color_args[0] as number, color_args[1] as string); |
|
}); |
|
ctx.strokeStyle = gradient; |
|
} else { |
|
ctx.strokeStyle = line_style.color; |
|
} |
|
|
|
ctx.moveTo(lastPoints[0].x, lastPoints[0].y); |
|
for (var i = 1; i < lastPoints.length; i++) { |
|
// const pre_point = lastPoints[i - 1]; |
|
const cur_point = lastPoints[i]; |
|
ctx.lineTo(cur_point.x, cur_point.y); |
|
} |
|
if (po) { |
|
ctx.lineTo(po.x, po.y); |
|
} |
|
ctx.stroke(); |
|
ctx.closePath(); |
|
|
|
} |
|
/** |
|
* 所有的轨迹点 |
|
* |
|
* @private |
|
* @type {Point[]} |
|
* @memberof H5lock |
|
*/ |
|
private arr: Point[] |
|
/** |
|
* 剩余可用的轨迹点 |
|
* |
|
* @private |
|
* @type {Point[]} |
|
* @memberof H5lock |
|
*/ |
|
private restPoint: Point[] |
|
/** |
|
* 创建解锁点的坐标,根据canvas的大小来平均分配半径 |
|
* |
|
* @memberof H5lock |
|
*/ |
|
createCircle() { |
|
var n = this.chooseType; |
|
var count = 0; |
|
this.r = this.ctx.canvas.width / (2 + 4 * n);// 公式计算 |
|
this.lastPoints = []; |
|
this.arr = []; |
|
this.restPoint = []; |
|
var r = this.r; |
|
for (var i = 0; i < n; i++) { |
|
for (var j = 0; j < n; j++) { |
|
count++; |
|
var obj = { |
|
x: j * 4 * r + 3 * r, |
|
y: i * 4 * r + 3 * r, |
|
index: count |
|
}; |
|
this.arr.push(obj); |
|
this.restPoint.push(obj); |
|
} |
|
} |
|
this.drawCircles(); |
|
//return arr; |
|
} |
|
drawCircles() { |
|
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); |
|
for (var i = 0; i < this.arr.length; i++) { |
|
this.drawCle(this.arr[i].x, this.arr[i].y); |
|
} |
|
} |
|
/** |
|
* 获取touch点相对于canvas的坐标 |
|
* |
|
* @param {any} e |
|
* @returns |
|
* @memberof H5lock |
|
*/ |
|
getPosition(e): Position | null { |
|
if (e && e.touches && e.touches.length) { |
|
var rect = e.currentTarget.getBoundingClientRect(); |
|
var po = { |
|
x: (e.touches[0].clientX - rect.left) * this.devicePixelRatio, |
|
y: (e.touches[0].clientY - rect.top) * this.devicePixelRatio |
|
}; |
|
return po; |
|
} |
|
return null; |
|
} |
|
error_msg_style = { |
|
font_size: 15, |
|
color: "#ff5c6a", |
|
stroke_color: "#ff99a2", |
|
stroke_width: 1, |
|
} |
|
private _show_error = false; |
|
set show_error(show_error) { |
|
this._show_error = show_error; |
|
requestAnimationFrame(this._update.bind(this)) |
|
} |
|
get show_error() { |
|
return this._show_error; |
|
} |
|
private _error_msg: string |
|
set error_msg(error_msg) { |
|
this._error_msg = error_msg; |
|
if (this.show_error) { |
|
requestAnimationFrame(this._update.bind(this)) |
|
} |
|
} |
|
get error_msg() { |
|
return this._error_msg; |
|
} |
|
private _error_auto_close_ti: any |
|
showError(msg: string, time = 1200, cb?: () => void) { |
|
this.show_error = true; |
|
this.error_msg = msg; |
|
clearTimeout(this._error_auto_close_ti); |
|
this._error_auto_close_ti = setTimeout(() => { |
|
this._error_auto_close_ti = null; |
|
this.show_error = false; |
|
cb instanceof Function && cb(); |
|
}, time) |
|
} |
|
/** |
|
* 最少选择的点 |
|
* |
|
* @memberof H5lock |
|
*/ |
|
min_point_number = 4; |
|
/** |
|
* touchend结束之后对密码和状态的处理 |
|
* |
|
* @param {any} psw |
|
* @memberof H5lock |
|
*/ |
|
private storePass() { |
|
// this.prevPoint = psw; |
|
// var str = ''; |
|
// for (var i = 0; i < psw.length; i++) { |
|
// str += psw[i].index; |
|
// } |
|
var str = ''; |
|
const lastPoints = this.lastPoints; |
|
for (var i = 0; i < lastPoints.length; i++) { |
|
str += lastPoints[i].index; |
|
} |
|
if (lastPoints.length < this.min_point_number) { |
|
this.disabled = true; |
|
this.showError(`请至少选择 ${this.min_point_number} 个点`, void 0, () => { |
|
this.disabled = false; |
|
this.reset(); |
|
}); |
|
} else { |
|
this.inputEnd && this.inputEnd(str); |
|
} |
|
} |
|
|
|
setChooseType(type) { |
|
this.chooseType = type; |
|
this.init(); |
|
} |
|
updatePassword() { |
|
window.localStorage.removeItem('passwordxx'); |
|
window.localStorage.removeItem('chooseType'); |
|
this.pswObj = {}; |
|
document.getElementById('title').innerHTML = '绘制解锁图案'; |
|
this.reset(); |
|
} |
|
initDom() { |
|
var wrap: Element |
|
if (this.container instanceof Element) { |
|
wrap = this.container |
|
} else { |
|
wrap = document.getElementById(this.container); |
|
} |
|
if (wrap) { |
|
var canvas = this.canvas = document.createElement('canvas'); |
|
canvas.className = "h5lock"; |
|
canvas.style.cssText = 'display: inline-block;'; |
|
|
|
wrap.appendChild(canvas); |
|
|
|
var width = this.width || 300; |
|
var height = this.height || 300; |
|
|
|
// document.body.appendChild(wrap); |
|
|
|
// 高清屏锁放 |
|
canvas.style.width = width + "px"; |
|
canvas.style.height = height + "px"; |
|
canvas.height = height * this.devicePixelRatio; |
|
canvas.width = width * this.devicePixelRatio; |
|
} |
|
|
|
} |
|
private pswObj: any |
|
private touchFlag: boolean |
|
private canvas: HTMLCanvasElement |
|
init() { |
|
this.initDom(); |
|
this.pswObj = window.localStorage.getItem('passwordxx') ? { |
|
step: 2, |
|
spassword: JSON.parse(window.localStorage.getItem('passwordxx')) |
|
} : {}; |
|
this.lastPoints = []; |
|
// this.prevPoint = []; |
|
this.touchFlag = false; |
|
// this.canvas = document.getElementById('canvas') as HTMLCanvasElement; |
|
this.ctx = this.canvas.getContext('2d'); |
|
this.createCircle(); |
|
this.bindEvent(); |
|
} |
|
reset() { |
|
this.createCircle(); |
|
} |
|
bindEvent() { |
|
this.canvas.addEventListener("touchstart", this.touchstart.bind(this), false); |
|
this.canvas.addEventListener("mousedown", this.touchstart.bind(this), false); |
|
this.canvas.addEventListener("touchmove", this.touchmove.bind(this), false); |
|
this.canvas.addEventListener("mousemove", this.touchmove.bind(this), false); |
|
this.canvas.addEventListener("touchend", this.touchend.bind(this), false); |
|
this.canvas.addEventListener("mouseup", this.touchend.bind(this), false); |
|
} |
|
|
|
private _loop = false |
|
startUpdateFrame() { |
|
if (!this._loop) { |
|
this._loop = true; |
|
requestAnimationFrame(this._updateFrame); |
|
} |
|
} |
|
private _updateFrame() { |
|
if (this._loop) { |
|
this._update(); |
|
requestAnimationFrame(this._updateFrame); |
|
} |
|
} |
|
stopUpdateFrame() { |
|
this._loop = false; |
|
} |
|
private _lastPos: Position | null = null; |
|
/** |
|
* 核心变换方法在touchmove时候调用 |
|
* |
|
* @memberof H5lock |
|
*/ |
|
private _update() { |
|
const po = this._lastPos; |
|
this.drawCircles(); |
|
// 绘制错误信息 |
|
if (this.show_error) { |
|
const error_msg_style = this.error_msg_style; |
|
const ctx = this.ctx; |
|
ctx.fillStyle = error_msg_style.color; |
|
ctx.strokeStyle = error_msg_style.stroke_color; |
|
ctx.lineWidth = error_msg_style.stroke_width * this.devicePixelRatio; |
|
ctx.textAlign = "center"; |
|
const font_size = error_msg_style.font_size * this.devicePixelRatio; |
|
ctx.font = font_size + "px sans-serif"; |
|
ctx.strokeText(this.error_msg, this.canvas.width / 2, font_size, this.canvas.width); |
|
ctx.fillText(this.error_msg, this.canvas.width / 2, font_size, this.canvas.width); |
|
} |
|
|
|
this.drawLines(po);// 每帧画轨迹 |
|
this.drawPoints();// 每帧画圆心 |
|
|
|
// for (var i = 0; i < this.restPoint.length; i++) { |
|
// if (Math.abs(po.x - this.restPoint[i].x) < this.r && Math.abs(po.y - this.restPoint[i].y) < this.r) { |
|
// this.drawPoints(); |
|
// this.lastPoints.push(this.restPoint[i]); |
|
// this.restPoint.splice(i, 1); |
|
// break; |
|
// } |
|
// } |
|
|
|
} |
|
disabled = false; |
|
private touchstart(e) { |
|
if (this.disabled) { |
|
return; |
|
} |
|
e.preventDefault();// 某些android 的 touchmove不宜触发 所以增加此行代码 |
|
this._lastPos = this.getPosition(e); |
|
if (this._tryStorePoint()) { |
|
this.touchFlag = true; |
|
this.startUpdateFrame(); |
|
} |
|
} |
|
private _tryStorePoint() { |
|
const pos = this._lastPos; |
|
for (let point of this.restPoint) { |
|
if (Math.abs(pos.x - point.x) < this.r && Math.abs(pos.y - point.y) < this.r) { |
|
this.lastPoints.push(point); |
|
this.restPoint.splice(this.restPoint.indexOf(point), 1); |
|
// console.log("触摸到指定的点咯!!", point); |
|
return true; |
|
} |
|
} |
|
} |
|
private touchmove(e) { |
|
if (this.disabled) { |
|
return; |
|
} |
|
if (this.touchFlag) { |
|
this._tryStorePoint(); |
|
this._lastPos = this.getPosition(e); |
|
} |
|
} |
|
private touchend(e) { |
|
if (this.disabled) { |
|
return; |
|
} |
|
if (this.touchFlag) { |
|
this.touchFlag = false; |
|
this.storePass(); |
|
this._lastPos = null; |
|
} |
|
// 等绘制玩最后一帧再结束 |
|
requestAnimationFrame(() => { |
|
this.stopUpdateFrame(); |
|
}); |
|
} |
|
} |
|
|