h5实现手势解锁

<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>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值