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

发布于:2025-08-16 ⋅ 阅读:(18) ⋅ 点赞:(0)

一、本期内容简述

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()) return
  
  isDrawing.value = true
  const 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 = true
    eraseAtPoint(point)
  }
}

其中eraseAtPoint

// 跟踪最后一个橡皮擦操作
let lastEraserOperation = null
let eraserTimeout = null

// 在指定点进行擦除
const eraseAtPoint = (point) => {
  const size = eraserSize.value
  const 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()) return
  
  const 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) return
  
  if (currentMode.value === 'draw' && currentPath.value.length > 0) {
    // 保存完成的绘图路径
    drawingHistory.value.push({
      type: 'draw',
      points: [...currentPath.value],
      color: currentColor.value,
      size: lineSize.value
    })
  }
  
  isDrawing.value = false
  currentPath.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;
  }
}


      网站公告

      今日签到

      点亮在社区的每一天
      去签到