当前位置:首页 > VUE

用vue实现手绘功能

2026-02-21 17:41:22VUE

使用 Vue 实现手绘功能

核心思路

基于 HTML5 的 Canvas API 结合 Vue 的响应式特性,通过监听鼠标或触摸事件实现手绘功能。需要处理画笔移动轨迹、颜色、粗细等属性,并提供清除画布功能。

基础实现代码

以下是一个完整的 Vue 3 组件示例(Composition API):

用vue实现手绘功能

<template>
  <div class="drawing-board">
    <canvas 
      ref="canvasRef"
      @mousedown="startDrawing"
      @mousemove="draw"
      @mouseup="stopDrawing"
      @mouseleave="stopDrawing"
      @touchstart="handleTouchStart"
      @touchmove="handleTouchMove"
      @touchend="handleTouchEnd"
    ></canvas>
    <div class="controls">
      <input type="color" v-model="drawColor">
      <input type="range" v-model="lineWidth" min="1" max="50">
      <button @click="clearCanvas">清除</button>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'

const canvasRef = ref(null)
const isDrawing = ref(false)
const drawColor = ref('#000000')
const lineWidth = ref(5)

let ctx = null

onMounted(() => {
  const canvas = canvasRef.value
  ctx = canvas.getContext('2d')
  resizeCanvas()
  window.addEventListener('resize', resizeCanvas)
})

function resizeCanvas() {
  const canvas = canvasRef.value
  canvas.width = canvas.offsetWidth
  canvas.height = canvas.offsetHeight
  ctx.lineJoin = 'round'
  ctx.lineCap = 'round'
}

function startDrawing(e) {
  isDrawing.value = true
  draw(e)
}

function draw(e) {
  if (!isDrawing.value) return

  const rect = canvasRef.value.getBoundingClientRect()
  const x = e.clientX - rect.left
  const y = e.clientY - rect.top

  ctx.strokeStyle = drawColor.value
  ctx.lineWidth = lineWidth.value

  ctx.lineTo(x, y)
  ctx.stroke()
  ctx.beginPath()
  ctx.moveTo(x, y)
}

function stopDrawing() {
  isDrawing.value = false
  ctx.beginPath()
}

function handleTouchStart(e) {
  e.preventDefault()
  const touch = e.touches[0]
  const mouseEvent = new MouseEvent('mousedown', {
    clientX: touch.clientX,
    clientY: touch.clientY
  })
  canvasRef.value.dispatchEvent(mouseEvent)
}

function handleTouchMove(e) {
  e.preventDefault()
  const touch = e.touches[0]
  const mouseEvent = new MouseEvent('mousemove', {
    clientX: touch.clientX,
    clientY: touch.clientY
  })
  canvasRef.value.dispatchEvent(mouseEvent)
}

function handleTouchEnd() {
  const mouseEvent = new MouseEvent('mouseup')
  canvasRef.value.dispatchEvent(mouseEvent)
}

function clearCanvas() {
  ctx.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height)
}
</script>

<style scoped>
.drawing-board {
  width: 100%;
  height: 500px;
  position: relative;
}

canvas {
  border: 1px solid #ccc;
  background-color: white;
  width: 100%;
  height: 100%;
  touch-action: none;
}

.controls {
  margin-top: 10px;
  display: flex;
  gap: 10px;
  align-items: center;
}
</style>

功能扩展建议

保存绘图结果

用vue实现手绘功能

function saveAsImage() {
  const dataURL = canvasRef.value.toDataURL('image/png')
  const link = document.createElement('a')
  link.download = 'drawing.png'
  link.href = dataURL
  link.click()
}

撤销/重做功能

const history = ref([])
const historyIndex = ref(-1)

function saveState() {
  historyIndex.value++
  history.value = history.value.slice(0, historyIndex.value)
  history.value.push(canvasRef.value.toDataURL())
}

function undo() {
  if (historyIndex.value <= 0) return
  historyIndex.value--
  const img = new Image()
  img.onload = () => ctx.drawImage(img, 0, 0)
  img.src = history.value[historyIndex.value]
}

function redo() {
  if (historyIndex.value >= history.value.length - 1) return
  historyIndex.value++
  const img = new Image()
  img.onload = () => ctx.drawImage(img, 0, 0)
  img.src = history.value[historyIndex.value]
}

画笔类型扩展

const brushType = ref('pen')

function setBrush(type) {
  brushType.value = type
  switch(type) {
    case 'marker':
      ctx.globalCompositeOperation = 'multiply'
      break
    case 'eraser':
      ctx.globalCompositeOperation = 'destination-out'
      break
    default:
      ctx.globalCompositeOperation = 'source-over'
  }
}

性能优化技巧

  • 使用 requestAnimationFrame 优化绘制性能
  • 对于复杂绘图,考虑使用离屏 Canvas 进行缓冲
  • 实现节流处理高频绘制事件
  • 移动端适配时注意 passive event listeners

注意事项

  1. Canvas 尺寸必须通过属性设置(非 CSS),否则会出现拉伸变形
  2. 触摸事件需要正确处理 preventDefault 以避免页面滚动
  3. 高 DPI 设备需要处理像素比问题:
    function resizeCanvas() {
    const canvas = canvasRef.value
    const ratio = window.devicePixelRatio || 1
    canvas.width = canvas.offsetWidth * ratio
    canvas.height = canvas.offsetHeight * ratio
    ctx.scale(ratio, ratio)
    ctx.lineJoin = 'round'
    ctx.lineCap = 'round'
    }

标签: 手绘功能
分享给朋友:

相关文章

php实现打印功能

php实现打印功能

PHP实现打印功能的方法 在PHP中实现打印功能可以通过多种方式完成,包括直接输出到浏览器、生成PDF文件、调用打印机接口等。以下是几种常见的方法: 直接输出HTML内容 PHP可以通过echo或p…

vue 实现收藏功能

vue 实现收藏功能

实现收藏功能的基本思路 在Vue中实现收藏功能通常涉及前端交互与后端数据存储的结合。核心逻辑包括:用户点击收藏按钮时切换状态,并通过API将状态同步到后端数据库。 前端组件实现 创建收藏按钮组件,使…

vue实现注册功能

vue实现注册功能

实现注册功能的基本步骤 在Vue中实现注册功能通常需要结合前端表单和后端API交互。以下是常见的实现方式: 创建注册表单组件 <template> <div class="re…

vue实现名字查找功能

vue实现名字查找功能

实现名字查找功能 在Vue中实现名字查找功能,可以通过多种方式完成。以下是几种常见的实现方法: 使用v-model和computed属性 创建一个输入框,使用v-model绑定输入的值,通过comp…

vue功能实现难点

vue功能实现难点

Vue 功能实现的常见难点 响应式系统深度监听 Vue 的响应式系统基于 Object.defineProperty(Vue 2)或 Proxy(Vue 3),但对嵌套对象或数组的深层属性变化需要额外…

vue实现预览功能

vue实现预览功能

Vue 实现文件预览功能 使用 FileReader 实现本地文件预览 通过 HTML5 的 FileReader API 可以读取本地文件内容并显示预览。创建一个文件上传组件,监听 change…