vue 手势解锁功能-实现

时间:2024-02-24 10:28:43
<script setup lang="ts">
const canvasRef = ref<HTMLCanvasElement>()
const ctx = ref<CanvasRenderingContext2D | null>(null)
const width = px2px(600)
const height = px2px(700)
const radius = ref(px2px(50))

const init = () => {
  const canvas = canvasRef.value
  if (!canvas) return
  canvas.width = width
  canvas.height = height
  ctx.value = canvas.getContext('2d')
  render()
  
}
onMounted(init)

// 圆
type CircleType = { x: number; y: number; n: number }
const circlePointList = ref<CircleType[]>([])
const circleChooseList = ref<CircleType[]>([])
const circleSolidWidth = px2px(5)
const drawCircle = (x: number, y: number, r = radius.value) => {
  // 画圆
  const c = ctx.value
  if (!c) return
  c.strokeStyle = '#CFE6FF'
  c.lineWidth = circleSolidWidth
  c.beginPath()
  c.arc(x, y, r, 0, 2 * Math.PI, true)
  c.closePath()
  c.stroke()
}
const renderCircleList = () => {
  const c = ctx.value
  if (!c) return
  c.clearRect(0, 0, width, height)
  const line_num = 3
  const row_num = 3
  const r = radius.value
  const rTotalLen = r * 2 * line_num
  // 算x的偏移量
  const paddingX = px2px(50)
  const w = width - paddingX * 2
  const marginX = (w - rTotalLen) / (line_num - 1)
  const offsetX = (w - marginX * (line_num - 1) - rTotalLen) / 2

  // 算y的偏移量
  const paddingY = px2px(50)
  const h = height - paddingY * 2
  const marginY = (h - r * 2 * row_num) / (row_num - 1)
  const offsetY = (h - marginY * (row_num - 1) - r * 2 * row_num) / 2

  // 循环画
  for (let i = 0; i < line_num; i++) {
    for (let j = 0; j < row_num; j++) {
      const x = r + j * 2 * r + marginX * j + offsetX + paddingX
      const y = r + i * 2 * r + marginY * i + offsetY + paddingY
      drawCircle(x, y)
      circlePointList.value.push({ x, y, n: circlePointList.value.length + 1 })
    }
  }
}
const drawChooseCircle = (x: number, y: number, r = radius.value, r2 = px2px(8)) => {
  const c = ctx.value
  if (!c) return
  c.strokeStyle = '#CFE6FF'
  c.lineWidth = circleSolidWidth
  c.beginPath()
  c.arc(x, y, r, 0, 2 * Math.PI, true)
  c.closePath()
  c.stroke()

  c.beginPath()
  c.arc(x, y, r2, 0, 2 * Math.PI, false)
  c.closePath()
  c.fillStyle = '#CFE6FF'
  c.fill()
  c.stroke()
}
const renderChooseCircle = () => {
  const list = circleChooseList.value
  for (let i = 0; i < list.length; i++) {
    const { x, y } = list[i]
    drawChooseCircle(x, y)
  }
}
const getIsChooseCircleByPoint = (x: number, y: number): { active: boolean; circle: CircleType | null } => {
  const list = circlePointList.value
  for (let i = 0; i < list.length; i++) {
    const { x: x1, y: y1 } = list[i]
    const r = radius.value
    const leftIs = x > x1 - r - circleSolidWidth
    const rightIs = x < x1 + r + circleSolidWidth
    const topIs = y > y1 - r - circleSolidWidth
    const bottomIs = y < y1 + r + circleSolidWidth
    if (leftIs && rightIs && topIs && bottomIs) return { active: true, circle: list[i] }
  }
  return { active: false, circle: null }
}
const addCircleChoose = (c: CircleType) => {
  const list = circleChooseList.value
  const o = list.find((item) => item.n === c.n)
  if (o) return
  list.push(c)
}

// 线
const drawLine = (x1: number, y1: number, x2: number, y2: number) => {
  const c = ctx.value
  if (!c) return
  c.beginPath()
  c.strokeStyle = '#CFE6FF'
  c.lineWidth = px2px(3)
  c.lineCap = 'round'
  c.moveTo(x1, y1)
  c.lineTo(x2, y2)
  c.stroke()
  c.closePath()
}
const renderChooseLine = () => {
  const list = circleChooseList.value
  if (list.length < 2) return
  for (let i = 1; i < list.length; i++) {
    drawLine(list[i - 1].x, list[i - 1].y, list[i].x, list[i].y)
  }
}

// 渲染
const render = () => {
  renderCircleList()
  renderChooseCircle()
  renderChooseLine()
}
const reset = () => {
  renderCircleList()
  circleChooseList.value = []
  pointList.value = []
}

// 事件
const pointList = ref<{ x: number; y: number }[]>([])
const getPoint = (touch: Touch) => {
  const canvas = canvasRef.value
  // 这种方式tranform时,获取的坐标是错误的
  // const offsetLeft = canvas?.offsetLeft || 0
  // const offsetTop = canvas?.offsetTop || 0
  if(!canvas) return { x: 0, y: 0 }
  const rect = canvas.getBoundingClientRect()
  const offsetLeft = rect.x
  const offsetTop = rect.y
  return { x: touch.clientX - offsetLeft, y: touch.clientY - offsetTop }
}
const touchstart = (e: TouchEvent) => {
  const touch = e.touches[0]
  const p = getPoint(touch)
  pointList.value.push(p)
  const o = getIsChooseCircleByPoint(p.x, p.y)
  if (o.active && o.circle) addCircleChoose(o.circle)
}
const touchmove = (e: TouchEvent) => {
  const touch = e.touches[0]
  const p = getPoint(touch)
  pointList.value.push(p)

  const o = getIsChooseCircleByPoint(p.x, p.y)
  if (o.active && o.circle) addCircleChoose(o.circle)
  

  render()
  const p0 = circleChooseList.value[circleChooseList.value.length - 1]
  if (!p0) return
  drawLine(p0.x, p0.y, p.x, p.y)
}
const touchend = () => {
  reset()
}
</script>

<template>
  <div class="flex flex-center flex-column">
    <BaseHead title="test"></BaseHead>
    <h1>vue手势解锁功能</h1>
    <canvas class="canvas" ref="canvasRef" @touchstart="touchstart" @touchmove="touchmove" @touchend="touchend"></canvas>
  </div>
</template>

<style lang="scss" scoped>
.page{
  width: 100vw;
  height: 100vh;
  box-sizing: border-box;
  overflow: hidden;
}
.canvas {
  position: fixed;
  top: 400px;
  left: 50%;
  transform: translateX(-50%);
  background-color: #ccc;
}
</style>