📅 我们继续 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"><!-- 🎨 画板区域 --><canvasref="canvasRef"class="aspect-square w-[800px] border-2 border-gray-300 bg-white"@mousedown="startDrawing"@mousemove="draw"@mouseup="stopDrawing"@mouseleave="stopDrawing"@contextmenu.prevent></canvas><!-- 🛠️ 工具栏 --><divclass="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.valuecanvas.width = canvas.offsetWidthcanvas.height = canvas.offsetHeightctx = canvas.getContext('2d')ctx.lineCap = 'round'ctx.lineJoin = 'round'
})// 开始绘制
const startDrawing = (e) => {if (e.button === 0) {isDrawing.value = truelastX = e.offsetXlastY = e.offsetY}
}// 绘制中
const draw = (e) => {if (!isDrawing.value) returnctx.beginPath()ctx.moveTo(lastX, lastY)ctx.lineTo(e.offsetX, e.offsetY)ctx.strokeStyle = brushColor.valuectx.lineWidth = brushSize.valuectx.stroke()lastX = e.offsetXlastY = 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.valuectx.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-centerFlexbox 居中对齐布局
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组件,一个很有意思的旋转加载动画。🚀

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

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

相关文章

Eureka、Nacos、LoadBalance、OpenFeign​之间的区别联系和协作 (附代码讲解)

这篇文章聊聊微服务里的这几个老伙计&#xff1a;Eureka、Nacos、LoadBalance、OpenFeign。咱们做微服务开发&#xff0c;总会跟这几个组件打交道&#xff1a;Eureka、Nacos、Spring Cloud LoadBalancer、OpenFeign。它们各司其职又互相配合&#xff0c;今天就把它们的关系、用…

JavaSE-继承

继承&#xff08;inheritance&#xff09;继承的意义我们首先来看下面两个类&#xff1a;public class Dog {public String name;public int age;public void eat(){System.out.println(this.name"正在吃饭");}public void bark(){System.out.println(this.name"…

第二届虚拟现实、图像和信号处理国际学术会议(VRISP 2025)

重要信息 官网&#xff1a;www.icvisp.net 时间&#xff1a;2025年8月1-3日 地点&#xff1a;中国-长沙 简介 近年来&#xff0c;虚拟现实技术取得了显著进步&#xff0c;与5G、云计算和物联网等新一代信息技术的融合加速&#xff0c;推动了其在硬件、软件和内容应用等方面…

SpringBoot+Mybatis+MySQL+Vue+ElementUI前后端分离版:整体布局、架构调整(二)

目录 一、前言 二、后端调整 1.实体类调整 2.菜单相关接口 3.用户相关接口 4.新增工具类 5.新增菜单树返回类 6.配置类、拦截器 三、前端调整 1.请求调整 2.页面布局、样式调整 1.user.vue 2.index.vue 3.请求拦截 四、开发过程中的问题 五、附&#xff1a…

vue3官方文档学习心得

这几天抽空把vue3的文档整个看了一遍。简介 | Vue.js 23年写过一个vue2的项目&#xff0c;24年写了一个vue3的项目&#xff0c;页面功能比较简单&#xff0c;用几个简单的API&#xff0c;watch、watchEffect、ref、reactive就能实现的业务功能。 写了几年的react的&#xff0…

Pycharm恢复默认设置,配置导致复制粘贴等不能使用

在file 种找到manage IDE settings在manage IDE settings中找到restore default settings

【王树森推荐系统】召回12:曝光过滤 Bloom Filter

概述 曝光过滤通常是在召回阶段做&#xff0c;具体的方法就是用 Bloom Filter 曝光过滤问题 如果用户看过某个物品&#xff0c;则不再把该物品曝光给用户。原因是同一个物品重复曝光给用户会损害用户体验&#xff0c;但也不是所有推荐系统都有曝光过滤&#xff0c;像 youtube 这…

基于STM32单片机的心率血氧监测系统设计(STM32代码编写+手机APP设计+PCB设计+Proteus仿真)

系列文章目录 文章目录 系列文章目录前言1 资料获取与演示视频1.1 资料介绍1.2 资料获取1.3 演示视频 2 系统框架3 硬件3.1 主控制器3.2 显示屏3.3 WIFI模块3.4心率血氧传感器 4 设计PCB4.1 安装下载立创EDA专业版4.2 画原理图4.4 使用嘉立创下单助手进行下单&#xff0c;打板。…

main(int argc,char **agrv)的含义

今天和大家讨论一个常见的但是不容易深入了解的知识点。那就是 main 函数声明中使用到的 argc 和 argv 的含义。通常我们写主函数的时候一般都是直接使用int main() 或者 void main() 来声明 main 函数。但是你知道吗&#xff1f;在c89/c99的语言标准中&#xff0c;main函数的声…

如何简单实现发版不影响客户使用?nginx负载

nginx负载发版不影响客户使用 1.需要二台服务器 2.二台服务器均是正式环境配置 3.服务器Nginx配置修改 发版顺序&#xff1a;先在服务器2发版&#xff0c;发布成功后&#xff0c;再改服务器Nginx配置&#xff0c;重新加载nginx&#xff1b;然后在服务器再发版&#xff0c;发布成…

qt笔记(1)——Qtablewidget使用

1.基础使用方法 &#xff08;略&#xff09; 2.坑和注意点 2.1 设置一个单元格的编辑属性 在代码中&#xff0c;想要修改一个单元格的编辑属性&#xff0c;需要对这个item的flags进行设置&#xff1b;注意对一个tablewidget的一个item成员进行设置后&#xff0c;进行一次编…

字符串的模糊匹配方法介绍

字符串的模糊匹配方法介绍 目录字符串的模糊匹配方法介绍一、编辑距离&#xff08;Levenshtein Distance&#xff09;复杂度分析二、Jaro-Winkler 距离复杂度分析三、最长公共子序列&#xff08;LCS&#xff09;复杂度分析四、模糊搜索&#xff08;Fuzzy Search&#xff09;复杂…

ActiveMQ在Spring Boot中的详细使用指南

📋 目录 🚀 ActiveMQ简介 什么是ActiveMQ? 核心概念 🏗️ 基础架构组件 📝 重要概念解释 ActiveMQ vs 其他消息中间件 🔧 环境搭建 1. ActiveMQ服务端安装 Docker方式(推荐初学者) 手动安装方式 2. 验证安装 访问Web管理界面 连接参数 测试连接 �…

二元一次方程

前言 最近刚学二元一次方程&#xff0c;想写一篇专栏熟悉一下本文写给初一的同学看&#xff0c;学过的就划了吧二元一次方程 两个未知数最高项次数为 111 次为整式方程二元一次方程的解不唯一&#xff0c;但是二元一次方程可以用一个未知数来表达另一个未知数eg:eg:eg: xy1x y…

AI编程的未来是智能体原生开发?

目录 前言 一、从“串行”到“并行”&#xff1a;什么是智能体原生开发&#xff1f; 1.1 传统模式&#xff08;串行思维&#xff09; 1.2 智能体原生模式&#xff08;并行思维&#xff09; 二、程序员的新角色&#xff1a;从代码手艺人到系统思想家 三、软件开发的终局&a…

【牛客刷题】小红的与运算

文章目录 一、题目介绍1.1 题目描述1.2 输入描述1.3 输出描述1.4 示例二、 解题思路2.1 核心算法设计2.2 性能优化关键2.3 算法流程图三、解法实现3.1 解法一:基础实现3.1.1 初级版本分析3.2 解法二:优化版本(推荐)3.2.1 优化版本分析四、总结与拓展4.1 关键优化技术4.2 算…

spring中 方法上@Transation实现原理

Spring中Transactional注解方法实现原理Spring的Transactional注解在方法级别实现事务管理的原理主要基于动态代理和拦截器机制&#xff0c;以下是其核心实现流程&#xff1a;1. 代理创建阶段当Spring容器启动时&#xff0c;会为带有Transactional注解的类创建代理对象&#xf…

qt-C++语法笔记之Stretch与Spacer的关系分析

qt-C语法笔记之Stretch与Spacer的关系分析 code review! 文章目录qt-C语法笔记之Stretch与Spacer的关系分析1. Stretch&#xff08;拉伸因子&#xff09;2. Horizontal Spacer 和 Vertical Spacer3. Stretch 和 Spacer 的关系4. 实际应用中的选择5. 注意事项6. 代码与 Qt Desig…

Qwen3技术综述

1. 引入 2025年5月&#xff0c;qwen推出了旗舰模型&#xff08;flagship model&#xff09;Qwen3-235B-A22B。并以Apache 2.0版权发布&#xff08;可自由商业使用&#xff0c;修改代码和商用要包含原始版权&#xff09;。本文对其技术报告中提到的数据处理技术与模型结构进行综…

[特殊字符] Excel 读取收件人 + Outlook 批量发送带附件邮件 —— Python 自动化实战

许多公司定期需要将不同部门或客户的报告发送给指定人员。手动操作容易出错、耗时且繁琐。今天这篇文章教你如何利用 Python 实现&#xff1a; &#x1f9e9; 从 Excel 中读取“收件人 抄送人 附件文件路径”&#xff1b; &#x1f4e4; 使用 win32com.client 调用 Outlook …