不知道你有没有遇到过这种场面:明明代码逻辑一点问题都没有,画出来的图形却总是“丑得让人不想多看一眼”。线条歪歪扭扭、颜色死板、阴影生硬、文字对不齐,又或者图形一多就卡得掉帧。这些问题的根源,十有八九不是你逻辑的问题,而是对HTML和JavaScript里Canvas这套绘图样式系统不够熟。作为一个用Canvas做过可视化大屏、做过小游戏、也做过数据标注工具的人,我可以负责任地说:Canvas的绘图API虽然只有几十个属性和方法,但真正把它们组合出彩的人并不多。这玩意儿就是一个“会者不难,难者不会”的典型。
这篇指南就是冲着“绘图样式”这个方向去的。我会把fillStyle、strokeStyle、lineWidth、shadow、渐变、图案、合成模式、变换样式这些核心知识点全部过一遍,并且在不同场景下给出可以直接抄的代码和参数推荐。适合的人群很明确:正在学Canvas基础、或者做了一半项目突然发现自己对样式控制力不足的前端开发者。不用你有多深的数学功底,也不用你背API文档,跟着思路走一遍,下次画图心里就有底了。
1. 为什么Canvas绘图样式值得单独写一篇
1.1 样式系统是Canvas的“皮肤制造机”
很多人初学Canvas时,注意力都放在“画什么”上面,画矩形、画圆、画路径、画图片,每个API都要亲手敲一遍才放心。但真正决定画面最终效果的是“怎么画”,也就是样式。Canvas的绘图样式决定了线条的粗细和端点形状、填充的颜色和透明度、阴影的模糊与偏移、文字的字体和对齐方式,甚至决定了多个图形之间的叠加关系。这些单独看起来都不难,但组合起来会产生巨大的视觉差异。
举个例子,同样是画一条折线,你把lineCap从butt换成round,再把lineJoin从miter换成bevel,整张图的气质立刻就不一样了。再来一个shadowBlur,线条就像加了发光滤镜一样。这种“四两拨千斤”的效果,在UI图表、数据可视化、小游戏里都是直接拉开差距的地方。
1.2 整篇文章的脉络和掌握方法
这篇文章不是API手册的复述,而是我按实际开发中“先搭基础样式、再做进阶效果、最后处理复杂组合”的顺序来组织的。先从最常用的颜色、线条、阴影、文本四类基础样式讲起,再进入渐变、图案、合成这些需要一点空间想象力的进阶样式,接着聊变换样式和像素级处理,最后给一个真实的“数据仪表盘卡片”实战案例,再做一份常见问题和避坑清单。
我的建议是:你手头最好有一个空白的html文件,把文章里的示例代码复制进去,边读边看效果。Canvas的样式参数特别吃“手感”,同一个shadowBlur在不同尺寸的画布上感觉完全不一样,只看不练很难形成记忆。
2. 核心样式属性逐个拆解
2.1 颜色样式:fillStyle与strokeStyle
fillStyle和strokeStyle是所有绘图操作中最常用到的两个参数,它们分别决定填充颜色和描边颜色。这两个属性支持三种赋值形式:CSS颜色字符串、渐变色对象、图案对象。很多人只用了第一种字符串形式,其实后两种才是进阶玩法的关键。
先说说最基础的字符串形式。它接受所有合法的CSS颜色值,比如#ff6600、rgb(255, 102, 0)、rgba(255, 102, 0, 0.5)、hsl(30, 100%, 50%)。我在实际项目里比较推荐用rgba和hsl,因为调透明度、做色彩体系时非常灵活。特别是hsl,改色相做系列配色时不用来回换算RGB,省心得多。
ctx.fillStyle = 'rgba(66, 135, 245, 0.4)'; ctx.strokeStyle = 'hsl(210, 80%, 60%)';渐变对象和图案对象我会在第三章详细讲,因为它们的坐标系和普通颜色完全不一样,牵涉到Canvas的变换矩阵,需要单独建立认知模型。这里先记住一点:一旦把fillStyle设置为渐变或图案,后续所有用这个样式的图形都会被“贴上”这份渐变或图案,直到你重新赋值。
注意:
fillStyle和strokeStyle的默认值都是#000000,也就是纯黑色。很多人画的线条“看不见”,其实是忘了设置描边颜色,而黑色线条在深色背景上自然看不到。先检查这个,再查别的。
2.2 线条样式:lineWidth、lineCap、lineJoin与miterLimit
线条样式是很多Canvas新手最容易忽略的一组属性,但它对图形精致的贡献率极高。lineWidth不用多解释,是线条的宽度,单位是像素。真正考验经验的是lineCap和lineJoin的组合选择。
lineCap控制线段端点的样式,有三个可选值:butt(平直、不延伸)、round(圆形端点、超出半个线宽)、square(方形端点、同样超出半个线宽)。lineJoin控制两条线段连接处的样式,也有三个值:miter(尖角)、round(圆角)、bevel(斜角)。做地图路径、折线图时,lineJoin设为round能让拐角变得柔和,避免出现突兀的尖刺;做表格边框、建筑图纸时,butt和miter更合适,线条干净利落。
miterLimit是个容易被忽视的参数。它的默认值是10,控制的是miter连接方式的“尖角极限”。当两条线段夹角特别小、形成的尖角过长时,Canvas会自动把尖角切平,变成bevel样式。如果你在做折线图时发现某些拐角“莫名其妙变钝了”,大概率就是miterLimit在起作用。
ctx.lineWidth = 4; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; ctx.miterLimit = 5;这里有几个我实测下来比较稳的搭配建议:做流程图连线推荐lineWidth=2, lineCap='round', lineJoin='round',线条既有存在感又不抢内容;做数据图表的坐标轴推荐lineWidth=1, lineCap='butt',保持像素级的锐利;做装饰性的光效线条时,lineWidth=8, lineCap='round', shadowBlur=12的组合,效果接近画笔笔触。
2.3 阴影样式:shadow系列四件套
Canvas的阴影由四个属性控制:shadowColor阴影颜色、shadowBlur模糊程度、shadowOffsetX水平偏移、shadowOffsetY垂直偏移。这四个属性可单独使用,也可以组合出各种发光和立体效果。
shadowColor支持带透明度的颜色,我常用的有两种:做发光效果时用rgba(255, 255, 255, 0.8)或主题色的半透明版本;做投影效果时用rgba(0, 0, 0, 0.3)。shadowBlur是模糊半径,值越大边缘越柔和,但性能消耗也越大。做真实感投影时,我会把偏移值和模糊值配在一起调:偏移大、模糊大,像是阳光斜射;偏移小、模糊小,像是环境光。
需要特别提醒的是:阴影是在绘制操作发生时生效的,所以你必须先设置好阴影属性,再调用绘制方法。绘制完成后如果要恢复,把shadowBlur设为0即可。如果阴影不生效,或者图形不更新,先检查是不是绘制顺序的问题。
// 发光效果 ctx.shadowColor = 'rgba(255, 200, 0, 0.8)'; ctx.shadowBlur = 20; ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 0; ctx.fillRect(50, 50, 100, 100); // 投影效果 ctx.shadowColor = 'rgba(0, 0, 0, 0.3)'; ctx.shadowBlur = 10; ctx.shadowOffsetX = 4; ctx.shadowOffsetY = 4;还有一个坑:文字和图形的阴影表现不太一样。文字加上大shadowBlur后,边缘容易发糊发虚,所以做标题文字时我一般把模糊值控制在6~10之间,再配一个稍微明显的偏移,看起来最有质感。
2.4 文本样式:font、textAlign、textBaseline与direction
在Canvas里绘制文字,最常用的属性是font、textAlign、textBaseline。font的取值和CSS的font简写类似,但要注意两点:必须是大小空格字体族的格式;单位建议用px。常见的写法是'16px "Microsoft YaHei", sans-serif'或'bold 18px Arial, sans-serif'。如果你不设置font,默认值是10px sans-serif,那画出来的文字会小得离谱。
textAlign控制水平对齐:start、end、left、right、center。textBaseline控制垂直对齐方式,取值有top、hanging、middle、alphabetic、ideographic、bottom。这两个属性一定要配合起来用,否则文字定位会“飘”。我做图表标签时固定用textAlign='center'加textBaseline='middle',这样文字的几何中心正好落在坐标点上,后续调整位置也不会歪。
direction属性用于设置文字方向,取值ltr或rtl,大多数中文场景用不到,但做混合语言排版时会需要它。它本质上和textAlign是配合关系,start和end会随direction变化,而left和right是固定的。这一点经验不足的人很容易绕晕。
ctx.font = '600 20px "Source Han Sans", sans-serif'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillStyle = '#222'; ctx.fillText('Canvas 绘图样式指南', 200, 150);3. 渐变、图案与合成样式
3.1 线性渐变与径向渐变的实战细节
渐变样式能极大提升视觉层次感。Canvas提供了两种渐变:线性渐变createLinearGradient和径向渐变createRadialGradient。这两种方法都会返回一个CanvasGradient对象,再把它赋给fillStyle或strokeStyle。
createLinearGradient(x0, y0, x1, y1)接收四个参数,代表渐变起点和终点的坐标。理解这个坐标系是掌握渐变的关键:渐变是“画到画布上”的,而不是“贴到图形上”的。也就是说,当你给一个正方形设置从(0,0)到(200,0)的渐变时,只有落在这个正方形范围内的部分才会显示渐变效果,如果正方形没覆盖完整的渐变范围,颜色就会“截断”。
const gradient = ctx.createLinearGradient(20, 20, 220, 20); gradient.addColorStop(0, '#4285F4'); gradient.addColorStop(0.5, '#9C27B0'); gradient.addColorStop(1, '#FF5252'); ctx.fillStyle = gradient; ctx.fillRect(20, 20, 200, 100);径向渐变createRadialGradient(x0, y0, r0, x1, y1, r1)接收六个参数,两个圆:内圆和外圆。内圆和外圆之间的区域就是渐变区。这个参数组合初看有些抽象,我习惯把它理解成“从内圆开始,向外圆扩散”。做光晕效果时,把内圆半径设为0、外圆半径设为光晕半径,颜色从明亮到透明过渡,非常自然。做圆球立体感时,把内圆圆心偏向左上、外圆圆心居中,颜色一深一浅,立刻就有体积感。
提示:渐变效果和描边也能组合。把渐变赋给
strokeStyle,线条就会带上渐变颜色,常用于画数据曲线时强调最高点或最低点。
3.2 图案样式:createPattern的坐标系陷阱
createPattern(image, repetition)可以创建图案填充样式,其中repetition支持repeat、repeat-x、repeat-y和no-repeat。这个方法最大的坑在于:图像必须先加载完成才能成功创建图案。如果你在img.onload之前就调用createPattern,这个函数会静默失败,后面画出来的图形什么填充都没有。
我踩过这个坑好几次,后来总结出一个稳定的封装方案:先new Image(),设置src,等onload事件触发之后再创建图案、再绘制。如果你用Canvas自身作为图案源,要注意图案的坐标系同样是基于画布左上角的,图形位置变了,图案的“纹路”也会跟着变。这在做游戏地图、重复纹理时很容易让人困惑,你以为是图案跟着图形走,实际是图案“钉”在画布上。
const img = new Image(); img.onload = function () { const pattern = ctx.createPattern(img, 'repeat'); ctx.fillStyle = pattern; ctx.fillRect(0, 0, 600, 400); }; img.src = './texture.png';如果你想让图案的位置随图形移动,需要在绘制前用translate调整画布原点,或者用save和restore配合变换。这属于变换样式的范畴,我在第四章会展开。
3.3 全局合成样式:globalAlpha与globalCompositeOperation
globalAlpha是全局透明度,范围为0到1。它作用于后续所有绘制操作,和fillStyle里带rgba透明度的效果类似,但有区别:globalAlpha影响的是“整块内容”,包括已经叠加的复杂图形;而rgba只影响当前设置的样式。做整体淡入淡出动画时用globalAlpha非常方便,逐帧改变它的值就能实现平滑过渡。
globalCompositeOperation这个属性有点被低估了,它是Canvas绘图样式中最强大的“图层混合开关”。取值非常多,常用的有source-over(默认,新图形覆盖旧图形)、destination-over(新图形在旧图形下方)、multiply(正片叠底)、screen(滤色)、overlay(叠加)、source-atop(新图形只显示在旧图形内部区域)等。
设计视觉元素时,这类混合模式特别适合做光照效果。比如我给一个圆形加上径向渐变,然后用globalCompositeOperation = 'overlay'叠加一个亮色区域,质感立刻提升。做遮罩、擦除效果时用destination-out,它可以让已有内容变成透明,实现马赛克擦除、橡皮擦等功能。这里没有统一的“最佳模式”,每种效果都在不同场景下有不同用途,动手试几轮才能形成感觉。
ctx.globalAlpha = 0.6; ctx.globalCompositeOperation = 'destination-over'; ctx.fillStyle = '#e3f2fd'; ctx.fillRect(0, 0, 300, 200); // 记得用完重置 ctx.globalAlpha = 1; ctx.globalCompositeOperation = 'source-over';4. 变换样式与像素级控制
4.1 变换样式:translate、rotate、scale与transform
变换一并归入“绘图样式”,原因在于它直接改变后续图形的呈现方式。translate(x, y)把原点移动到指定位置,rotate(angle)绕当前原点旋转,scale(sx, sy)按比例缩放。这三个方法都是叠加式的,调用顺序不同,结果完全不一样。
这里有个重要的认知模型:变换操作的顺序是按照“后调用先生效”的规则来的。换句话说,平移后旋转和旋转后平移,最终效果是不同的。我经常用save()和restore()把变换限制在局部,做完一组绘制就立刻恢复状态。如果你画着画着发现图形的位置越来越偏,十有八九是因为translate一直在叠加,却没有用restore恢复原点。
ctx.save(); ctx.translate(150, 150); ctx.rotate(Math.PI / 4); ctx.scale(1.5, 1.5); ctx.fillStyle = '#4CAF50'; ctx.fillRect(-25, -25, 50, 50); ctx.restore();transform(a, b, c, d, e, f)是底层的矩阵变换方法,它对应的是a水平缩放、b水平倾斜、c垂直倾斜、d垂直缩放、e水平平移、f垂直平移。直接用矩阵可能有点抽象,但当你需要做镜像、斜切等效果时,transform是最直接的选择。比如水平镜像一张图,可以这样:
ctx.transform(-1, 0, 0, 1, canvas.width, 0);这个技巧是我在做双人对称游戏界面时发现的:把图像坐标系反一下,同样的绘制代码就能画出镜像版本,省了重新布局的功夫。第一次用的时候容易绕晕,建议你在控制台里用不同的参数搭配试,看到输出结果后自然就理解了。
4.2 像素级样式:getImageData与putImageData
如果说前面的样式是对“矢量图形”的呈现控制,getImageData和putImageData则是对“像素矩阵”的底层读写,这算得上Canvas样式的终极控制方式。getImageData(x, y, width, height)会读取指定区域的像素信息,返回一个ImageData对象,它的data属性是一个数组,按RGBA顺序排列,每四个元素代表一个像素。
用这个方法可以做出任何滤镜效果:反色、灰度、马赛克、色相偏移,全都可以通过遍历像素数组来实现。我做一个简单的灰度效果只需要三行代码:
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; for (let i = 0; i < data.length; i += 4) { const gray = data[i] * 0.299 + data[i + 1] * 0.587 + data[i + 2] * 0.114; data[i] = gray; data[i + 1] = gray; data[i + 2] = gray; } ctx.putImageData(imageData, 0, 0);这里的灰度公式是业界通用的加权计算,因为人眼对绿色最敏感、对蓝色最迟钝,所以加了不同的权重值,出来的效果会比取平均数自然得多。做像素操作时要特别注意性能:遍历一整个1920x1080的Canvas意味着要处理超过800万个数组元素,直接把界面卡死都有可能。我的做法是先降低画布分辨率、做完滤镜再放大展示,或者用document.createDocumentFragment分批处理,避免一次卡死主线程。
5. 实战:绘制一个完整的“数据仪表盘卡片”
5.1 设计目标和样式组合方案
前面讲的都是知识点,现在我们来串一个完整的例子。这次要绘制的是一个“数据仪表盘卡片”,包含三部分:一个带渐变和高光的圆环进度、一段带阴影的文本标题、一个用图案填充的底纹区域。这个案例基本涵盖了颜色、线条、阴影、文本、渐变、图案、合成模式这些主要样式维度。
开始之前,我先确定整体风格:卡片底色是深蓝灰,圆环进度用蓝绿渐变,进度超过一定阈值后数字变成亮橙色。这个配色在数据可视化项目里很常见,既不过分花哨,又能在深色背景下保持层次。画布尺寸设为400x300,圆环中心在(120, 150),半径为80,线宽为12;标题文字放在上方,数值文字放在圆环中心。
5.2 分步骤实现代码
先把整体框架搭好,包括画布尺寸、最基础的背景绘制和圆环的数据计算:
const canvas = document.getElementById('dashboard'); const ctx = canvas.getContext('2d'); const width = canvas.width = 400; const height = canvas.height = 300; // 背景 ctx.fillStyle = '#1e293b'; ctx.fillRect(0, 0, width, height); // 圆环进度数据 const progress = 0.72; const startAngle = -Math.PI / 2; const endAngle = startAngle + Math.PI * 2 * progress; const centerX = 120; const centerY = 150; const radius = 80;背景覆盖之后,接着画圆环的底环和进度环。底环用深灰色,进度环用线性渐变。这一步的重点是渐变范围要和圆环的包围盒对齐,否则颜色会偏离预期位置:
// 底环 ctx.beginPath(); ctx.arc(centerX, centerY, radius, 0, Math.PI * 2); ctx.strokeStyle = '#334155'; ctx.lineWidth = 12; ctx.stroke(); // 进度环渐变 const gradient = ctx.createLinearGradient( centerX - radius, centerY - radius, centerX + radius, centerY + radius ); gradient.addColorStop(0, '#22d3ee'); gradient.addColorStop(1, '#4f8ef7'); ctx.beginPath(); ctx.arc(centerX, centerY, radius, startAngle, endAngle); ctx.strokeStyle = gradient; ctx.lineWidth = 12; ctx.lineCap = 'round'; ctx.stroke();圆环画完后,在右上角加一个带投影的装饰圆点,用来体现“高光”感。这个圆点用destination-over做底色衬托,所以整体不会破坏原图的颜色关系:
// 装饰高光点 ctx.save(); ctx.shadowColor = 'rgba(255, 255, 255, 0.6)'; ctx.shadowBlur = 12; ctx.fillStyle = 'rgba(255, 255, 255, 0.25)'; ctx.beginPath(); ctx.arc(240, 80, 24, 0, Math.PI * 2); ctx.fill(); ctx.restore();然后是文字部分。标题用textAlign='center',数值文字用更大号字体并加阴影。注意文字绘制顺序:先设置样式,再fillText。数值的小数点显示一位,保证布局可控:
// 标题 ctx.font = '600 14px "Microsoft YaHei", sans-serif'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillStyle = '#94a3b8'; ctx.fillText('季度销售目标完成率', 200, 40); // 中心数值 ctx.shadowColor = 'rgba(79, 142, 247, 0.6)'; ctx.shadowBlur = 16; ctx.font = '700 38px "Arial", sans-serif'; ctx.fillStyle = '#f8fafc'; ctx.fillText('72%', centerX, centerY); ctx.shadowBlur = 0; // 右上角标签 ctx.font = '500 12px "Microsoft YaHei", sans-serif'; ctx.fillStyle = '#22d3ee'; ctx.fillText('较上期 +12%', 300, 80);最后处理底纹。我准备一个很小的Canvas作为纹理源,然后铺满卡片的下半部分。这步用的是createPattern,注意要把图案的坐标平移到目标区域,不然纹理会和圆环位置错位:
// 纹理底纹 const textureCanvas = document.createElement('canvas'); textureCanvas.width = 20; textureCanvas.height = 20; const tctx = textureCanvas.getContext('2d'); tctx.strokeStyle = 'rgba(148, 163, 184, 0.12)'; tctx.lineWidth = 1; tctx.beginPath(); tctx.moveTo(0, 0); tctx.lineTo(20, 20); tctx.stroke(); const pattern = ctx.createPattern(textureCanvas, 'repeat'); ctx.save(); ctx.translate(0, 180); ctx.fillStyle = pattern; ctx.fillRect(0, 0, 400, 120); ctx.restore();5.3 关键样式参数调整记录
这个案例我调试了大概五轮才稳定。第一轮的问题是渐变范围没对齐,整个圆环颜色偏淡;把渐变起点终点改到包围盒的四角后解决。第二轮问题出在线帽,lineCap默认是butt,进度环起点收尾特别生硬,改成round后气质立刻柔和。第三轮发现数值文字离圆环边界太近,一度挤压到看不清,后来把数值字号从42调整到38、并且把textBaseline从alphabetic改成middle,才彻底解决。
这三个调整本质上是同一类问题:Canvas属性之间的联动效应。调了一个参数,其他参数也要跟着一起适配。这也是为什么我不建议照着别人的代码直接抄,最好是理解了每个参数的意义,再根据视觉反馈做微调。
6. 常见问题与避坑实录
6.1 问题速查表
| 问题现象 | 常见原因 | 解决方案 |
|---|---|---|
| 图形内容不变,但样式没生效 | fillStyle在绘制后改变,或者期望的样式被后续绘制覆盖 | 每次绘制前重新设置样式,绘制顺序要确认 |
| 线条边界参差不齐 | lineWidth值太小,且lineCap为butt | 适当加粗线条,或在低DPI下调整lineWidth,必要时用round线帽 |
| 渐变颜色怎么调都“偏” | 渐变坐标和图形包围盒没有对齐 | 手动计算图形包围盒,并按包围盒左上角为起点设置渐变坐标 |
| 阴影太重或太轻 | shadowBlur和shadowOffset组合不匹配 | 偏移值大时模糊值也要大,偏移小时模糊值减小 |
| 文字位置“漂移” | textAlign或textBaseline设置不对 | 固定使用center配middle,特殊情况再单独调整 |
| 图案纹路错位 | 没有考虑pattern坐标基于画布原点 | 用translate把原点移到图形位置后再填充 |
| 画布在高分辨率屏上模糊 | 没有处理devicePixelRatio | 按比例缩放画布物理尺寸,重绘时乘以DPR |
| 多个图形叠加颜色发暗 | globalCompositeOperation忘记重置 | 绘制后立刻重置为source-over |
这八类问题是我在Canvas开发中碰到频率最高的。有一半以上不是代码写得“不对”,而是Canvas的状态管理做得不够严谨。
6.2 独家避坑心得
有几个坑是官方文档不会主动告诉你的,我整理几条真实经验。
第一个是关于save和restore的配对问题。很多人在绘制循环里忘写save,导致变换、透明度、阴影越攒越多,画布状态被“污染”。我现在的习惯是:凡是涉及translate、rotate、scale、globalAlpha、globalCompositeOperation、shadow这几组中的任意一组,一律先用save,操作完立刻restore。这样即使中间报错,状态也能在下一个绘制帧之前恢复干净。
第二个是关于devicePixelRatio导致文字模糊的问题。普通屏幕DPR是1,但Retina屏可能是2或3。如果不做适配,画布图形会看起来“软绵绵”的。我的做法是固定用物理像素创建画布,再通过CSS把显示尺寸压回逻辑像素,同时在绘制前把坐标轴乘以DPR。这样文字、线条的渲染精度都会更高,虽然代码量多了一点,但视觉提升是立竿见影的。
第三个是关于阴影和性能的平衡。shadowBlur是非常消耗性能的属性,你可以在一个图形上开大阴影、画完立刻关掉,但如果在几百个图形上同时保留大阴影,卡顿几乎是必然的。我做的性能优化方案是:先用离屏Canvas绘制一个带阴影的图形缓存,再通过drawImage把它贴到主画布上,阴影只计算一次,性能提升非常明显。
第四个小技巧是手动模拟roundRect圆角矩形,因为不同浏览器对标配的roundRect支持不一致。用beginPath加arcTo能稳定画出圆角矩形,同时还能配合渐变和阴影实现更丰富的视觉效果。这个方案虽然多几行代码,但兼容性和可控性都要好得多。
function drawRoundRect(ctx, x, y, w, h, r) { ctx.beginPath(); ctx.moveTo(x + r, y); ctx.arcTo(x + w, y, x + w, y + h, r); ctx.arcTo(x + w, y + h, x, y + h, r); ctx.arcTo(x, y + h, x, y, r); ctx.arcTo(x, y, x + w, y, r); ctx.closePath(); }结尾的小提醒
最后分享一点我自己的体会:学习Canvas绘图样式,最有价值的方式不是记住API,而是建立一个“先想后画”的习惯。每次动手绘制前,先花几秒钟从头到尾过一遍:这个图形需要哪种填充、哪种描边、哪种阴影、是否需要变换、是否需要合成。有了这个全局审视,很多问题在落笔之前就已经避开了。
如果你在实战中遇到某个样式组合不生效,别急着改参数,先确认画布上下文是否新鲜、状态是否被污染、绘制顺序是否符合预期。大多数“诡异问题”,追根溯源都是这三个环节里的某一个出了问题。
Canvas样式这一块,代码量不多,但变化极大。多试几次,多存几个自己满意的配方,慢慢就会形成手感。希望这篇指南能在你下次画图的时候帮你少走几步弯路。