效果
实现
<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>