尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Canvas 2D绘图指南:从基础到实战
Canvas 2Dcanvas是html中的一个标签用width和height属性来设置画布的宽高然后通过getContext方法来获取画笔对象。canvas idcanvas width500 height500/canvas通过getContext方法获取画笔对象canvas.getContext(2d)canvas 对象的获取方法(原生Html中)canvasEl document.getElementById(canvas)或者在vue3中一般采用 ElementRef 的方式获取:canvas refcanvasRef width500 height500/canvasconstcanvasRefrefHTMLCanvasElement|null(null);letcanvasEl!:HTMLCanvasElement;onMounted((){constcanvasElcanvasRef.value;});除此之外还有一种DOM结构的获取方式(Vue3.5以上版本)canvas refcanvasRef width500 height500/canvas// useTemplateRef 中的参数要与元素中的ref属性一致// 但DOM元素的变量名可以不用与ref一致例如当前命名为canvas// ref获取DOM结构需使用与ref一致的变量名// useTemplateRef 可自动推导DOM类型constcanvasuseTemplateRef(canvasRef);Canvas的基本操作Canvas(画布)的坐标原点在左上角,向右边为x轴,向下边为y轴绘制直线 / 折线 / 矩形绘制直线需要设置起点和终点然后调用lineTo方法绘制直线。constcanvasElrefHTMLCanvasElement|null(null);varctxcanvasEl.getContext(2d);// 开始绘制ctx.beginPath();// 移动画笔ctx.moveTo(0,0);// 绘制直线ctx.lineTo(500,500);// 轮廓ctx.stroke();// 绘制折线ctx.beginPath();ctx.moveTo(0,0);ctx.lineTo(250,500);ctx.lineTo(500,500);ctx.stroke();// 使用fill()方法填充ctx.fill();// 使用closePath()方法闭合路径ctx.closePath();// 绘制矩形ctx.beginPath();// 参数依次为(x,y,width,height)ctx.rect(0,0,100,100);ctx.stroke();// 上方方法等价于直接使用strokeRect()方法绘制矩形ctx.strokeRect(0,0,100,100);// 实心矩阵ctx.fillRect(0,0,100,100);// 清除矩形ctx.clearRect(0,0,50,50);绘制曲线绘制曲线需要设置曲线的起点和终点然后设置曲线的曲线控制点。圆弧ctx.arc(x,y,r,startAngle,endAngle,anticlockwise)x y 圆的坐标r 圆的半径startAngle 起点弧度endAngle 终点弧度anticlockwise 默认false 为顺时针true为逆时针一次贝塞尔曲线ctx.quadraticCurveTo(cpx,cpy,x,y)cpx,cpy 控制点的坐标x,y 终点的坐标二次贝塞尔曲线ctx.bezierCurveTo(cp1x,cp1y,cp2x,cp2y,x,y)cp1x,cp1y 控制点1的坐标cp2x,cp2y 控制点2的坐标x,y 终点的坐标绘制文本ctx.fillText(text,x,y)ctx.strokeText(text,x,y)text 文本内容x,y 文本的坐标绘制图片ctx.drawImage(img,sx,sy,sWidth,sHeight,dx,dty,dWidth,dHeight)img 图片对象sx,sy 图片的起始坐标sWidth,sHeight 图片的宽高dx,dy 图片的绘制坐标dWidth,dHeight 图片的绘制宽高绘制样式ctx.fillStylectx.strokeStyle其中颜色的表示方式有如下几种ctx.fillStyle redctx.fillStyle rgb(255,0,0)ctx.fillStyle rgba(255,0,0,0.5)ctx.fillStyle #ff0000默认fillStyle / strokeStyle 为黑色线宽ctx.lineWidth默认为1线结尾样式ctx.lineCap默认 buttbutt 默认以方形结尾round 圆形结尾加圆帽子square 方形结尾加帽子线结合样式ctx.lineJoin默认mitermiter 默认以尖角连接bevel 斜角连接round 圆角连接虚线ctx.setLineDash(dashArray)其中dashArray为数组数组中的前一个元素为虚线长度第二个元素为虚线间隔长度ctx.setLineDash([5,10])样式存档机制存档当前样式设置ctx.save()恢复最新存档样式ctx.restore()ctx.fillStyleblue;ctx.lineCapround;// 保存的是 blue, roundctx.save();ctx.fillStylered;ctx.lineCapsquare;ctx.fillRect(0,0,100,100);// 恢复的是 blue, roundctx.restore();变形ctx.translate(x,y)移动坐标原点ctx.rotate(angle)旋转画板坐标系angle为弧度制ctx.scale(x,y)缩放画板坐标系在变形操作之前可以使用save()方法保存当前的坐标系在变形操作之后可以使用restore()方法恢复Demo1画板 Nuxt4script setup langtsletpaintingfalse;letstartPoint:{x:number,y:number}|undefined;letctx:CanvasRenderingContext2D;// 画布上下文// 画布DOMconstcanvasuseTemplateRef(canvasRef);// 画板Card DOMconstcanvasCarduseTemplateRef(cardRef);// 画笔线宽constlineWidthref(1);// 开始绘画constdrawStart(e:MouseEvent){// 获取鼠标关于元素位置的坐标let{offsetX:x,offsetY:y}e;startPoint{x,y};paintingtrue;// 设置画笔线宽ctx.lineWidthlineWidth.value;};// 绘画持续constdrawMove(e:MouseEvent){let{offsetX:x,offsetY:y}e;letnewPoint{x,y};if(painting){ctx.beginPath();if(!startPoint)return;ctx.moveTo(startPoint.x,startPoint.y);ctx.lineTo(newPoint.x,newPoint.y);ctx.stroke();ctx.closePath();startPointnewPoint;}};// 停止绘画constdrawStop(e:MouseEvent){paintingfalse;startPointundefined;};// 切换画板constswitchCard(){if(!canvasCard.value)return;constcanvasCardElcanvasCard.value;canvasCardEl.style.visibilitygetComputedStyle(canvasCardEl).visibilityhidden?visible:hidden;}// 保存图片constsaveCanvas(isWhiteBG:booleanfalse){if(!canvas.value)return;constcanvasElcanvas.value;letexportCanvasElcanvasEl;// 创建一个临时的白色背景 canvas 用于保存的图片背景是白色if(isWhiteBG){consttempCanvasdocument.createElement(canvas);tempCanvas.widthcanvasEl.width;tempCanvas.heightcanvasEl.height;consttempCtxtempCanvas.getContext(2d);if(!tempCtx)return;tempCtx.fillStylewhite;tempCtx.fillRect(0,0,tempCanvas.width,tempCanvas.height);// 将原图片绘制到临时canvas上tempCtx.drawImage(canvasEl,0,0);exportCanvasEltempCanvas;}exportCanvasEl.toBlob((blob){if(!blob)return;// 创建一个URL对象consturlURL.createObjectURL(blob);// 定义一个a标签并且自启动点击constadocument.createElement(a);a.hrefurl;a.downloadcanvas;a.click();// 释放内存URL.revokeObjectURL(url);},image/jpeg)}onMounted((){// 获取画布DOMif(!canvas.value)return;constcanvasElcanvas.value;// 获取画布上下文constcontentcanvasEl.getContext(2d);// 缓存画布上下文if(!content)return;ctxcontent;// 同步缓冲区 实际显示大小canvasEl.widthcanvasEl.clientWidth;canvasEl.heightcanvasEl.clientHeight;// 如果窗口大小变化也随之变化constobservernewResizeObserver((){canvasEl.widthcanvasEl.clientWidth;canvasEl.heightcanvasEl.clientHeight;})observer.observe(canvasEl);// 设置画笔逻辑监听canvasEl.addEventListener(pointerdown,drawStart);canvasEl.addEventListener(pointermove,drawMove);canvasEl.addEventListener(pointerup,drawStop);})/scripttemplatedivclassHomedivclassmenudivclassbuttonclickswitchCard画板/divdivclassbuttonidbtn_resetclickctx.reset重置/divdivclassbuttonidbtn_saveclicksaveCanvas(true)保存/div/divdiv refcardRefclasscarddivclasstopdivclassbuttonstylebackground: #FE574A;/divdivclassbuttonstylebackground: #FBB124;/divdivclassbuttonstylebackground: #2AC037;/divdivclassLineWidthRangepLineWidth/pinput typerangemin1max10v-model.numberlineWidthp{{lineWidth}}/p/div/divcanvas refcanvasRefidcanvas/canvas/div/div/templatestyle langlessscopedmenu_bg:#fafafa80;// 背景图片 该网页会随机给出一张背景图home_bg:url(https://t.alcy.cc/pc)no-repeat center center/cover;.Home{width:100%;height:100vh;background:home_bg;// 防止元素溢出出现滚动条overflow:hidden;}.menu{width:100%;display:flex;background-color:menu_bg;backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);flex-direction:row;gap:25px;padding-left:60px;padding-top:5px;padding-bottom:10px;.button{cursor:pointer;:nth-child(1)::before{content:;}:nth-child(2)::before{content:;}:nth-child(3)::before{content:;}}}.card{width:600px;height:800px;display:flex;flex-direction:column;background-color:white;border-radius:10px;box-shadow:10px-10px 20pxrgba(0,0,0,0.5);margin:0auto;margin-top:50px;visibility:visible;.top{display:flex;gap:10px;padding-left:10px;.button{width:20px;height:20px;border-radius:50%;margin-top:10px}.LineWidthRange{display:flex;margin-top:8px;padding-left:10px;font-weight:bold;}}canvas{flex:1;width:100%;// 禁止触摸事件 移动端适配touch-action:none;}}/styleDemo2时钟 Nuxt4script setup langtsconstcanvasuseTemplateRef(canvasRef)constanimate(){constnownewDate();constsecnow.getSeconds()constminnow.getMinutes()lethournow.getHours()hourhour%12if(!canvas.value)return;constctxcanvas.value.getContext(2d)if(!ctx)return;ctx.save();ctx.clearRect(0,0,canvas.value.width,canvas.value.height);ctx.translate(canvas.value.width/2,canvas.value.height/2);ctx.rotate(-Math.PI/2);ctx.strokeStyleblack;ctx.lineCapround;constdrawTick(count:number,len:number,width:number){ctx.save();ctx.lineWidthwidth;for(leti0;icount;i){ctx.beginPath();ctx.rotate(Math.PI*2/count);ctx.moveTo(120-len,0);ctx.lineTo(120,0);ctx.stroke();}ctx.restore();}// 绘制时钟刻度drawTick(12,20,5);drawTick(60,10,3);// 时针ctx.save();ctx.rotate(hour*Math.PI/6min*Math.PI/360sec*Math.PI/21600)ctx.lineWidth14;ctx.beginPath();ctx.moveTo(-20,0);ctx.lineTo(80,0);ctx.stroke();ctx.restore();// 分针ctx.save();ctx.rotate(min*Math.PI/30sec*Math.PI/1800)ctx.lineWidth10;ctx.beginPath();ctx.moveTo(-28,0);ctx.lineTo(105,0);ctx.stroke();ctx.restore();// 秒针ctx.save();ctx.rotate(sec*Math.PI/30)ctx.lineWidth6;ctx.strokeStyle#D40000ctx.fillStyle#D40000ctx.beginPath();ctx.moveTo(-30,0);ctx.lineTo(110,0);ctx.stroke();ctx.arc(0,0,10,0,Math.PI*2,true);ctx.fill();ctx.restore();ctx.restore();requestAnimationFrame(animate)}onMounted((){if(!canvas.value)return;letcanvasElcanvas.value;// 同步缓冲区 实际显示大小canvasEl.widthcanvasEl.clientWidth;canvasEl.heightcanvasEl.clientHeight;constobservernewResizeObserver((){canvasEl.widthcanvasEl.clientWidth;canvasEl.heightcanvasEl.clientHeight;})observer.observe(canvasEl);// 开启动画requestAnimationFrame(animate);})/scripttemplatedivclassClockcanvas refcanvasRef/canvas/div/templatestyle langlessscoped.Clock{width:100%;height:100vh;display:flex;canvas{flex:1;width:100%;}}/style
RELATED

相关推荐

C++实现轻量级桌面截图工具:GDI/GDI+与Windows消息循环实战

C++实现轻量级桌面截图工具:GDI/GDI+与Windows消息循环实战

1. 项目概述:从零构建一个C桌面截图工具最近在整理一些桌面自动化工具时,发现一个高频需求:快速、精准地截取屏幕上的任意区域。市面上的截图工具虽然功能强大,但要么体积臃肿,要么带有我不需要的网络上传或编辑功能。…

📅 2026/8/24 8:24:32
户外型微电网协调控制系统柜设计要点:防护等级与环境适配

户外型微电网协调控制系统柜设计要点:防护等级与环境适配

在光伏电站、户外储能场站、园区分布式微网、乡村微电网等场景中,大量源网荷储控制设备需要长期部署在露天户外环境。不同于室内控制柜恒温、干燥、无尘的优越运行条件,户外型微电网协调控制系统柜常年面临日晒雨淋、高低温骤变、沙尘盐雾、凝露积水、紫…

📅 2026/8/24 8:24:32
C++图形渲染性能优化:从CPU-GPU协作瓶颈到实战技巧

C++图形渲染性能优化:从CPU-GPU协作瓶颈到实战技巧

1. 项目概述:直面图形渲染的性能痛点在任何一个需要实时图形交互的领域,无论是游戏开发、虚拟现实、数字孪生还是影视特效预览,性能问题永远是悬在开发者头顶的达摩克利斯之剑。你或许已经熟练掌握了C的语法,也了解了OpenGL或Vulk…

📅 2026/8/24 8:24:33
MORE NEWS

更多资讯

📰

Nacos 适配达梦和人大金仓:国产数据库方言与SPI插件落地

1. 为什么 Nacos 原生跑不起来达梦和人大金仓先说结论:Nacos 的配置中心和命名空间元数据是持久化到关系型数据库里的,而官方只给了 Derby 和 MySQL 两套方案。Derby 是内嵌的,只在单机演示场景下用;真正上生产基本都是 MySQL。问…

📰

超声图像CNN分类实践:甲状腺结节良恶性诊断的完整工程流程

简介:这是一份面向医学影像处理、深度学习与机器学习研究者的学术文献,聚焦基于卷积神经网络的甲状腺结节超声图像良恶性分类问题。资源为PDF格式,共1个文件,大小约2.87MB,内容源自《中国医学装备》2020年3月第17卷第3…

📰

Windows C盘空间清理:绕过磁盘清理的三大盲区

1. 别再点“磁盘清理”就完事了:C盘红了背后的真相与真实瓶颈你是不是也经历过这样的时刻:打开电脑,右下角弹出“C盘空间不足”的红色警告,点开“此电脑”,C盘图标赫然一片刺眼的红色;点开“磁盘清理”&…

📰

Storybook Args 组合实战:为组合式页面复用子组件 Stories 数据(全框架代码解析)

Storybook Args 组合实战:为组合式页面复用子组件 Stories 数据(全框架代码解析) 本篇指南以 page-story-with-args-composition.md 代码片段为骨架,解析 Storybook 中"Args 组合(args composition)&q…

📰

CSS height:100%失效的排查与解决方案:从vh到Flex/Grid

简介:一份专门讲解如何让div高度自适应浏览器高度的PDF技术笔记,面向前端开发者和对CSS百分比高度有困惑的初学者。内容从百分比高度的计算原理切入,明确指出仅给div设置height:100%通常无效,根因在于父级未定义具体高度&#xff…

📰

WPF UI 主题管理指南:用 IThemeService 三行代码搞定深浅色切换

WPF UI 主题管理指南:用 IThemeService 三行代码搞定深浅色切换 【免费下载链接】wpfui WPF UI provides the Fluent experience in your known and loved WPF framework. Intuitive design, themes, navigation and new immersive controls. All natively and effo…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬