📅 我们继续 50 个小项目挑战!—— DrawingApp组件

仓库地址:https://github.com/SunACong/50-vue-projects

项目预览地址:https://50-vue-projects.vercel.app/

在这里插入图片描述


使用 Vue 3 的 Composition API(<script setup> 和 HTML5 <canvas> 元素,结合 TailwindCSS 构建一个简单的在线画板应用。用户可以自由绘制图形、调节画笔粗细、选择颜色,并支持一键清空画布。

🎯 组件目标

  • 创建一个固定尺寸的画布区域
  • 支持鼠标点击拖动进行绘画
  • 提供按钮控制画笔粗细(+ / -)
  • 使用原生 <input type="color"> 选择画笔颜色
  • 提供“清空”按钮重置画布内容
  • 使用 TailwindCSS 快速构建现代 UI 界面

⚙️ 技术实现点

技术点 描述
Vue 3 Composition API (<script setup>) 使用响应式变量管理画笔状态
ref 响应式变量 控制画笔大小、颜色、是否正在绘图等
onMounted 生命周期钩子 初始化 Canvas 上下文
canvas.getContext('2d') 获取 2D 渲染上下文用于绘图
鼠标事件监听 包括 mousedownmousemovemouseupmouseleave
TailwindCSS 布局类 实现居中布局、按钮样式、工具栏设计

🧱 组件实现

模板结构 <template>

<template>
    <div class="flex min-h-screen items-center justify-center bg-gray-900">
        <div class="flex flex-col items-center">
            <!-- 🎨 画板区域 -->
            <canvas
                ref="canvasRef"
                class="aspect-square w-[800px] border-2 border-gray-300 bg-white"
                @mousedown="startDrawing"
                @mousemove="draw"
                @mouseup="stopDrawing"
                @mouseleave="stopDrawing"
                @contextmenu.prevent></canvas>

            <!-- 🛠️ 工具栏 -->
            <div
                class="mt-4 flex w-[800px] items-center justify-between rounded-lg bg-gray-800 p-3">
                <!-- 粗细调节 -->
                <div class="flex items-center">
                    <button
                        @click="decreaseBrushSize"
                        class="rounded p-2 text-white hover:bg-gray-700">
                        -
                    </button>
                    <span class="mx-3 text-white">{{ brushSize }}</span>
                    <button
                        @click="increaseBrushSize"
                        class="rounded p-2 text-white hover:bg-gray-700">
                        +
                    </button>
                </div>

                <!-- 🎨 颜色选择 -->
                <input type="color" v-model="brushColor" class="h-10 w-10 cursor-pointer" />

                <!-- 清空画布 -->
                <button
                    @click="clearCanvas"
                    class="rounded bg-red-600 p-2 text-white hover:bg-red-700">
                    清空
                </button>
            </div>
        </div>
    </div>
</template>

脚本逻辑 <script setup>

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

// 🖼️ 画布引用
const canvasRef = ref(null)
// ✍️ 是否正在绘图
const isDrawing = ref(false)
// 画笔粗细 & 颜色
const brushSize = ref(5)
const brushColor = ref('#000000')

let lastX = 0
let lastY = 0
let ctx = null

// 初始化画布
onMounted(() => {
    const canvas = canvasRef.value
    canvas.width = canvas.offsetWidth
    canvas.height = canvas.offsetHeight

    ctx = canvas.getContext('2d')
    ctx.lineCap = 'round'
    ctx.lineJoin = 'round'
})

// 开始绘制
const startDrawing = (e) => {
    if (e.button === 0) {
        isDrawing.value = true
        lastX = e.offsetX
        lastY = e.offsetY
    }
}

// 绘制中
const draw = (e) => {
    if (!isDrawing.value) return

    ctx.beginPath()
    ctx.moveTo(lastX, lastY)
    ctx.lineTo(e.offsetX, e.offsetY)
    ctx.strokeStyle = brushColor.value
    ctx.lineWidth = brushSize.value
    ctx.stroke()

    lastX = e.offsetX
    lastY = e.offsetY
}

// 停止绘制
const stopDrawing = () => {
    isDrawing.value = false
}

// 控制画笔大小
const increaseBrushSize = () => {
    if (brushSize.value < 50) brushSize.value += 1
}
const decreaseBrushSize = () => {
    if (brushSize.value > 1) brushSize.value -= 1
}

// 清除画布
const clearCanvas = () => {
    const canvas = canvasRef.value
    ctx.clearRect(0, 0, canvas.width, canvas.height)
}
</script>

自定义样式 <style scoped>

<style scoped>
[draggable='true'] {
    transition: all 0.2s ease;
}
</style>

🔍 重点效果实现

✅ 鼠标事件绑定机制

我们通过以下事件监听器实现了完整的绘图交互:

事件名 触发时机 作用
mousedown 鼠标按下时 标记为开始绘制,并记录初始坐标
mousemove 鼠标移动时 如果处于绘制状态,则根据当前坐标绘制线条
mouseup 鼠标释放时 结束绘制
mouseleave 鼠标移出画布 安全结束绘制,防止悬停后继续绘制
contextmenu.prevent 右键菜单阻止 防止右键弹出默认菜单影响操作

💡 Canvas 绘图基础设置

ctx.lineCap = 'round'
ctx.lineJoin = 'round'

这两行代码设置了线条两端为圆角,使绘制效果更自然流畅。

🎨 动态画笔颜色与粗细

我们通过响应式变量 brushColorbrushSize 来动态更新画笔属性:

ctx.strokeStyle = brushColor.value
ctx.lineWidth = brushSize.value

这样就能实时反映用户的选择变化。

🗑️ 清空画布功能

ctx.clearRect(0, 0, canvas.width, canvas.height)

调用 clearRect 方法一次性清除整个画布,实现“清空”功能。


🎨 TailwindCSS 样式重点讲解

类名 作用
min-h-screen 设置最小高度为视口高度
items-center, justify-center Flexbox 居中对齐布局
bg-gray-900 设置深色背景
aspect-square 保持画布为正方形比例
w-[800px] 固定宽度为 800px
border-2, border-gray-300 边框样式
bg-white 画布背景色
rounded-lg, p-3 工具栏圆角与内边距
hover:bg-gray-700 按钮悬停变色
text-white 白色文字
cursor-pointer 鼠标悬停变为手型
h-10, w-10 设置颜色选择器大小

这些 TailwindCSS 类帮助我们快速构建了一个美观、响应式的画板界面。


📁 常量定义 + 组件路由

constants/index.js 添加组件预览常量:

{
        id: 22,
        title: 'Drawing App',
        image: 'https://50projects50days.com/img/projects-img/22-drawing-app.png',
        link: 'DrawingApp',
    },

router/index.js 中添加路由选项:

{
        path: '/DrawingApp',
        name: 'DrawingApp',
        component: () => import('@/projects/DrawingApp.vue'),
    },

📁 扩展建议

你可以进一步扩展此组件的功能,例如:

  • ✅ 支持保存画布内容为图片(canvas.toDataURL()
  • ✅ 添加撤销/重做功能(记录历史快照)
  • ✅ 支持触控设备(如 iPad 或触摸屏)
  • ✅ 封装为独立组件(支持 props 传入默认颜色或大小)

🏁 总结

基于 Vue 3 和 Canvas 的画板组件不仅实现了基本的绘图功能,还提供了友好的交互体验和清晰的视觉反馈。无论是作为学习项目还是实际开发中的组件模块,都非常实用。

感谢阅读,欢迎点赞、收藏和分享 😊


👉 下一篇,我们将完成KineticLoader组件,一个很有意思的旋转加载动画。🚀

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐