尧图网络 高端网站定制 · 原创设计
免费咨询热线
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/10/26 20:11:06
户外型微电网协调控制系统柜设计要点:防护等级与环境适配

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

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

📅 2026/10/26 16:05:01
C++图形渲染性能优化:从CPU-GPU协作瓶颈到实战技巧

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

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

📅 2026/11/7 12:05:41
MORE NEWS

更多资讯

📰

开封劳务班组负责人真实经历:考电工证的电线怎么考试?怕考不过白交培训费?

开封劳务班组负责人真实经历:考电工证的电线怎么考试?怕考不过白交培训费? 你是不是也遇到过这种情况:花了几千块报了个电工证培训班,结果考试时发现题目根本看不懂,白白浪费了钱?别急,我来给你支招,这可不是我一个人的经历,而是我们开封劳务班组里…

📰

开封电工证强电和弱电在哪考?新手必看报名流程全解析

开封电工证强电和弱电在哪考?新手必看报名流程全解析 你是不是也这样,刚从工地干完活就接到老板的电话,说新项目马上要开工,但是电工证还没办下来,现在急需上岗?别急,电工证强电和弱电在哪考?这个问题是很多刚入行的朋友第一个要弄清楚的。尤其是在开…

📰

电工证实操考试怕挂科?昆山大明电工证怎么报名全攻略

电工证实操考试怕挂科?昆山大明电工证怎么报名全攻略 实操考试心里没底怕挂科,这是很多准备报考昆山大明电工证的学员最担心的问题。尤其在特种作业领域,电工证不仅关系到能否顺利上岗,更是关系到人身安全与作业规范的重要凭证。如果你正在考虑报名昆山大…

📰

郫都区特种工考证培训:工地太忙根本没时间复习考试?真实经历告诉你怎么高效备考

郫都区特种工考证培训:工地太忙根本没时间复习考试?真实经历告诉你怎么高效备考 工地太忙根本没时间复习考试,这几乎是每个一线施工人员的真实经历。尤其是像电工作业、焊接与热切割作业、高处作业这些需要持证上岗的特种作业岗位,考证成了“刚需”,可偏…

📰

盐城市学高压电工证千万别踩坑,急用证的看过来

盐城市学高压电工证千万别踩坑,急用证的看过来 你是不是正在找高压电工证,但不知道怎么报名?是不是因为证到期了,生怕耽误了工作?盐城市学高压电工证确实是个刚需,但 千万别踩坑 ,选错了地方,不仅浪费钱,还可能耽误你找工作的关键时间。…

📰

工建特种作业资格证保姆级教程:怕考不过白交培训费?这篇全搞定

工建特种作业资格证保姆级教程:怕考不过白交培训费?这篇全搞定 怕考不过白交培训费?别慌!工建特种作业资格证备考其实有套路,只要掌握好方法,一次通过不是梦。今天这波保姆级教程,帮你从零到有,轻松搞定工建特种作业资格证,省心省力更省钱。…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

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

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

📞 💬