一、本期内容简述

1. 开发内容

上一期,我们一起学习了如何进行绘画,本期我们将学习如何擦除我们所绘画的内容,也就是“橡皮擦”功能。

首先,我们应该明确需求,橡皮擦可以擦除掉我们绘画的内容。

2. 开发需求

所以开发需求:

(1)​​擦除绘画内容:

  • 单指触摸屏幕并缓慢移动即可擦除

​​(2)修改橡皮擦的形状和大小​​:

  • 可以选择橡皮擦的形状
  • 可以调整橡皮擦的大小

二、核心实现代码

1. html

添加橡皮擦的预览效果显示

 <!-- 橡皮擦预览 --><view class="eraser-preview":class="`shape-${eraserShape}`":style="{display: eraserPreviewVisible ? 'block' : 'none',left: `${eraserPreviewPos.x}px`,top: `${eraserPreviewPos.y}px`,width: `${eraserSize}px`,height: `${eraserSize}px`}"></view>

2. 常量定义

const currentMode = ref('draw') // 'draw' 或 'erase'
const eraserShapes = ref(['圆形', '方形'])
const eraserShapeIndex = ref(0) // 0: 圆形, 1: 方形

首先定义currentMode作为判断当前是绘画,还是使用橡皮擦的模式

定义橡皮擦的形状,以及当前所选橡皮擦的索引值

3. 触摸状态

还是之前的核心三个方法的 内容,触摸、触摸中、触摸结束

(1)handleTouchStart

你会发现,这次得如果是绘画就是将单签的位置添加到currentPaht中,如果是橡皮擦则记录橡皮擦的位置,显示橡皮擦,并

const handleTouchStart = async (e) => {if (!ensureContext()) returnisDrawing.value = trueconst point = {x: e.touches[0].x,y: e.touches[0].y}if (currentMode.value === 'draw') {// 开始新的绘图路径currentPath.value = [point]} else {// 橡皮擦模式eraserPreviewPos.value = { x: point.x, y: point.y }eraserPreviewVisible.value = trueeraseAtPoint(point)}
}

其中eraseAtPoint

// 跟踪最后一个橡皮擦操作
let lastEraserOperation = null
let eraserTimeout = null// 在指定点进行擦除
const eraseAtPoint = (point) => {const size = eraserSize.valueconst halfSize = size / 2// 直接在画布上绘制背景色来覆盖原有内容ctx.value.setFillStyle('#ffffff') // 使用画布背景色ctx.value.beginPath()if (eraserShape.value === 'circle') {// 圆形橡皮擦ctx.value.arc(point.x, point.y, halfSize, 0, 2 * Math.PI)} else {// 方形橡皮擦ctx.value.rect(point.x - halfSize,point.y - halfSize,size,size)}ctx.value.fill()ctx.value.draw(true)// 优化:批量处理橡皮擦操作const currentTime = Date.now()// 如果有最近的橡皮擦操作,且时间间隔短、参数相同,则合并if (lastEraserOperation && currentTime - lastEraserOperation.time < 100 && lastEraserOperation.size === size && lastEraserOperation.shape === eraserShape.value) {// 添加当前点到最后一个橡皮擦操作lastEraserOperation.points.push({ x: point.x, y: point.y })} else {// 创建新的橡皮擦操作lastEraserOperation = {type: 'eraser',points: [{ x: point.x, y: point.y }],size: size,shape: eraserShape.value,time: currentTime}drawingHistory.value.push(lastEraserOperation)}// 清除之前的定时器if (eraserTimeout) {clearTimeout(eraserTimeout)}// 设置定时器,在一段时间不操作后重置最后一个橡皮擦操作eraserTimeout = setTimeout(() => {lastEraserOperation = null}, 200)
}

  • 执行擦除:在画布上指定的 point 点,用橡皮擦的形状和大小,覆盖上背景色(白色),从而实现视觉上的擦除效果。
  • 记录历史:将这次擦除操作作为一个对象,高效地添加到 drawingHistory 数组中。这里的“高效”体现在它会合并短时间内连续发生的、参数相同的擦除操作,以避免历史记录数组变得过于庞大,影响后续的重绘和撤销操作。
  • eraserTimeout 是一个计时器,它的核心作用是界定一次连续的、完整的橡皮擦操作。它通过一个“延迟重置”的机制,告诉程序:“如果用户在短时间内(比如200毫秒)没有再擦了,我们就认为他这次擦的动作已经结束了,下一次擦就是一次全新的动作了。”

(2)handleTouchMove

const handleTouchMove = async (e) => {if (!isDrawing.value || !ensureContext()) returnconst point = {x: e.touches[0].x,y: e.touches[0].y}if (currentMode.value === 'draw') {// 绘图模式 - 添加点到当前路径currentPath.value.push(point)// 优化:只绘制当前路径的最后一段,而不是重绘整个画布if (currentPath.value.length > 1) {const lastPoint = currentPath.value[currentPath.value.length - 2]const currentPoint = currentPath.value[currentPath.value.length - 1]ctx.value.setStrokeStyle(currentColor.value)ctx.value.setLineWidth(lineSize.value)ctx.value.setLineCap('round')ctx.value.setLineJoin('round')ctx.value.beginPath()ctx.value.moveTo(lastPoint.x, lastPoint.y)ctx.value.lineTo(currentPoint.x, currentPoint.y)ctx.value.stroke()ctx.value.draw(true)}} else {// 橡皮擦模式eraserPreviewPos.value = { x: point.x, y: point.y }eraseAtPoint(point)}
}

(3)handleTouchEnd

触摸结束

const handleTouchEnd = () => {if (!isDrawing.value) returnif (currentMode.value === 'draw' && currentPath.value.length > 0) {// 保存完成的绘图路径drawingHistory.value.push({type: 'draw',points: [...currentPath.value],color: currentColor.value,size: lineSize.value})}isDrawing.value = falsecurrentPath.value = []eraserPreviewVisible.value = false
}

drawingHistory 是一个“记忆库”或“操作日志”。它记录了用户在画布上执行的每一个绘图和擦除动作。这使得应用能够实现重绘、撤销/重做(如果需要添加的话)以及最终保存等高级功能。

4. css

/* 橡皮擦预览样式 */
.eraser-preview {position: absolute;pointer-events: none;z-index: 9999;background-color: rgba(200, 200, 200, 0.3);border: 1px dashed #666;transform: translate(-50%, -50%);&.shape-circle {border-radius: 50%;}&.shape-square {border-radius: 0;}
}

      本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
      如若转载,请注明出处:http://www.pswp.cn/news/919057.shtml
      繁体地址,请注明出处:http://hk.pswp.cn/news/919057.shtml
      英文地址,请注明出处:http://en.pswp.cn/news/919057.shtml

      如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

      相关文章

      《A Practical Guide to Building Agents》文档学习

      《A Practical Guide to Building Agents》文档总结 该文档是一份面向产品和工程团队的实用指南&#xff0c;旨在帮助团队探索并构建首个基于大语言模型&#xff08;LLM&#xff09;的智能体&#xff08;Agent&#xff09;&#xff0c;提炼了大量客户部署经验&#xff0c;提供了…

      OpenCV图像注册模块

      操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 注册模块实现了参数化图像配准。所实现的方法是直接对齐&#xff08;direct alignment&#xff09;&#xff0c;即&#xff0c;它直接使用像素值来…

      模型驱动与分布式建模:技术深度与实战落地指南

      摘要 在AI、云原生与全球化协作的大潮中&#xff0c;模型驱动架构&#xff08;MDA&#xff09;与分布式建模不再是概念&#xff0c;而是支撑复杂系统设计与持续演化的核心引擎。本文从元模型、模型转换引擎&#xff0c;到协同协议、冲突解决算法&#xff0c;再到AI辅助建模与自…

      计算机基础速通--数据结构·图的基础应用二(基础图算法)

      如有问题大概率是我的理解比较片面&#xff0c;欢迎评论区或者私信指正。 最近了解到了一个新的改变和提高自己的方法时刻记录不论多小的事情都记下&#xff0c;我目前用了4天&#xff0c;之前感觉一天天忙死但没啥收获&#xff0c;但是记录了之后知道自己的时间花在了哪里&…

      设计模式-策略模式 Java

      模式概述 策略模式是一种行为型设计模式&#xff0c;它通过定义一系列可互换的算法&#xff0c;并将每个算法封装成独立类&#xff0c;使客户端能够根据需要动态切换算法 简单代码示例 // 1. 抽象策略接口 interface PaymentStrategy {void pay(int amount); }// 2. 具体策略实…

      【机器学习深度学习】客观评估训练程度

      目录 前言 一、什么是客观评估&#xff1f; 二、客观评估的两大核心方法 1. 判别式评测&#xff08;Discriminative Evaluation&#xff09; 2. 生成式评测&#xff08;Generative Evaluation&#xff09; 三、为什么客观评估成本更高&#xff1f; 1.训练目标收紧 2.训…

      Linux软件编程:线程间通信

      目录 一、线程间通信基础 1. 概念 2. 通信基础&#xff1a;共享空间 二、互斥锁&#xff08;Mutex&#xff09; 1. 概念 2. 使用流程 3. 函数接口 三、死锁 1. 概念 2. 死锁产生的 4 个必要条件 3. 避免死锁的方法 四、信号量&#xff08;Semaphore&#xff09; 1…

      【学习笔记】JVM GC回收机制

      1.三种基本的垃圾回收算法 1>标记-清除法 ①先将从树根开始&#xff0c;可以到达的对象标记为可达&#xff08;JVM中的对象们存储为一颗树&#xff09; ②将没有标记的对象清除掉 缺点&#xff1a;会产生大量内存碎片 2>复制算法&#xff08;新生代&#xff09; ①先将a区…

      软件的终极:为70亿人编写70亿个不同的软件

      这是个脑洞大开的想法。昨天晚上&#xff0c;我在用Claude code帮我写一个小工具&#xff0c;用来管理我本地那些乱七八糟的文档。写着写着&#xff0c;突然意识到一个问题&#xff1a;这个工具完全是按照我的工作习惯定制的——我喜欢用Markdown&#xff0c;习惯把TODO放在文件…

      LakeHouse--湖仓一体架构

      大家可能发现了,近些年湖仓一体数据架构被提及的频率越来越高。各家大厂也有湖仓一体架构的实践,也有很多公开分享。 那什么是湖仓一体?为什么出现了湖仓一体架构,换言之,它解决了以前数据仓库、数据湖+数仓两层架构所不能解决的什么问题? 本文会从数仓、数据湖依次介绍…

      基于FPGA的实时图像处理系统(1)——SDRAM回环测试

      SDRAM回环设计 文章目录SDRAM回环设计一、SDRAM简介1、引脚2、内部结构框图3、操作指令二、系统设计三、实现流程1、SDRAM接口2、FIFO设置3、内部SDRAM的控制模块4、其他四、实现效果五、总结六、代码1、top2、sdram_top3、sdram_ctrl一、SDRAM简介 SDRAM英文全称“Synchronou…

      一键检测接口是否存活:用 Python/Shell 写个轻量级监控脚本

      网罗开发&#xff08;小红书、快手、视频号同名&#xff09;大家好&#xff0c;我是 展菲&#xff0c;目前在上市企业从事人工智能项目研发管理工作&#xff0c;平时热衷于分享各种编程领域的软硬技能知识以及前沿技术&#xff0c;包括iOS、前端、Harmony OS、Java、Python等方…

      优秀工具包-Hutool工具详解

      优秀工具包-Hutool工具详解 课程概述 Hutool简介 定位&#xff1a; 小而全的Java工具库&#xff0c;简化开发流程。对文件、流、加密解密、转码、正则、线程、XML等JDK方法进行封装。 核心优势&#xff1a;零依赖、高性能、中文网页完善。 应用场景&#xff1a;Web开发、数…

      《深度解构:构建浏览器端Redis控制台的WebSocket协议核心技术》

      Redis作为高性能的内存数据库,其原生客户端多依赖命令行或桌面应用,而浏览器端控制台的缺失,成为制约Web化管理的关键瓶颈,WebSocket协议的出现,打破了HTTP协议单向通信的局限,为浏览器与Redis服务之间建立持久、双向的实时连接提供了可能。本文将从协议本质、交互逻辑、…

      Pushgateway安装和部署,以及对应Prometheus调整

      目录Pushgateway简介安装验证Prometheus的配置&#xff1a;其它命令Pushgateway简介 Pushgateway 是 Prometheus 生态系统中的一个组件。主要特点是推送而非拉取&#xff1a;Prometheus 默认采用拉取&#xff08;pull&#xff09;模式收集指标&#xff0c;但 Pushgateway 允许…

      JAVA面试汇总(四)JVM(一)

      久违的重新写了一篇面试汇总的&#xff0c;关于JVM的一篇&#xff0c;一共三篇&#xff0c;今天写了第一篇&#xff0c;继续重新学习&#xff0c;重新卷起来&#xff0c;come on baby 1.什么情况下会触发类的初始化&#xff1f; &#xff08;1&#xff09;首先是类未被初始化时…

      Agent中的memory

      rag系列文章目录 文章目录rag系列文章目录前言一、Memory机制作用二、memory分类三、langgraph实践总结前言 众所周知&#xff0c;大模型是无状态的。但是基于大模型的agent一般是有状态的&#xff0c;也就是它有记忆功能。在AI Agent框架中&#xff0c;Memory机制是核心组件之…

      AI与IT从业者的未来:替代焦虑还是协作革命?

      ​​引言&#xff1a;技术渗透与核心命题​​2025年&#xff0c;人工智能技术已从实验室走向产业核心。国务院《关于深入实施“人工智能”行动的意见》推动AI在医疗、制造、金融等领域的规模化落地&#xff0c;全球AI应用用户规模突破2.3亿&#xff0c;生成式AI工具渗透率达16.…

      手机版碰一碰发视频系统批量剪辑功能开发,支持OEM贴牌

      引言在当今短视频盛行的时代&#xff0c;视频内容的快速生产与分享变得愈发重要。手机版碰一碰发视频系统&#xff0c;借助 NFC 等近场通信技术&#xff0c;实现了便捷的数据交互与视频分享&#xff0c;而在此基础上集成的批量剪辑功能&#xff0c;更是为内容创作者和商家带来了…

      Spring AMQP如何通过配置文件避免硬编码实现解耦

      在使用Spring AMQP基于注解声明监听者时&#xff0c;可通过抽取常量来避免硬编码&#xff1a;RabbitListener(bindings QueueBinding(exchange Exchange(MQConstant.USER_EXCHANGE),value Queue(MQConstant.USER_QUEUE),key MQConstant.USER_REDIS_BINDING))public void de…