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

uni-app实战教程 从0到1开发 画图软件 (橡皮擦)

一、本期内容简述

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.lryc.cn/news/620495.html

      相关文章:

    • PDF压缩原理详解:如何在不失真的前提下减小文件体积?
    • 高分辨率PDF压缩技巧:保留可读性的最小体积方案
    • 深入理解 RAG:检索增强生成技术详解
    • Hadoop面试题及详细答案 110题 (01-15)-- 基础概念与架构
    • gitlab仓库如何进行多人协作
    • 无人机探测器技术解析
    • GITLAB的Personal Access Tokens 和Project Access Tokens有什么区别
    • 走遍美国 10 Smell the Flowers 偷得浮生半日闲
    • 使用HalconDotNet实现异步多相机采集与实时处理
    • Java基础 8.14
    • 哈希表特性与unordered_map/unordered_set实现分析
    • 【159页PPT】智慧方案企业数字化转型流程体系建设与运营方案(附下载方式)
    • 群晖 NAS 影音访问:通过 cpolar 内网穿透服务实现 Nastool 远程管理
    • openvsx搭建私有插件仓库
    • Elasticsearch RBAC 配置:打造多租户环境的安全访问控制
    • Cherryusb UAC例程对接STM32 SAI播放音乐和录音(上)=>SAI+TX+RX+DMA的配置与音频回环测试
    • 深入详解C语言数组:承上启下——从C语言数组基础到数据结构衔接
    • 抓取系统升级,是优化还是重构更合适?
    • CSS aspect-ratio 属性
    • RTC时钟倒计时数码管同步显示实现(STC8)
    • 【基于个人博客系统】---测试报告
    • 当GitHub宕机时,我们如何协作?
    • GO学习记录五——数据库表的增删改查
    • 手写MyBatis第16弹:泛型魔法应用:MyBatis如何破解List的运行时类型
    • C++ 应用场景全景解析:从系统级到AI的跨越式演进
    • 分布式系统架构设计模式:从微服务到云原生
    • 河南萌新联赛2025第(五)场:信息工程大学”(补题)
    • DataHub OPC Gateway:实现OPC UA与OPC DA无缝集成的高性能网关
    • iOS App TF上架全流程实战 高效内测分发与IPA包管理
    • Boost库中Pool 基础内存池(boost::pool<>)的详细用法解析和实战应用