<template>
<div id="container">
<h3>{{ handTitle }}</h3>
<canvas ref="canvasRef" width="300" height="300" />
</div>
</template>
<script>
export default {
data() {
return {
handTitle: '绘制手势解锁',
points: [], // 初始手势圆坐标集合
restPoints: [], // 手势未选中的圆坐标集合
choosePoints: [], // 手势选中的圆坐标集合
touchFlag: false, // 初始点是否处于手势圆中
r: 0, // 圆半径
ctx: null, // canvas上下文对象
point_lines: '12369' // 检测选中圆心点['123'表示:选中第一、第二、第三个点]
}
},
mounted() {
this.initData()
},
methods: {
// 初始化
initData() {
const canvasDom = this.$refs.canvasRef
const ctx = canvasDom.getContext('2d')
this.ctx = ctx
this.createHandCircle(canvasDom)
},
// 创建初始手势圆
createHandCircle(canvasDom) {
// 重置数据
this.handTitle = '绘制手势解锁'
this.points = []
this.choosePoints = []
this.restPoints = []
this.touchFlag = false
// 重置绘制
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height)
const line_handNum = 3 // 每行手势圆数量
const r = canvasDom.width / (2 + 4 * line_handNum) // 总宽度拆分为半径数
this.r = r
for (let index = 1; index <= line_handNum * line_handNum; index++) {
const row = Math.ceil(index / line_handNum) - 1
const column = (index % line_handNum == 0 ? line_handNum : index % line_handNum) - 1
const x = (2 + 4 * column + 1) * r // x坐标点
const y = (2 + 4 * row + 1) * r // y坐标点
const point = {
x,
y,
index
}
this.points.push(point)
this.restPoints.push(point)
this.drawCle(x, y)
}
this.bindEvents(canvasDom)
},
// 绑定手势触发事件
bindEvents(canvasDom) {
canvasDom.addEventListener('touchstart', this.touchStart, false)
canvasDom.addEventListener('touchmove', this.touchMove, false)
canvasDom.addEventListener('touchend', this.touchEnd, false)
},
touchStart(e) {
// point有x和y,并且是相较于canvas边距
var point = this.getPosition(e)
// 判断是否在圆内的原理:勾股定理(x坐标与y坐标的平方小于等于r半径的平方) x*x+y*y <= r*r 在圆内
for (var i = 0; i < this.points.length; i++) {
const x_len = Math.abs(point.x - this.points[i].x)
const y_len = Math.abs(point.y - this.points[i].y)
// if (Math.abs(po.x - this.points[i].x) < this.r && Math.abs(po.y - this.points[i].y) < _this.r) { // 该判断是以圆心为中心的长度为半径的正方形判断处理
if (Math.pow(x_len, 2) + Math.pow(y_len, 2) <= Math.pow(this.r, 2)) {
this.touchFlag = true
// lastPoints存放的就是选中的圆圈的x/y坐标值
this.choosePoints.push(this.points[i])
this.restPoints.splice(i, 1)
break
}
}
},
touchMove(e) {
// touchmove做的就是:画圆drawChoosePoint和划线drawLine
if (this.touchFlag) {
this.handleTouchMove(this.getPosition(e))
}
},
touchEnd(e) {
const _this = this
if (this.touchFlag) {
this.storePass()
setTimeout(function () {
_this.initData()
}, 1000)
}
},
// 获取手势当前坐标点
getPosition(e) {
// 获取touch点相对于canvas的坐标
const rect = e.currentTarget.getBoundingClientRect()
const point = {
x: e.touches[0].clientX - rect.left,
y: e.touches[0].clientY - rect.top
}
return point
},
// 手势移动处理方法
handleTouchMove(point) {
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height)
// 重新画9个圆圈
for (var i = 0; i < this.points.length; i++) {
// 每帧先把面板画出来
this.drawCle(this.points[i].x, this.points[i].y)
}
this.drawChoosePoint() // 画选中圆
this.drawLine(point) // 画连接线
// 1、检测手势移动的位置是否处于下一个圆内
// 2、圆内的话则画圆 drawChoosePoint
// 3、已经画过实心圆的圆,无需重复检测
for (var i = 0; i < this.restPoints.length; i++) {
const x_len = Math.abs(point.x - this.restPoints[i].x)
const y_len = Math.abs(point.y - this.restPoints[i].y)
if (Math.pow(x_len, 2) + Math.pow(y_len, 2) <= Math.pow(this.r, 2)) {
this.drawChoosePoint()
this.choosePoints.push(this.restPoints[i])
this.restPoints.splice(i, 1)
break
}
}
},
// 画初始圆环
drawCle(x, y) {
this.ctx.strokeStyle = '#CFE6FF'
this.ctx.lineWidth = 2
this.ctx.beginPath()
this.ctx.arc(x, y, this.r, 0, Math.PI * 2, true)
this.ctx.closePath()
this.ctx.stroke()
},
// 画选中点的圆
drawChoosePoint() {
for (var i = 0; i < this.choosePoints.length; i++) {
this.ctx.fillStyle = '#CFE6FF'
this.ctx.beginPath()
this.ctx.arc(this.choosePoints[i].x, this.choosePoints[i].y, this.r / 2, 0, Math.PI * 2, true)
this.ctx.closePath()
this.ctx.fill()
}
},
// 画选中点的圆的连线,最后连上手势当前点
drawLine(point) {
this.ctx.beginPath()
this.ctx.lineWidth = 3
this.ctx.moveTo(this.choosePoints[0].x, this.choosePoints[0].y)
for (var i = 1; i < this.choosePoints.length; i++) {
this.ctx.lineTo(this.choosePoints[i].x, this.choosePoints[i].y)
}
this.ctx.lineTo(point.x, point.y)
this.ctx.stroke()
this.ctx.closePath()
},
// 手势路径检测
storePass() {
if (this.checkPass()) {
this.handTitle = '解锁成功'
this.drawStatusPointColor('#2CFF26')
} else {
this.handTitle = '解锁失败'
this.drawStatusPointColor('#f03')
}
},
// 检测手势点连线与初始点手势连线是否一致
checkPass() {
let current_lines = ''
for (let i = 0; i < this.choosePoints.length; i++) {
current_lines += this.choosePoints[i].index
}
return this.point_lines === current_lines
},
// 绘制不同检测状态的点颜色
drawStatusPointColor(color) {
for (var i = 0; i < this.choosePoints.length; i++) {
this.ctx.strokeStyle = color
this.ctx.beginPath()
this.ctx.arc(this.choosePoints[i].x, this.choosePoints[i].y, this.r, 0, Math.PI * 2, true)
this.ctx.closePath()
this.ctx.stroke()
}
}
}
}
</script>
<style lang="scss" scoped>
#container {
width: 100%;
height: 100vh;
text-align: center;
background: #305066;
h3 {
padding: 100px 0 50px 0;
color: #22ceaa;
font-size: 30px;
}
}
</style>
05-16
12-20
1222