娄底网站建设工作室,阿里云虚拟主机和云服务器的区别,白酒网站源码,大量word发布wordpress#x1f4c5; 我们继续 50 个小项目挑战#xff01;—— DrawingApp 组件 仓库地址#xff1a;https://gitee.com/hhm-hhm/50days50projects.git 构建一个简单的在线画板应用。用户可以自由绘制图形、调节画笔粗细、选择颜色#xff0c;并支持一键清空画布。
#x1f30… 我们继续 50 个小项目挑战—— DrawingApp 组件仓库地址https://gitee.com/hhm-hhm/50days50projects.git构建一个简单的在线画板应用。用户可以自由绘制图形、调节画笔粗细、选择颜色并支持一键清空画布。 组件目标创建一个固定尺寸的画布区域支持鼠标点击拖动进行绘画提供按钮控制画笔粗细 / -使用原生input typecolor选择画笔颜色提供“清空”按钮重置画布内容使用 TailwindCSS 快速构建现代 UI 界面 DrawingApp.tsx组件实现import React, { useRef, useEffect, useState } from react const DrawingApp: React.FC () { // Refs const canvasRef useRefHTMLCanvasElement(null) const isDrawingRef useRef(false) // 使用 ref 避免 draw 闭包问题 const lastXRef useRef(0) const lastYRef useRef(0) const ctxRef useRefCanvasRenderingContext2D | null(null) // State const [brushSize, setBrushSize] useStatenumber(5) const [brushColor, setBrushColor] useStatestring(#000000) // 初始化画布 useEffect(() { const canvas canvasRef.current if (!canvas) return // 设置画布尺寸为显示尺寸避免模糊 const dpr window.devicePixelRatio || 1 const rect canvas.getBoundingClientRect() canvas.width rect.width * dpr canvas.height rect.height * dpr const ctx canvas.getContext(2d) if (!ctx) return // 缩放上下文以适配高清屏 ctx.scale(dpr, dpr) ctx.lineCap round ctx.lineJoin round ctxRef.current ctx }, []) // 开始绘制仅左键 const startDrawing (e: React.MouseEventHTMLCanvasElement) { if (e.button ! 0) return // 只响应左键 const canvas canvasRef.current if (!canvas) return const rect canvas.getBoundingClientRect() const x e.clientX - rect.left const y e.clientY - rect.top lastXRef.current x lastYRef.current y isDrawingRef.current true } // 绘制中 const draw (e: React.MouseEventHTMLCanvasElement) { if (!isDrawingRef.current || !ctxRef.current) return const canvas canvasRef.current if (!canvas) return const rect canvas.getBoundingClientRect() const x e.clientX - rect.left const y e.clientY - rect.top const ctx ctxRef.current ctx.beginPath() ctx.moveTo(lastXRef.current, lastYRef.current) ctx.lineTo(x, y) ctx.strokeStyle brushColor ctx.lineWidth brushSize ctx.stroke() lastXRef.current x lastYRef.current y } // 停止绘制 const stopDrawing () { isDrawingRef.current false } // 控制画笔大小 const increaseBrushSize () { setBrushSize((prev) Math.min(prev 1, 50)) } const decreaseBrushSize () { setBrushSize((prev) Math.max(prev - 1, 1)) } // 清空画布 const clearCanvas () { const canvas canvasRef.current const ctx ctxRef.current if (!canvas || !ctx) return ctx.clearRect( 0, 0, canvas.width / (window.devicePixelRatio || 1), canvas.height / (window.devicePixelRatio || 1) ) } return ( div classNameflex min-h-screen items-center justify-center bg-gray-900 div classNameflex flex-col items-center {/* 画板区域 */} canvas ref{canvasRef} classNameaspect-square w-[800px] border-2 border-gray-300 bg-white onMouseDown{startDrawing} onMouseMove{draw} onMouseUp{stopDrawing} onMouseLeave{stopDrawing} onContextMenu{(e) e.preventDefault()} // 禁用右键菜单 / {/* ️ 工具栏 */} div classNamemt-4 flex w-[800px] items-center justify-between rounded-lg bg-gray-800 p-3 {/* 粗细调节 */} div classNameflex items-center button onClick{decreaseBrushSize} classNamerounded p-2 text-white hover:bg-gray-700 disabled{brushSize 1} - /button span classNamemx-3 text-white{brushSize}/span button onClick{increaseBrushSize} classNamerounded p-2 text-white hover:bg-gray-700 disabled{brushSize 50} /button /div {/* 颜色选择 */} input typecolor value{brushColor} onChange{(e) setBrushColor(e.target.value)} classNameh-10 w-10 cursor-pointer appearance-none rounded border-0 bg-transparent / {/* 清空画布 */} button onClick{clearCanvas} classNamerounded bg-red-600 p-2 text-white hover:bg-red-700 清空 /button /div /div div classNamefixed right-20 bottom-5 text-2xl text-red-500CSDNHao_Harrision/div /div ) } export default DrawingApp 关键技术说明1.使用useRef管理可变状态isDrawing,lastX,lastY使用ref而非state避免draw函数因闭包捕获旧值。ctx也用ref缓存避免重复获取。2.高 DPI 屏幕适配防模糊获取devicePixelRatio并放大 canvas 尺寸同时缩放绘图上下文ctx.scale(dpr, dpr)清空时需除以dpr得到逻辑尺寸。3.坐标计算使用getBoundingClientRect()获取 canvas 位置clientX/Y - rect.left/top得到相对于 canvas 的坐标。4.事件处理onMouseDown/onMouseMove等使用 React 事件系统onContextMenu阻止默认右键菜单。5.无障碍与 UX按钮添加disabled状态当画笔已达最小/最大颜色选择器移除浏览器默认样式appearance-noneborder-0。 可选增强建议功能实现方式移动端支持添加onTouchStart/onTouchMove等事件撤销功能保存 canvas 快照到栈中导出图片使用canvas.toDataURL()自定义背景在clearCanvas中填充背景色或图案 TailwindCSS 样式重点讲解 TailwindCSS 样式说明类名作用min-h-screen设置最小高度为视口高度items-center,justify-centerFlexbox 居中对齐布局bg-gray-900设置深色背景aspect-square保持画布为正方形比例w-[800px]固定宽度为 800pxborder-2,border-gray-300边框样式bg-white画布背景色rounded-lg,p-3工具栏圆角与内边距hover:bg-gray-700按钮悬停变色ext-white白色文字cursor-pointer鼠标悬停变为手型h-10,w-10设置颜色选择器大小 路由组件 常量定义router/index.tsx中children数组中添加子路由{ path: /, element: App /, children: [ ... { path: /DrawingApp, lazy: () import(/projects/DrawingApp.tsx).then((mod) ({ Component: mod.default, })), }, ], },constants/index.tsx 添加组件预览常量import demo22Img from /assets/pic-demo/demo-22.png 省略部分.... export const projectList: ProjectItem[] [ 省略部分.... { id: 22, title: DrawingApp, image: demo22Img, link: DrawingApp, }, 小结你可以进一步扩展此组件的功能例如✅ 支持保存画布内容为图片canvas.toDataURL()✅ 添加撤销/重做功能记录历史快照✅ 支持触控设备如 iPad 或触摸屏✅ 封装为独立组件支持 props 传入默认颜色或大小 明日预告 我们将完成KineticLoader组件一个很有意思的旋转加载动画。原文链接https://blog.csdn.net/qq_44808710/article/details/149150719每天造一个轮子码力暴涨不是梦