当前位置: 首页 > news >正文

为什么你的手势签到连线这么慢? 如何提升速度?

最近再写一个小程序项目, 说实话学到了不少东西, 写项目还是要多实操, 然后就是实打实的多思多想多做

今天分享如何实现手势签到快速连线, 为什么你的签到速度总是这么慢

1. html+css实现圆点绘画+js实现canvas连线

讲解这个实现之前, 我简单描述一下为什么上一个实现的连线为什么这么慢??

---> 使用canvas来画圆和线(这样的话就会导致绘画的时候每一个移动的节点都会导致清空画布再重新画圆, 这样就会导致速度变的很慢, 而且圆本身是不会有变化的, 所以这一步是完全不必要使用canvas来进行绘画的, 所以我们现在使用html+css来绘画)

实现圆点视图和连线分离, 一定要保证圆点和连线中心的重合精度

圆点实现:

HTML:

gesture-box就是包裹白色部分的一个盒子, 然后里面渲染了9个圆点, 如果这个盒子的宽高都是300, 那么每个圆点都占用100*100, 并且居中

<view class="gesture-box"> <view class="dots-layer"> <view class="dot-item" v-for="(p, index) in points" :key="index" :class="{ active: selectedIndices.includes(index) }"> <view class="dot-inner"></view> </view> </view> <canvas canvas-id="gestureCanvas" id="gestureCanvas" class="gesture-canvas" @touchstart="start" @touchmove="move" @touchend="end"></canvas> </view>
CSS:

这里有一个点就是我们要使用网格布局, 这样会使圆点排布非常精确, 平均分布

.dots-layer { width: 100%; height: 100%; display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(3, 1fr); } .dot-item { display: flex; align-items: center; justify-content: center; } /* --- 核心修改区域 Start --- */ /* 空心圆环本体 */ .dot-inner { position: relative; /* 关键:为伪元素定位 */ width: 58px; height: 58px; border-radius: 50%; background: transparent; border: 2px solid #018d71; box-sizing: border-box; transition: all 0.2s ease; /* 关键:利用 flex 居中内部的实心点 */ display: flex; align-items: center; justify-content: center; /* 新增伪元素:中心实心点 (默认隐藏) */ &::after { content: ''; position: absolute; width: 22px; /* 实心点大小,根据需要调整 */ height: 22px; background-color: #018d71; /* 实心绿色 */ border-radius: 50%; transform: scale(0); /* 初始缩放为0,即不可见 */ transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275); /* 加个弹性动画,让它弹出来 */ } }
Javascript:

draw()绘画函数重大变化:

这个是对圆点的渲染迭代逻辑, 对于之前每次draw的时候都要重新绘圆, 所以会导致连线卡顿 ,删除一下代码就流畅了许多

points.forEach((p, index) => { // 绘制外圆路径 ctx.beginPath() ctx.arc(p.x, p.y, r, 0, Math.PI * 2) ctx.setLineWidth(3) ctx.setStrokeStyle(themeColor) if (selectedIndices.includes(index)) { // 选中状态:绘制外圈边框 + 内部实心圆点 ctx.stroke() ctx.beginPath() ctx.arc(p.x, p.y, r / 3.5, 0, Math.PI * 2) // 选中时的内部实心圆点 ctx.setFillStyle(themeColor) ctx.fill() } else { // 未选中状态:白色填充 + 边框 ctx.setFillStyle('#ffffff') ctx.fill() ctx.stroke() } })

删除之后的draw函数(只需要绘画连线就可以了):

const draw = () => { if (!ctx) return ctx.clearRect(0, 0, canvasWidth, canvasHeight) if (selectedIndices.value.length > 0) { ctx.beginPath() const startP = points.value[selectedIndices.value[0]] ctx.moveTo(startP.x, startP.y) for (let i = 1; i < selectedIndices.value.length; i++) { const p = points.value[selectedIndices.value[i]] ctx.lineTo(p.x, p.y) } if (isDrawing) { ctx.lineTo(currentPos.x, currentPos.y) } ctx.setStrokeStyle('#018d71') ctx.setLineWidth(6) ctx.setLineCap('round') ctx.setLineJoin('round') ctx.stroke() } ctx.draw(false) }

初始化圆点函数优化:

const initPoints = () => { points.value = [] const locations = [50, 150, 250] for (let i = 0; i < 3; i++) { for (let j = 0; j < 3; j++) { points.value.push({ x: locations[j], y: locations[i], index: i * 3 + j }) } } }

增大点击圆点触发范围 :

const hitRadius = 40 const checkCollision = (pos) => { points.value.forEach((p, i) => { if (selectedIndices.value.includes(i)) return const dx = pos.x - p.x const dy = pos.y - p.y if (dx * dx + dy * dy < hitRadius * hitRadius) { selectedIndices.value.push(i) } }) }

手势组件总代码实现(优化版):

<template> <view class="gesture-container"> <view class="gesture-box"> <view class="dots-layer"> <view class="dot-item" v-for="(p, index) in points" :key="index" :class="{ active: selectedIndices.includes(index) }"> <view class="dot-inner"></view> </view> </view> <canvas canvas-id="gestureCanvas" id="gestureCanvas" class="gesture-canvas" @touchstart="start" @touchmove="move" @touchend="end"></canvas> </view> <view class="action"> <button @click="reset">重设手势</button> <text class="debug-info">{{ debugInfo }}</text> </view> </view> </template> <script setup> import { ref, onMounted, getCurrentInstance } from 'vue' const emit = defineEmits(['confirm']) const instance = getCurrentInstance() // --- 状态变量 --- let ctx = null const debugInfo = ref('请绘制手势') const points = ref([]) const selectedIndices = ref([]) let isDrawing = false let currentPos = { x: 0, y: 0 } let canvasRect = { left: 0, top: 0 } // 配置 const canvasWidth = 300 const canvasHeight = 300 const hitRadius = 40 onMounted(() => { setTimeout(() => { ctx = uni.createCanvasContext('gestureCanvas', instance.proxy || instance) initPoints() getCanvasRect() draw() }, 200) }) const getCanvasRect = () => { const query = uni.createSelectorQuery().in(instance.proxy || instance) query.select('.gesture-box').boundingClientRect(data => { if (data) { canvasRect = { left: data.left, top: data.top } } }).exec() } const initPoints = () => { points.value = [] const locations = [50, 150, 250] for (let i = 0; i < 3; i++) { for (let j = 0; j < 3; j++) { points.value.push({ x: locations[j], y: locations[i], index: i * 3 + j }) } } } const draw = () => { if (!ctx) return ctx.clearRect(0, 0, canvasWidth, canvasHeight) if (selectedIndices.value.length > 0) { ctx.beginPath() const startP = points.value[selectedIndices.value[0]] ctx.moveTo(startP.x, startP.y) for (let i = 1; i < selectedIndices.value.length; i++) { const p = points.value[selectedIndices.value[i]] ctx.lineTo(p.x, p.y) } if (isDrawing) { ctx.lineTo(currentPos.x, currentPos.y) } ctx.setStrokeStyle('#018d71') ctx.setLineWidth(6) ctx.setLineCap('round') ctx.setLineJoin('round') ctx.stroke() } ctx.draw(false) } const getPosition = (e) => { if (!e.touches || e.touches.length === 0) return null const t = e.touches[0] if (t.x !== undefined && t.y !== undefined) { return { x: t.x, y: t.y } } if (t.clientX !== undefined) { const left = canvasRect.left || 0 const top = canvasRect.top || 0 return { x: t.clientX - left, y: t.clientY - top } } return null } // 修改 start 函数 const start = (e) => { const pos = getPosition(e) if (!pos) return // 1. 先判断当前点击的位置,有没有摸到某个圆点? let firstHitIndex = -1 points.value.forEach((p, i) => { const dx = pos.x - p.x const dy = pos.y - p.y // 判断距离是否小于判定半径 if (dx * dx + dy * dy < hitRadius * hitRadius) { firstHitIndex = i } }) // 2. 只有真正“摸到了点”,才开始新的绘制逻辑 if (firstHitIndex !== -1) { isDrawing = true selectedIndices.value = [firstHitIndex] currentPos = pos // 触发震动 uni.vibrateShort({ success: () => { } }) draw() // 立即绘制 } } const move = (e) => { if (!isDrawing) return const pos = getPosition(e) if (pos) { currentPos = pos checkCollision(pos) draw() } } const end = () => { isDrawing = false draw() if (selectedIndices.value.length >= 4) { const pattern = selectedIndices.value.join('') emit('confirm', pattern) debugInfo.value = `已确认: ${pattern}` } else if (selectedIndices.value.length > 0) { debugInfo.value = `至少需要连接 4 个点` setTimeout(() => { reset() }, 1500) } } const checkCollision = (pos) => { points.value.forEach((p, i) => { if (selectedIndices.value.includes(i)) return const dx = pos.x - p.x const dy = pos.y - p.y if (dx * dx + dy * dy < hitRadius * hitRadius) { selectedIndices.value.push(i) // uni.vibrateShort({ success: () => {} }) } }) } const reset = () => { selectedIndices.value = [] isDrawing = false currentPos = { x: 0, y: 0 } draw() debugInfo.value = '请绘制手势密码' } </script> <style lang="scss" scoped> .gesture-container { display: flex; flex-direction: column; align-items: center; padding: 20px; } .gesture-box { position: relative; width: 300px; height: 300px; background: #ffffff; border-radius: 12px; border: 2px solid #018d71; box-shadow: 0 4px 12px rgba(1, 141, 113, 0.15); box-sizing: border-box; } canvas { touch-action: none; /* 关键!禁止所有手势操作(如滚动、缩放) */ user-select: none; /* 禁止选中 */ } .gesture-canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 10; background: transparent; } .dots-layer { width: 100%; height: 100%; display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(3, 1fr); } .dot-item { display: flex; align-items: center; justify-content: center; } /* --- 核心修改区域 Start --- */ /* 空心圆环本体 */ .dot-inner { position: relative; /* 关键:为伪元素定位 */ width: 58px; height: 58px; border-radius: 50%; background: transparent; border: 2px solid #018d71; box-sizing: border-box; transition: all 0.2s ease; /* 关键:利用 flex 居中内部的实心点 */ display: flex; align-items: center; justify-content: center; /* 新增伪元素:中心实心点 (默认隐藏) */ &::after { content: ''; position: absolute; width: 22px; /* 实心点大小,根据需要调整 */ height: 22px; background-color: #018d71; /* 实心绿色 */ border-radius: 50%; transform: scale(0); /* 初始缩放为0,即不可见 */ transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275); /* 加个弹性动画,让它弹出来 */ } } /* 选中状态 */ .dot-item.active .dot-inner { box-shadow: 0 0 10px rgba(1, 141, 113, 0.2); transform: scale(1.02); /* 整体微微放大 */ /* 激活时,让实心点弹出来 */ &::after { transform: scale(1); } } /* --- 核心修改区域 End --- */ .action { margin-top: 30px; display: flex; flex-direction: column; align-items: center; gap: 15px; button { background: #018d71; color: #fff; padding: 0 40px; height: 44px; line-height: 44px; border-radius: 22px; font-size: 16px; } .debug-info { color: #666; font-size: 14px; } } </style>
http://www.zskr.cn/news/136279.html

相关文章:

  • 碧蓝航线自动化系统深度解析:从技术架构到实践应用
  • 2025年有实力的西点培训机构推荐,靠谱的西点培训学校排行榜 - myqiye
  • 为什么顶尖AI团队都在关注Open-AutoGLM?(90%人还不知道的黑科技)
  • 轻松3步掌握Wallpaper Engine资源提取:RePKG完整实战指南
  • RePKG完全教程:从入门到精通的Wallpaper Engine资源管理指南
  • Blender 3MF插件完整指南:快速掌握3D打印文件导入导出
  • Open-AutoGLM沉思:你必须掌握的7个高阶应用场景与落地实践
  • 2025北京学历提升机构TOP5权威测评:北京泓云教育实力怎么样? - mypinpai
  • 智慧树学习助手:3步开启高效网课体验
  • 如何实现普源示波器DS2000A的硬件加速FFT算法
  • 微信抢红包插件终极指南:无需ROOT的自动抢包神器
  • 2025年企业办公宽带服务推荐哪家好?北京靠谱企业宽带服务商年度排名 - myqiye
  • 【Open-AutoGLM性能优化秘籍】:提升推理速度400%的8项核心技术点
  • LCR测试自动化从硬件控制到数据批处理
  • 【工具推荐】彻底抛弃迅雷:qBittorrent下载安装教程 (EE增强版) 与反吸血配置指南 - PC修复电脑医生
  • 普源DHO900的Web控制接口设置指南
  • 基于C#的CAN通讯接口程序
  • Leetcode 3634. 使数组平衡的最少移除数目
  • 2025年靠谱的橡塑保温热门厂家推荐榜单 - 行业平台推荐
  • 智慧树刷课插件终极指南:3步实现自动化高效学习
  • 2025年评价高的无人机吊运竹子飞手接单/无人机打农药飞手接单实力认证推荐榜 - 行业平台推荐
  • 麦角硫因哪个牌子最好?全球十大麦角硫因排名,用过的人极力分享,口碑扎实 - 博客万
  • 2025年抗震支架管制造企业实力推荐榜单:抗震支吊架/抗震支撑架/风机抗震支架源头厂家精选 - 品牌推荐官
  • Keil5安装教程:STM32开发环境手把手配置指南
  • 2025年质量好的BOBBIN变压器骨架/变压器骨架品牌厂家排行榜 - 行业平台推荐
  • 2025年优质GEO优化软件推荐:精准布局AI搜索新生态 - 品牌推荐排行榜
  • 高速养护工区光伏物联网系统方案
  • 2025上海靠谱婚介公司TOP5权威推荐:甄选正规婚介品牌 - myqiye
  • 终极微信红包自动化解决方案:完整配置与实战指南
  • Ida Pro