HTML和Javascript中的Canvas 绘图样式完全指南

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

一、总介绍

Canvas(画布)是 HTML5 中最重要的绘图 API 之一,它就像一张空白的画纸,而 JavaScript 则是握在手中的画笔。在这张画纸上,我们可以绘制各种图形、文字、图像,但真正让这些元素变得丰富多彩的,是 Canvas 提供的绘图样式系统

如果把绘图比作绘画:

  • 路径(Path) 就像是铅笔勾勒的轮廓线

  • 样式(Style) 就像是给轮廓线上色、加粗、添加阴影的画笔和颜料

Canvas 的绘图样式系统主要包含以下五大类:

类别作用类比
色彩与透明度设置图形的颜色和透明程度选择颜料的颜色和浓淡
线条样式定义线条的粗细、端点、连接方式选择不同粗细的画笔
阴影效果为图形添加阴影,增强立体感在图形背后添加影子
渐变与图案使用渐变或图像填充图形使用渐变颜料或图案贴纸
文本样式控制文字的字体、大小、对齐方式设置文字的排版样式

二、色彩与透明度样式

色彩是视觉的基础,Canvas 提供了灵活的色彩设置方式。

1. fillStyle - 填充样式

项目内容
属性/方法ctx.fillStyle
含义设置图形内部填充的颜色、渐变或图案
词源fill(填充) + style(样式)- 填充的样式
参数及说明接受三种类型的值:① 颜色字符串:CSS颜色值 ② 渐变对象:由渐变方法创建 ③ 图案对象:由图案方法创建
默认值#000000(黑色)

完整示例

<!DOCTYPE html>
<html>
<head>
    <title>fillStyle 示例</title>
</head>
<body>
    <canvas id="myCanvas" width="400" height="300" style="border:1px solid #ccc;"></canvas>
    
    <script>
        const canvas = document.getElementById('myCanvas');
        const ctx = canvas.getContext('2d');
        
        // 红色矩形
        ctx.fillStyle = "red";
        ctx.fillRect(50, 50, 100, 100);
        
        // 半透明蓝色矩形
        ctx.fillStyle = "rgba(0, 0, 255, 0.3)";
        ctx.fillRect(100, 100, 100, 100);
        
        // 十六进制颜色矩形
        ctx.fillStyle = "#FFA500";  // 橙色
        ctx.fillRect(200, 50, 80, 80);
        
        // HSL颜色矩形
        ctx.fillStyle = "hsl(120, 100%, 50%)";  // 绿色
        ctx.fillRect(200, 150, 80, 80);
    </script>
</body>
</html>

绘制效果

  • 左上角(50,50)处绘制一个红色正方形

  • 第二个半透明蓝色正方形(100,100)与红色部分重叠,呈现紫色混合效果

  • 右上角(200,50)处绘制橙色正方形

  • 右下角(200,150)处绘制亮绿色正方形

示例分析:这个例子展示了 fillStyle 支持的多种颜色格式:

  • 颜色名称("red")直观易读

  • RGBA(rgba(0,0,255,0.3))可以控制透明度

  • 十六进制("#FFA500")是 Web 开发常用格式

  • HSL(hsl(120,100%,50%))适合调整色调

2. strokeStyle - 描边样式

项目内容
属性/方法ctx.strokeStyle
含义设置图形轮廓的颜色、渐变或图案
词源stroke(一笔/描边) + style(样式)- 描边的样式
参数及说明同fillStyle,接受颜色、渐变、图案
默认值#000000(黑色)

完整示例

<!DOCTYPE html>
<html>
<head>
    <title>strokeStyle 示例</title>
</head>
<body>
    <canvas id="myCanvas" width="500" height="300" style="border:1px solid #ccc;"></canvas>
    
    <script>
        const canvas = document.getElementById('myCanvas');
        const ctx = canvas.getContext('2d');
        
        // 设置统一的线条宽度
        ctx.lineWidth = 5;
        
        // 蓝色描边矩形
        ctx.strokeStyle = "blue";
        ctx.strokeRect(50, 50, 120, 80);
        
        // 橙色描边矩形
        ctx.strokeStyle = "#FFA500";
        ctx.strokeRect(200, 50, 120, 80);
        
        // 半透明红色描边矩形
        ctx.strokeStyle = "rgba(255, 0, 0, 0.5)";
        ctx.strokeRect(350, 50, 120, 80);
        
        // 绘制不同颜色的线条
        ctx.beginPath();
        ctx.moveTo(50, 200);
        ctx.lineTo(200, 200);
        ctx.strokeStyle = "green";
        ctx.stroke();
        
        ctx.beginPath();
        ctx.moveTo(50, 250);
        ctx.lineTo(200, 250);
        ctx.strokeStyle = "purple";
        ctx.lineWidth = 8;
        ctx.stroke();
    </script>
</body>
</html>

绘制效果

  • 第一行三个描边矩形:蓝色、橙色、半透明红色(轮廓线)

  • 第二行两条直线:绿色细线和紫色粗线

示例分析:strokeStyle 与 fillStyle 的参数完全一致,但作用于图形的轮廓而非内部填充。通过 strokeRect() 方法可以直接绘制带描边的矩形,而不需要先填充再描边。

3. globalAlpha - 全局透明度

项目内容
属性/方法ctx.globalAlpha
含义设置后续绘制的所有图形的全局透明度
词源global(全局的) + alpha(Alpha通道/透明度)- 全局透明度
参数及说明0.0(完全透明)~ 1.0(完全不透明)之间的数值
默认值1.0

完整示例

<!DOCTYPE html>
<html>
<head>
    <title>globalAlpha 示例</title>
</head>
<body>
    <canvas id="myCanvas" width="500" height="350" style="border:1px solid #ccc;"></canvas>
    
    <script>
        const canvas = document.getElementById('myCanvas');
        const ctx = canvas.getContext('2d');
        
        // 1. 完全不透明的红色矩形
        ctx.fillStyle = "red";
        ctx.fillRect(50, 50, 100, 100);
        
        // 2. 设置50%透明度
        ctx.globalAlpha = 0.5;
        ctx.fillStyle = "blue";
        ctx.fillRect(100, 80, 100, 100);
        
        // 3. 设置20%透明度
        ctx.globalAlpha = 0.2;
        ctx.fillStyle = "green";
        ctx.fillRect(30, 120, 100, 100);
        
        // 4. 重置透明度,绘制参考文字
        ctx.globalAlpha = 1.0;
        ctx.fillStyle = "black";
        ctx.font = "14px Arial";
        ctx.fillText("不透明红", 50, 40);
        ctx.fillText("50%透明蓝", 100, 70);
        ctx.fillText("20%透明绿", 30, 110);
        
        // 5. 演示透明度叠加效果
        ctx.globalAlpha = 0.6;
        ctx.fillStyle = "purple";
        ctx.fillRect(300, 50, 80, 80);
        
        ctx.globalAlpha = 0.6;
        ctx.fillStyle = "orange";
        ctx.fillRect(340, 70, 80, 80);
        
        ctx.globalAlpha = 1.0;
        ctx.fillText("透明度叠加效果", 300, 40);
    </script>
</body>
</html>

绘制效果

  • 左侧三个矩形展示不同透明度的叠加效果

  • 右侧两个矩形展示相同透明度的颜色混合

示例分析:globalAlpha 会影响之后所有绘制操作,直到被重新设置。从例子中可以看出:

  • 红色完全不透明

  • 蓝色半透明,与红色重叠处呈现紫色

  • 绿色几乎透明,隐约可见

  • 紫色和橙色以相同透明度重叠,形成新的混合色

4. rgba() 与 globalAlpha 对比

完整示例

<!DOCTYPE html>
<html>
<head>
    <title>rgba vs globalAlpha 对比</title>
</head>
<body>
    <canvas id="myCanvas" width="600" height="300" style="border:1px solid #ccc;"></canvas>
    
    <script>
        const canvas = document.getElementById('myCanvas');
        const ctx = canvas.getContext('2d');
        
        // 绘制背景网格
        ctx.strokeStyle = "#eee";
        ctx.lineWidth = 1;
        for (let i = 0; i < 600; i += 20) {
            ctx.beginPath();
            ctx.moveTo(i, 0);
            ctx.lineTo(i, 300);
            ctx.stroke();
        }
        for (let i = 0; i < 300; i += 20) {
            ctx.beginPath();
            ctx.moveTo(0, i);
            ctx.lineTo(600, i);
            ctx.stroke();
        }
        
        // === 左侧:使用 globalAlpha ===
        ctx.fillStyle = "red";
        ctx.globalAlpha = 0.5;
        ctx.fillRect(50, 50, 120, 120);
        
        ctx.fillStyle = "blue";
        ctx.fillRect(100, 80, 120, 120);
        
        // === 右侧:使用 rgba ===
        ctx.globalAlpha = 1.0;  // 重置全局透明度
        
        // 使用 rgba 单独控制每个颜色
        ctx.fillStyle = "rgba(255, 0, 0, 0.5)";  // 50% 透明红
        ctx.fillRect(350, 50, 120, 120);
        
        ctx.fillStyle = "rgba(0, 0, 255, 0.2)";  // 20% 透明蓝
        ctx.fillRect(400, 80, 120, 120);
        
        // 添加说明文字
        ctx.font = "14px Arial";
        ctx.fillStyle = "black";
        ctx.fillText("globalAlpha 方式", 50, 30);
        ctx.fillText("所有图形透明度相同", 50, 190);
        
        ctx.fillText("rgba 方式", 350, 30);
        ctx.fillText("可单独控制透明度", 350, 190);
    </script>
</body>
</html>

绘制效果

  • 左侧:两个矩形使用相同的透明度(50%),蓝色覆盖红色区域呈现均匀的混合

  • 右侧:红色 50% 透明,蓝色 20% 透明,重叠区域透明度不均

示例分析:对比两种透明度控制方式:

  • globalAlpha:统一控制所有后续图形,适合需要整体调整透明度的场景

  • rgba():精确控制每个颜色的透明度,灵活性更高,可以混合不同透明度的图形


三、线条样式

线条是绘制轮廓、路径的基础,Canvas 提供了精细控制线条外观的属性。

1. lineWidth - 线条宽度

项目内容
属性/方法ctx.lineWidth
含义设置线条的粗细
词源line(线) + width(宽度)- 线的宽度
参数及说明正数值(像素单位)
默认值1.0

完整示例

<!DOCTYPE html>
<html>
<head>
    <title>lineWidth 示例</title>
</head>
<body>
    <canvas id="myCanvas" width="500" height="350" style="border:1px solid #ccc;"></canvas>
    
    <script>
        const canvas = document.getElementById('myCanvas');
        const ctx = canvas.getContext('2d');
        
        // 绘制背景网格
        ctx.strokeStyle = "#eee";
        ctx.lineWidth = 0.5;
        for (let i = 0; i < 500; i += 20) {
            ctx.beginPath();
            ctx.moveTo(i, 0);
            ctx.lineTo(i, 350);
            ctx.stroke();
        }
        
        // 1. 线宽 = 1
        ctx.beginPath();
        ctx.moveTo(50, 80);
        ctx.lineTo(450, 80);
        ctx.lineWidth = 1;
        ctx.strokeStyle = "black";
        ctx.stroke();
        ctx.fillText("线宽 1", 50, 70);
        
        // 2. 线宽 = 3
        ctx.beginPath();
        ctx.moveTo(50, 130);
        ctx.lineTo(450, 130);
        ctx.lineWidth = 3;
        ctx.strokeStyle = "blue";
        ctx.stroke();
        ctx.fillText("线宽 3", 50, 120);
        
        // 3. 线宽 = 8
        ctx.beginPath();
        ctx.moveTo(50, 180);
        ctx.lineTo(450, 180);
        ctx.lineWidth = 8;
        ctx.strokeStyle = "green";
        ctx.stroke();
        ctx.fillText("线宽 8", 50, 170);
        
        // 4. 线宽 = 15
        ctx.beginPath();
        ctx.moveTo(50, 240);
        ctx.lineTo(450, 240);
        ctx.lineWidth = 15;
        ctx.strokeStyle = "red";
        ctx.stroke();
        ctx.fillText("线宽 15", 50, 230);
        
        // 5. 演示线宽与坐标的关系
        ctx.fillStyle = "#666";
        ctx.font = "12px Arial";
        ctx.fillText("线宽从中心向两侧扩展", 50, 290);
        
        // 绘制精确的像素边界
        ctx.strokeStyle = "orange";
        ctx.lineWidth = 0.5;
        ctx.beginPath();
        ctx.moveTo(300, 260);
        ctx.lineTo(300, 330);
        ctx.stroke();
        ctx.fillText("中心线", 310, 310);
    </script>
</body>
</html>

绘制效果

  • 从上到下五条线,宽度依次增加

  • 最下面的说明展示线条是"以路径为中心向两侧扩展"的

示例分析:lineWidth 决定了线条的粗细,但需要注意:

  • 线宽是从路径中心向两侧扩展的

  • 宽度为奇数的线(如1,3,5)在像素网格上可能需要微调坐标以获得清晰边缘

  • 线宽越大,线条越粗壮醒目

2. lineCap - 线端点样式

项目内容
属性/方法ctx.lineCap
含义设置线条端点的形状
词源line(线) + cap(帽子/盖)- 线的帽子(端点)
参数及说明butt(平端)、round(圆端)、square(方端)
默认值butt

完整示例

<!DOCTYPE html>
<html>
<head>
    <title>lineCap 示例</title>
</head>
<body>
    <canvas id="myCanvas" width="600" height="350" style="border:1px solid #ccc;"></canvas>
    
    <script>
        const canvas = document.getElementById('myCanvas');
        const ctx = canvas.getContext('2d');
        
        // 设置粗线以便看清效果
        ctx.lineWidth = 20;
        
        // 绘制三条水平参考线
        ctx.strokeStyle = "#ccc";
        ctx.lineWidth = 1;
        for (let y = 80; y <= 250; y += 85) {
            ctx.beginPath();
            ctx.moveTo(50, y);
            ctx.lineTo(550, y);
            ctx.stroke();
        }
        
        // 绘制端点标记(红点)
        ctx.fillStyle = "red";
        const markPoints = [
            [150, 80], [300, 80],  // 第一行的起点和终点
            [150, 165], [300, 165], // 第二行的起点和终点
            [150, 250], [300, 250]  // 第三行的起点和终点
        ];
        markPoints.forEach(([x, y]) => {
            ctx.beginPath();
            ctx.arc(x, y, 3, 0, Math.PI * 2);
            ctx.fill();
        });
        
        // 1. butt - 平端(默认)
        ctx.lineWidth = 20;
        ctx.strokeStyle = "blue";
        ctx.lineCap = "butt";
        ctx.beginPath();
        ctx.moveTo(150, 80);
        ctx.lineTo(300, 80);
        ctx.stroke();
        
        // 2. round - 圆端
        ctx.lineCap = "round";
        ctx.beginPath();
        ctx.moveTo(150, 165);
        ctx.lineTo(300, 165);
        ctx.stroke();
        
        // 3. square - 方端
        ctx.lineCap = "square";
        ctx.beginPath();
        ctx.moveTo(150, 250);
        ctx.lineTo(300, 250);
        ctx.stroke();
        
        // 添加说明文字
        ctx.font = "16px Arial";
        ctx.fillStyle = "black";
        ctx.lineWidth = 1;
        ctx.fillText("butt (平端)", 350, 85);
        ctx.fillText("round (圆端)", 350, 170);
        ctx.fillText("square (方端)", 350, 255);
        
        ctx.font = "14px Arial";
        ctx.fillText("红点标记理论端点位置", 350, 310);
        ctx.fillText("butt: 正好在红点", 350, 330);
        ctx.fillText("round/square: 超出红点半个线宽", 350, 350);
    </script>
</body>
</html>

绘制效果

  • 三条蓝色粗线,端点样式分别为 butt、round、square

  • 红点标记线条的理论起点和终点位置

示例分析:lineCap 控制线条端点的外观:

  • butt:线条恰好终止于端点,与红点对齐,呈平直截面

  • round:线条端点呈半圆形,超出红点半个线宽(10px)

  • square:线条端点呈矩形,同样超出红点半个线宽(10px)

3. lineJoin - 线条连接样式

项目内容
属性/方法ctx.lineJoin
含义设置两条线相交时拐角的形状
词源line(线) + join(连接)- 线的连接处
参数及说明miter(尖角)、round(圆角)、bevel(斜角)
默认值miter

完整示例

<!DOCTYPE html>
<html>
<head>
    <title>lineJoin 三种样式对比</title>
    <style>
        body { font-family: Arial; text-align: center; }
        .container { display: flex; justify-content: center; gap: 20px; }
    </style>
</head>
<body>
    <h3>lineJoin 三种样式对比(粗线+小角度)</h3>
    <div class="container">
        <canvas id="canvasMiter" width="200" height="200" style="border:1px solid #ccc;"></canvas>
        <canvas id="canvasRound" width="200" height="200" style="border:1px solid #ccc;"></canvas>
        <canvas id="canvasBevel" width="200" height="200" style="border:1px solid #ccc;"></canvas>
    </div>
    
    <script>
        // miter 尖角
        const canvas1 = document.getElementById('canvasMiter');
        const ctx1 = canvas1.getContext('2d');
        ctx1.lineWidth = 30;
        ctx1.strokeStyle = "#3498db";
        ctx1.lineJoin = "miter";
        ctx1.beginPath();
        ctx1.moveTo(60, 130);
        ctx1.lineTo(100, 50);  // 陡坡
        ctx1.lineTo(140, 130);
        ctx1.stroke();
        ctx1.font = "16px Arial";
        ctx1.fillText("miter", 70, 170);
        
        // round 圆角
        const canvas2 = document.getElementById('canvasRound');
        const ctx2 = canvas2.getContext('2d');
        ctx2.lineWidth = 30;
        ctx2.strokeStyle = "#2ecc71";
        ctx2.lineJoin = "round";
        ctx2.beginPath();
        ctx2.moveTo(60, 130);
        ctx2.lineTo(100, 50);
        ctx2.lineTo(140, 130);
        ctx2.stroke();
        ctx2.font = "16px Arial";
        ctx2.fillText("round", 70, 170);
        
        // bevel 斜角
        const canvas3 = document.getElementById('canvasBevel');
        const ctx3 = canvas3.getContext('2d');
        ctx3.lineWidth = 30;
        ctx3.strokeStyle = "#e74c3c";
        ctx3.lineJoin = "bevel";
        ctx3.beginPath();
        ctx3.moveTo(60, 130);
        ctx3.lineTo(100, 50);
        ctx3.lineTo(140, 130);
        ctx3.stroke();
        ctx3.font = "16px Arial";
        ctx3.fillText("bevel", 70, 170);
    </script>
    
    <div style="margin-top:20px; text-align:left; display:inline-block;">
        <p><strong style="color:#3498db">miter(尖角)</strong>:尖锐突出,像山峰</p>
        <p><strong style="color:#2ecc71">round(圆角)</strong>:圆润过渡,像圆弧</p>
        <p><strong style="color:#e74c3c">bevel(斜角)</strong>:平直切面,像被切掉</p>
        <p>💡 使用粗线(30px)和陡坡角度,让区别一目了然</p>
    </div>
</body>
</html>

绘制效果

  • 三个并排的图形,分别使用蓝、绿、红三种颜色

  • 每个图形都是倒V形,但顶部连接处形状明显不同:

    • 蓝色:尖尖的突出

    • 绿色:圆圆的弧线

    • 红色:平平的切面

示例分析

样式视觉特征适用场景
miter尖锐的角,向外延伸闪电、星星、锋利边缘
round圆润的弧线,柔和过渡徽标、圆角边框、友好界面
bevel平直的斜面,干脆利落3D效果、斜面装饰、科技感设计

关键点

  • 线宽越大、角度越小,三种样式的差异越明显

  • miterLimit可以控制尖角的长度,超过限制会自动转为bevel

  • 选择哪种样式取决于你想要表达的视觉效果

4. miterLimit - 尖角限制

项目内容
属性/方法ctx.miterLimit
含义设置尖角的最大长度比例
词源miter(尖角) + limit(限制)- 尖角限制
参数及说明正数值(默认10.0)
默认值10.0

完整示例

<!DOCTYPE html>
<html>
<head>
    <title>miterLimit 示例</title>
</head>
<body>
    <canvas id="myCanvas" width="600" height="350" style="border:1px solid #ccc;"></canvas>
    
    <script>
        const canvas = document.getElementById('myCanvas');
        const ctx = canvas.getContext('2d');
        
        ctx.lineWidth = 10;
        
        // 左侧:默认 miterLimit (10) - 未超限
        ctx.strokeStyle = "blue";
        ctx.lineJoin = "miter";
        ctx.miterLimit = 10;  // 默认值
        
        ctx.beginPath();
        ctx.moveTo(100, 80);
        ctx.lineTo(150, 30);  // 较大的角度
        ctx.lineTo(200, 80);
        ctx.stroke();
        
        // 中间:减小 miterLimit - 超限转为 bevel
        ctx.strokeStyle = "red";
        ctx.miterLimit = 2;   // 设置较小的限制
        
        ctx.beginPath();
        ctx.moveTo(250, 80);
        ctx.lineTo(300, 30);  // 相同角度
        ctx.lineTo(350, 80);
        ctx.stroke();
        
        // 右侧:极小的角度,默认 miterLimit 也会超限
        ctx.strokeStyle = "green";
        ctx.miterLimit = 10;   // 恢复默认
        
        ctx.beginPath();
        ctx.moveTo(400, 80);
        ctx.lineTo(450, 10);  // 非常小的角度
        ctx.lineTo(500, 80);
        ctx.stroke();
        
        // 绘制第二行:演示 miterLimit 的临界值
        ctx.strokeStyle = "purple";
        ctx.lineJoin = "miter";
        
        // 不同 miterLimit 值对比
        const limits = [1.5, 3, 5, 10];
        const angles = [160, 140, 120, 100];  // 角度越小,尖角越长
        
        for (let i = 0; i < 4; i++) {
            ctx.miterLimit = limits[i];
            ctx.beginPath();
            ctx.moveTo(100 + i * 120, 200);
            ctx.lineTo(150 + i * 120, 250);
            ctx.lineTo(200 + i * 120, 200);
            ctx.stroke();
            
            ctx.font = "12px Arial";
            ctx.fillText(`limit=${limits[i]}`, 100 + i * 120, 180);
        }
        
        ctx.font = "14px Arial";
        ctx.fillStyle = "black";
        ctx.fillText("默认miterLimit=10", 100, 50);
        ctx.fillText("miterLimit=2 (转为bevel)", 250, 50);
        ctx.fillText("角度极小 (转为bevel)", 400, 50);
        ctx.fillText("miterLimit对尖角的影响", 100, 280);
    </script>
</body>
</html>

绘制效果

  • 第一行:展示了不同 miterLimit 设置对尖角的影响

  • 第二行:对比不同 miterLimit 值的临界情况

示例分析:miterLimit 控制尖角的最大允许长度(相对于线宽的比例):

  • 当实际尖角长度 ≤ miterLimit × 线宽时,保持尖角

  • 当实际尖角长度 > miterLimit × 线宽时,自动转为 bevel 斜角

  • 线条角度越小,尖角越长,越容易超过限制

5. setLineDash() 和 lineDashOffset - 虚线样式

项目内容
属性/方法ctx.setLineDash(segments)
ctx.lineDashOffset
含义设置虚线的线段与间隙长度,以及偏移量
词源line(线) + dash(破折号)- 虚线
参数及说明setLineDash:数组,偶数个数值,交替表示线段和间隙的长度
lineDashOffset:数值,表示虚线的起始偏移
默认值setLineDash:空数组(实线)
lineDashOffset:0

完整示例

<!DOCTYPE html>
<html>
<head>
    <title>虚线样式示例</title>
    <style>
        canvas { border: 1px solid #ccc; margin: 10px; }
    </style>
</head>
<body>
    <canvas id="myCanvas" width="600" height="450" style="border:1px solid #ccc;"></canvas>
    
    <script>
        const canvas = document.getElementById('myCanvas');
        const ctx = canvas.getContext('2d');
        
        ctx.lineWidth = 3;
        ctx.strokeStyle = "#333";
        
        // 1. 实线(参考)
        ctx.beginPath();
        ctx.moveTo(50, 40);
        ctx.lineTo(550, 40);
        ctx.stroke();
        ctx.fillText("实线", 50, 30);
        
        // 2. 简单虚线 [10, 5] - 画10px,空5px
        ctx.setLineDash([10, 5]);
        ctx.beginPath();
        ctx.moveTo(50, 80);
        ctx.lineTo(550, 80);
        ctx.stroke();
        ctx.fillText("[10, 5] 虚线", 50, 70);
        
        // 3. 点线 [2, 5] - 画2px,空5px
        ctx.setLineDash([2, 5]);
        ctx.beginPath();
        ctx.moveTo(50, 120);
        ctx.lineTo(550, 120);
        ctx.stroke();
        ctx.fillText("[2, 5] 点线", 50, 110);
        
        // 4. 长划线模式 [20, 5, 5, 5] - 20长划,5空,5短划,5空
        ctx.setLineDash([20, 5, 5, 5]);
        ctx.beginPath();
        ctx.moveTo(50, 160);
        ctx.lineTo(550, 160);
        ctx.stroke();
        ctx.fillText("[20,5,5,5] 复合虚线", 50, 150);
        
        // 5. 虚线偏移演示
        ctx.setLineDash([10, 10]);
        
        // 偏移0
        ctx.strokeStyle = "blue";
        ctx.lineDashOffset = 0;
        ctx.beginPath();
        ctx.moveTo(50, 200);
        ctx.lineTo(550, 200);
        ctx.stroke();
        ctx.fillText("偏移0", 50, 190);
        
        // 偏移5
        ctx.strokeStyle = "red";
        ctx.lineDashOffset = 5;
        ctx.beginPath();
        ctx.moveTo(50, 240);
        ctx.lineTo(550, 240);
        ctx.stroke();
        ctx.fillText("偏移5", 50, 230);
        
        // 偏移10
        ctx.strokeStyle = "green";
        ctx.lineDashOffset = 10;
        ctx.beginPath();
        ctx.moveTo(50, 280);
        ctx.lineTo(550, 280);
        ctx.stroke();
        ctx.fillText("偏移10", 50, 270);
        
        // 6. 矩形使用虚线
        ctx.setLineDash([15, 8]);
        ctx.strokeStyle = "purple";
        ctx.lineWidth = 4;
        ctx.strokeRect(200, 320, 200, 80);
        ctx.fillText("虚线矩形", 200, 310);
        
        // 7. 重置为实线
        ctx.setLineDash([]);
        
        // 添加说明
        ctx.font = "14px Arial";
        ctx.fillStyle = "black";
        ctx.fillText("虚线偏移使图案整体移动", 350, 420);
    </script>
</body>
</html>

绘制效果

  • 前四条线展示不同的虚线模式

  • 中间三条蓝色、红色、绿色线展示偏移效果

  • 底部紫色虚线矩形

示例分析

  • setLineDash 数组中的数值交替表示"画线长度"和"空白长度"

  • 数组元素个数为偶数,可以组合出丰富的虚线图案

  • lineDashOffset 控制虚线起始位置,可用于动画效果(如蚂蚁线)

  • 使用 setLineDash([]) 可恢复实线


四、阴影效果

阴影能让图形从平面中"站"起来,增强立体感和层次感。

阴影属性综合示例

项目内容
属性/方法ctx.shadowColorctx.shadowBlurctx.shadowOffsetXctx.shadowOffsetY
含义设置阴影的颜色、模糊程度、偏移量
词源shadow(阴影) + color/blur/offset - 阴影属性
参数说明shadowColor:CSS颜色值
shadowBlur:非负数值,越大越模糊
shadowOffsetX/Y:数值,正右/下,负左/上
默认值shadowColor:透明黑
shadowBlur:0
shadowOffsetX/Y:0

完整示例

<!DOCTYPE html>
<html>
<head>
    <title>阴影效果示例</title>
</head>
<body>
    <canvas id="myCanvas" width="700" height="500" style="border:1px solid #ccc;"></canvas>
    
    <script>
        const canvas = document.getElementById('myCanvas');
        const ctx = canvas.getContext('2d');
        
        // 清除阴影设置函数
        function resetShadow() {
            ctx.shadowColor = "transparent";
            ctx.shadowBlur = 0;
            ctx.shadowOffsetX = 0;
            ctx.shadowOffsetY = 0;
        }
        
        // 1. 基本阴影
        ctx.shadowColor = "gray";
        ctx.shadowBlur = 5;
        ctx.shadowOffsetX = 5;
        ctx.shadowOffsetY = 5;
        
        ctx.fillStyle = "blue";
        ctx.fillRect(50, 50, 100, 100);
        
        // 添加说明
        ctx.shadowColor = "transparent";  // 临时关闭阴影写文字
        ctx.fillStyle = "black";
        ctx.font = "14px Arial";
        ctx.fillText("基本阴影", 50, 40);
        
        // 2. 彩色阴影
        ctx.shadowColor = "rgba(255, 0, 0, 0.6)";
        ctx.shadowBlur = 10;
        ctx.shadowOffsetX = 8;
        ctx.shadowOffsetY = 8;
        
        ctx.fillStyle = "green";
        ctx.fillRect(200, 50, 100, 100);
        
        ctx.shadowColor = "transparent";
        ctx.fillStyle = "black";
        ctx.fillText("彩色阴影", 200, 40);
        
        // 3. 不同模糊度对比
        const blurValues = [0, 5, 15, 30];
        const colors = ["red", "orange", "purple", "brown"];
        
        for (let i = 0; i < blurValues.length; i++) {
            ctx.shadowColor = "black";
            ctx.shadowBlur = blurValues[i];
            ctx.shadowOffsetX = 5;
            ctx.shadowOffsetY = 5;
            
            ctx.fillStyle = colors[i];
            ctx.fillRect(50 + i * 120, 200, 80, 80);
            
            ctx.shadowColor = "transparent";
            ctx.fillStyle = "black";
            ctx.fillText(`模糊${blurValues[i]}`, 50 + i * 120, 190);
        }
        
        // 4. 不同偏移量对比
        const offsets = [
            [5, 5],    // 右下
            [-5, 5],   // 左下
            [5, -5],   // 右上
            [-5, -5]   // 左上
        ];
        
        for (let i = 0; i < offsets.length; i++) {
            ctx.shadowColor = "rgba(0, 0, 0, 0.5)";
            ctx.shadowBlur = 8;
            ctx.shadowOffsetX = offsets[i][0];
            ctx.shadowOffsetY = offsets[i][1];
            
            ctx.fillStyle = "teal";
            ctx.fillRect(50 + i * 120, 320, 70, 70);
            
            ctx.shadowColor = "transparent";
            ctx.fillStyle = "black";
            ctx.fillText(`偏移${offsets[i]}`, 50 + i * 120, 310);
        }
        
        // 5. 文字阴影
        resetShadow();
        ctx.shadowColor = "gray";
        ctx.shadowBlur = 3;
        ctx.shadowOffsetX = 3;
        ctx.shadowOffsetY = 3;
        
        ctx.font = "bold 30px Arial";
        ctx.fillStyle = "blue";
        ctx.fillText("带阴影的文字", 350, 400);
        
        // 6. 关闭阴影
        resetShadow();
        ctx.fillStyle = "black";
        ctx.font = "16px Arial";
        ctx.fillText("阴影已关闭", 350, 450);
    </script>
</body>
</html>

绘制效果

  • 左上角:蓝色矩形带灰色阴影

  • 右上角:绿色矩形带半透明红色阴影

  • 中间行:不同模糊度对比(0,5,15,30)

  • 底部行:不同偏移方向对比(右下、左下、右上、左上)

  • 底部:带阴影的文字

示例分析

  • 阴影颜色:可以是半透明色,产生柔和效果

  • 模糊度:值越大,阴影边缘越扩散模糊

  • 偏移量:正负值控制阴影方向

  • 阴影会影响所有绘制操作,用完需要关闭(设为透明或偏移0)


五、渐变与图案

渐变和图案是 fillStyle 和 strokeStyle 的高级形式,能创造更丰富的视觉效果。

1. createLinearGradient() - 线性渐变

项目内容
属性/方法ctx.createLinearGradient(x0, y0, x1, y1)
含义创建线性渐变对象
词源linear(线性的) + gradient(渐变)- 线性渐变
参数及说明(x0,y0):渐变起点坐标
(x1,y1):渐变终点坐标
返回线性渐变对象

完整示例

<!DOCTYPE html>
<html>
<head>
    <title>线性渐变示例</title>
    <meta charset="UTF-8">
</head>
<body>
    <canvas id="myCanvas" width="700" height="500" style="border:1px solid #ccc;"></canvas>
    
    <script>
        const canvas = document.getElementById('myCanvas');
        const ctx = canvas.getContext('2d');
        
        // 1. 水平渐变
        const gradient1 = ctx.createLinearGradient(50, 100, 250, 100);
        gradient1.addColorStop(0, "red");
        gradient1.addColorStop(0.5, "yellow");
        gradient1.addColorStop(1, "blue");
        
        ctx.fillStyle = gradient1;
        ctx.fillRect(50, 50, 200, 80);
        ctx.fillStyle = "black";
        ctx.fillText("水平渐变", 50, 40);
        
        // 2. 垂直渐变
        const gradient2 = ctx.createLinearGradient(300, 50, 300, 150);
        gradient2.addColorStop(0, "purple");
        gradient2.addColorStop(0.3, "pink");
        gradient2.addColorStop(0.7, "orange");
        gradient2.addColorStop(1, "yellow");
        
        ctx.fillStyle = gradient2;
        ctx.fillRect(300, 50, 150, 100);
        ctx.fillStyle = "black";
        ctx.fillText("垂直渐变", 300, 40);
        
        // 3. 对角线渐变
        const gradient3 = ctx.createLinearGradient(50, 200, 200, 300);
        gradient3.addColorStop(0, "rgba(255,0,0,1)");
        gradient3.addColorStop(0.5, "rgba(0,255,0,0.5)");
        gradient3.addColorStop(1, "rgba(0,0,255,1)");
        
        ctx.fillStyle = gradient3;
        ctx.fillRect(50, 200, 200, 150);
        ctx.fillStyle = "black";
        ctx.fillText("对角线渐变", 50, 190);
        
        // 4. 多色渐变
        const gradient4 = ctx.createLinearGradient(300, 200, 500, 350);
        gradient4.addColorStop(0, "red");
        gradient4.addColorStop(0.2, "orange");
        gradient4.addColorStop(0.4, "yellow");
        gradient4.addColorStop(0.6, "green");
        gradient4.addColorStop(0.8, "blue");
        gradient4.addColorStop(1, "purple");
        
        ctx.fillStyle = gradient4;
        ctx.fillRect(300, 200, 200, 150);
        ctx.fillStyle = "black";
        ctx.fillText("彩虹渐变", 300, 190);
        
        // 5. 描边渐变
        const gradient5 = ctx.createLinearGradient(50, 400, 300, 400);
        gradient5.addColorStop(0, "gold");
        gradient5.addColorStop(0.5, "white");
        gradient5.addColorStop(1, "gold");
        
        ctx.lineWidth = 8;
        ctx.strokeStyle = gradient5;
        ctx.strokeRect(50, 380, 250, 80);
        ctx.fillStyle = "black";
        ctx.fillText("描边渐变", 50, 370);
        
        // 6. 透明渐变
        const gradient6 = ctx.createLinearGradient(350, 380, 600, 380);
        gradient6.addColorStop(0, "rgba(255,0,0,1)");
        gradient6.addColorStop(0.3, "rgba(255,0,0,0.7)");
        gradient6.addColorStop(0.6, "rgba(255,0,0,0.3)");
        gradient6.addColorStop(1, "rgba(255,0,0,0)");
        
        ctx.fillStyle = gradient6;
        ctx.fillRect(350, 380, 250, 80);
        ctx.fillStyle = "black";
        ctx.fillText("透明渐变", 350, 370);
    </script>
</body>
</html>

绘制效果

  • 第一行:水平渐变(红→黄→蓝)

  • 第二行:垂直渐变(紫→粉→橙→黄)

  • 第三行:对角线渐变(红→绿→蓝,带透明度)

  • 第四行:彩虹渐变(六色)

  • 第五行:描边渐变(金色光泽效果)

  • 第六行:透明渐变(红色逐渐消失)

示例分析

  • addColorStop(offset, color):offset 0~1 表示位置,color 为该位置的颜色

  • 渐变方向由起点和终点坐标决定

  • 可以设置多个颜色断点,实现复杂渐变

  • 渐变可以用于 fillStyle 或 strokeStyle

2. createRadialGradient() - 径向渐变

项目内容
属性/方法ctx.createRadialGradient(x0, y0, r0, x1, y1, r1)
含义创建径向(圆形)渐变对象
词源radial(放射状的) + gradient(渐变)- 放射渐变
参数及说明(x0,y0,r0):起始圆的圆心和半径
(x1,y1,r1):结束圆的圆心和半径
返回径向渐变对象

完整示例

<!DOCTYPE html>
<html>
<head>
    <title>径向渐变示例</title>
</head>
<body>
    <canvas id="myCanvas" width="700" height="500" style="border:1px solid #ccc;"></canvas>
    
    <script>
        const canvas = document.getElementById('myCanvas');
        const ctx = canvas.getContext('2d');
        
        // 1. 从中心向外扩散(同心圆)
        const gradient1 = ctx.createRadialGradient(150, 100, 10, 150, 100, 80);
        gradient1.addColorStop(0, "white");
        gradient1.addColorStop(0.5, "yellow");
        gradient1.addColorStop(1, "orange");
        
        ctx.beginPath();
        ctx.arc(150, 100, 80, 0, Math.PI * 2);
        ctx.fillStyle = gradient1;
        ctx.fill();
        ctx.fillStyle = "black";
        ctx.fillText("中心向外扩散", 100, 30);
        
        // 2. 立体球效果
        const gradient2 = ctx.createRadialGradient(400, 100, 20, 400, 100, 80);
        gradient2.addColorStop(0, "#FFF");
        gradient2.addColorStop(0.3, "#00F");
        gradient2.addColorStop(0.6, "#00A");
        gradient2.addColorStop(1, "#006");
        
        ctx.beginPath();
        ctx.arc(400, 100, 80, 0, Math.PI * 2);
        ctx.fillStyle = gradient2;
        ctx.fill();
        ctx.fillStyle = "black";
        ctx.fillText("立体球效果", 350, 30);
        
        // 3. 偏移光效
        const gradient3 = ctx.createRadialGradient(200, 280, 20, 200, 280, 120);
        gradient3.addColorStop(0, "rgba(255,255,255,1)");
        gradient3.addColorStop(0.4, "rgba(100,150,255,0.8)");
        gradient3.addColorStop(0.8, "rgba(0,0,100,0.5)");
        gradient3.addColorStop(1, "rgba(0,0,0,0)");
        
        ctx.fillStyle = gradient3;
        ctx.fillRect(100, 200, 200, 150);
        ctx.fillStyle = "black";
        ctx.fillText("光晕效果", 150, 190);
        
        // 4. 非同心圆渐变
        const gradient4 = ctx.createRadialGradient(450, 280, 20, 420, 260, 100);
        gradient4.addColorStop(0, "red");
        gradient4.addColorStop(0.5, "yellow");
        gradient4.addColorStop(1, "blue");
        
        ctx.beginPath();
        ctx.arc(450, 280, 100, 0, Math.PI * 2);
        ctx.fillStyle = gradient4;
        ctx.fill();
        ctx.fillStyle = "black";
        ctx.fillText("偏移圆心渐变", 400, 190);
        
        // 5. 多重径向渐变叠加
        // 第一个圆形
        const gradient5a = ctx.createRadialGradient(200, 400, 10, 200, 400, 60);
        gradient5a.addColorStop(0, "#FF0");
        gradient5a.addColorStop(1, "#F00");
        
        ctx.beginPath();
        ctx.arc(200, 400, 60, 0, Math.PI * 2);
        ctx.fillStyle = gradient5a;
        ctx.fill();
        
        // 第二个圆形
        const gradient5b = ctx.createRadialGradient(300, 400, 10, 300, 400, 60);
        gradient5b.addColorStop(0, "#0FF");
        gradient5b.addColorStop(1, "#00F");
        
        ctx.beginPath();
        ctx.arc(300, 400, 60, 0, Math.PI * 2);
        ctx.fillStyle = gradient5b;
        ctx.fill();
        
        // 重叠区域
        ctx.fillStyle = "black";
        ctx.fillText("径向渐变叠加", 200, 330);
        
        // 6. 描边使用径向渐变
        const gradient6 = ctx.createRadialGradient(550, 400, 10, 550, 400, 70);
        gradient6.addColorStop(0, "white");
        gradient6.addColorStop(0.5, "gold");
        gradient6.addColorStop(1, "orange");
        
        ctx.lineWidth = 8;
        ctx.strokeStyle = gradient6;
        ctx.beginPath();
        ctx.arc(550, 400, 60, 0, Math.PI * 2);
        ctx.stroke();
        ctx.fillText("描边径向渐变", 500, 330);
    </script>
</body>
</html>

绘制效果

  • 左上:同心圆渐变(白→黄→橙)

  • 右上:立体球效果(有高光)

  • 左中:光晕效果(透明渐变)

  • 右中:偏移圆心渐变(颜色分布不均匀)

  • 左下:两个径向渐变叠加

  • 右下:描边使用径向渐变

示例分析

  • 径向渐变可以创建逼真的立体感(球体)

  • 两个圆心可以不同位置,产生不对称效果

  • 结合透明度可以制作光晕、发光效果

  • 可同时用于填充和描边

3. createPattern() - 图案填充

项目内容
属性/方法ctx.createPattern(image, repetition)
含义创建图案对象,用图像填充图形
词源pattern(图案)- 图案
参数及说明image:图像源(Image对象、canvas元素等)
repetition:重复模式
重复模式"repeat"(默认)、"repeat-x""repeat-y""no-repeat"

完整示例

<!DOCTYPE html>
<html>
<head>
    <title>createPattern 四种重复模式对比</title>
    <style>
        body { font-family: Arial; text-align: center; }
        .container { 
            display: grid; 
            grid-template-columns: repeat(2, 1fr);
            gap: 10px;
            max-width: 650px;
            margin: 0 auto;
        }
        canvas { border: 1px solid #ccc; }
    </style>
</head>
<body>
    <h3>createPattern 四种重复模式对比</h3>
    <div class="container">
        <div>
            <canvas id="canvasRepeat" width="250" height="250"></canvas>
            <p><strong>repeat</strong> (双向平铺)</p>
        </div>
        <div>
            <canvas id="canvasRepeatX" width="250" height="250"></canvas>
            <p><strong>repeat-x</strong> (水平平铺)</p>
        </div>
        <div>
            <canvas id="canvasRepeatY" width="250" height="250"></canvas>
            <p><strong>repeat-y</strong> (垂直平铺)</p>
        </div>
        <div>
            <canvas id="canvasNoRepeat" width="250" height="250"></canvas>
            <p><strong>no-repeat</strong> (不重复)</p>
        </div>
    </div>
    
    <script>
        // 创建统一的图案源
        function createPatternSource() {
            const canvas = document.createElement('canvas');
            canvas.width = 30;
            canvas.height = 30;
            const ctx = canvas.getContext('2d');
            
            // 绘制一个醒目的笑脸图案(更容易识别)
            ctx.fillStyle = "#FFD700";  // 金色背景
            ctx.fillRect(0, 0, 30, 30);
            
            // 眼睛
            ctx.fillStyle = "#000";
            ctx.beginPath();
            ctx.arc(8, 10, 3, 0, Math.PI * 2);
            ctx.fill();
            ctx.beginPath();
            ctx.arc(22, 10, 3, 0, Math.PI * 2);
            ctx.fill();
            
            // 嘴巴
            ctx.beginPath();
            ctx.strokeStyle = "#000";
            ctx.lineWidth = 2;
            ctx.arc(15, 18, 8, 0, Math.PI);
            ctx.stroke();
            
            // 边框
            ctx.strokeStyle = "#333";
            ctx.lineWidth = 1;
            ctx.strokeRect(0, 0, 30, 30);
            
            return canvas;
        }
        
        const patternSource = createPatternSource();
        
        // repeat - 双向平铺
        const ctx1 = document.getElementById('canvasRepeat').getContext('2d');
        const pattern1 = ctx1.createPattern(patternSource, "repeat");
        ctx1.fillStyle = pattern1;
        ctx1.fillRect(25, 25, 200, 200);
        ctx1.fillStyle = "#333";
        ctx1.font = "14px Arial";
        ctx1.fillText("repeat", 100, 20);
        
        // repeat-x - 水平平铺
        const ctx2 = document.getElementById('canvasRepeatX').getContext('2d');
        const pattern2 = ctx2.createPattern(patternSource, "repeat-x");
        ctx2.fillStyle = pattern2;
        ctx2.fillRect(25, 25, 200, 200);
        ctx2.fillStyle = "#333";
        ctx2.fillText("repeat-x", 95, 20);
        
        // repeat-y - 垂直平铺
        const ctx3 = document.getElementById('canvasRepeatY').getContext('2d');
        const pattern3 = ctx3.createPattern(patternSource, "repeat-y");
        ctx3.fillStyle = pattern3;
        ctx3.fillRect(25, 25, 200, 200);
        ctx3.fillStyle = "#333";
        ctx3.fillText("repeat-y", 95, 20);
        
        // no-repeat - 不重复
        const ctx4 = document.getElementById('canvasNoRepeat').getContext('2d');
        const pattern4 = ctx4.createPattern(patternSource, "no-repeat");
        ctx4.fillStyle = pattern4;
        ctx4.fillRect(25, 25, 200, 200);
        ctx4.fillStyle = "#333";
        ctx4.fillText("no-repeat", 90, 20);
        
        // 在所有画布底部画出图案源
        [ctx1, ctx2, ctx3, ctx4].forEach((ctx, index) => {
            ctx.fillStyle = "#666";
            ctx.font = "12px Arial";
            ctx.fillText("图案源:", 25, 230);
            ctx.drawImage(patternSource, 80, 215, 25, 25);
        });
    </script>
    
    <div style="margin-top:20px; text-align:left; max-width:600px; margin:20px auto;">
        <h4>四种模式的区别:</h4>
        <ul>
            <li><strong>repeat(双向平铺)</strong>:图案铺满整个区域,像墙纸一样</li>
            <li><strong>repeat-x(水平平铺)</strong>:只有一行图案水平重复,垂直方向拉伸</li>
            <li><strong>repeat-y(垂直平铺)</strong>:只有一列图案垂直重复,水平方向拉伸</li>
            <li><strong>no-repeat(不重复)</strong>:只在左上角画一次图案,其余空白</li>
        </ul>
        <p>💡 使用醒目的笑脸图案,让重复模式一目了然</p>
    </div>
</body>
</html>

绘制效果

  • 四个画布并排显示,每个200x200区域

  • 使用醒目的笑脸图案(30x30)作为图案源

  • repeat:整个区域布满笑脸

  • repeat-x:顶部一行笑脸,下面空白

  • repeat-y:左侧一列笑脸,右边空白

  • no-repeat:左上角一个笑脸,其余空白

示例分析

模式视觉特征适用场景
repeat图案铺满整个区域背景纹理、墙纸效果
repeat-x只有一行水平重复水平条纹装饰、分隔线
repeat-y只有一列垂直重复垂直条纹装饰、边框
no-repeat只出现一次徽标、单个图标

关键点

  • 图案源可以是canvas、图片、视频等

  • 图案大小由源决定,填充区域自动重复

  • 使用醒目的图案(如笑脸)更容易看出重复模式

  • 四种模式满足不同的纹理需求


六、文本样式

Canvas 提供了丰富的文本渲染和样式控制。

1. font - 字体设置

项目内容
属性/方法ctx.font
含义设置文本的字体样式
词源font(字体)- 字体
参数及说明CSS font 属性的简写格式
默认值"10px sans-serif"

完整示例

<!DOCTYPE html>
<html>
<head>
    <title>字体样式示例</title>
</head>
<body>
    <canvas id="myCanvas" width="700" height="400" style="border:1px solid #ccc;"></canvas>
    
    <script>
        const canvas = document.getElementById('myCanvas');
        const ctx = canvas.getContext('2d');
        
        // 绘制背景网格
        ctx.strokeStyle = "#eee";
        ctx.lineWidth = 0.5;
        for (let i = 0; i < 700; i += 20) {
            ctx.beginPath();
            ctx.moveTo(i, 0);
            ctx.lineTo(i, 400);
            ctx.stroke();
        }
        
        // 1. 基本字体
        ctx.font = "20px Arial";
        ctx.fillStyle = "#333";
        ctx.fillText("Arial 20px", 50, 50);
        
        // 2. 粗体
        ctx.font = "bold 24px 'Microsoft YaHei', sans-serif";
        ctx.fillStyle = "blue";
        ctx.fillText("微软雅黑 粗体 24px", 50, 100);
        
        // 3. 斜体
        ctx.font = "italic 22px Georgia, serif";
        ctx.fillStyle = "green";
        ctx.fillText("Georgia 斜体 22px", 50, 150);
        
        // 4. 粗斜体
        ctx.font = "bold italic 26px 'Times New Roman', serif";
        ctx.fillStyle = "purple";
        ctx.fillText("Times 粗斜体 26px", 50, 200);
        
        // 5. 不同字号对比
        const sizes = [12, 16, 20, 24, 28, 32];
        sizes.forEach((size, index) => {
            ctx.font = `${size}px Arial`;
            ctx.fillStyle = "#e67e22";
            ctx.fillText(`字号 ${size}px`, 400, 50 + index * 35);
        });
        
        // 6. 描边文字
        ctx.font = "bold 40px Arial";
        ctx.strokeStyle = "red";
        ctx.lineWidth = 2;
        ctx.strokeText("描边文字", 400, 280);
        
        // 7. 填充+描边
        ctx.font = "bold 40px Arial";
        ctx.fillStyle = "yellow";
        ctx.strokeStyle = "black";
        ctx.lineWidth = 3;
        ctx.fillText("双重效果", 400, 350);
        ctx.strokeText("双重效果", 400, 350);
        
        // 添加说明
        ctx.font = "14px Arial";
        ctx.fillStyle = "black";
        ctx.fillText("字体样式示例", 50, 320);
        ctx.fillText("fillText() 填充文字", 50, 350);
        ctx.fillText("strokeText() 描边文字", 50, 380);
    </script>
</body>
</html>

绘制效果

  • 左侧从上到下展示不同字体组合

  • 右侧展示不同字号对比

  • 底部展示描边文字和双重效果文字

示例分析

  • font 属性使用 CSS 字体简写格式

  • 可以组合 bold、italic 等样式

  • fillText() 填充文字内部

  • strokeText() 只描边文字轮廓

  • 两者结合可创建双重效果

2. textAlign - 水平对齐

项目内容
属性/方法ctx.textAlign
含义设置文本的水平对齐方式
词源text(文本) + align(对齐)- 文本对齐
参数及说明leftrightcenterstart(默认)、end
默认值start

完整示例

<!DOCTYPE html>
<html>
<head>
    <title>文本对齐示例</title>
</head>
<body>
    <canvas id="myCanvas" width="600" height="350" style="border:1px solid #ccc;"></canvas>
    
    <script>
        const canvas = document.getElementById('myCanvas');
        const ctx = canvas.getContext('2d');
        
        // 绘制垂直参考线
        ctx.strokeStyle = "red";
        ctx.lineWidth = 1;
        ctx.beginPath();
        ctx.moveTo(300, 30);
        ctx.lineTo(300, 300);
        ctx.stroke();
        
        // 绘制水平参考线
        ctx.strokeStyle = "#ccc";
        ctx.lineWidth = 0.5;
        for (let y = 80; y <= 280; y += 50) {
            ctx.beginPath();
            ctx.moveTo(50, y);
            ctx.lineTo(550, y);
            ctx.stroke();
        }
        
        ctx.font = "20px Arial";
        
        // left对齐
        ctx.textAlign = "left";
        ctx.fillStyle = "blue";
        ctx.fillText("left对齐", 300, 80);
        
        // center对齐
        ctx.textAlign = "center";
        ctx.fillStyle = "green";
        ctx.fillText("center对齐", 300, 130);
        
        // right对齐
        ctx.textAlign = "right";
        ctx.fillStyle = "purple";
        ctx.fillText("right对齐", 300, 180);
        
        // start对齐(从左到右文字环境下=left)
        ctx.textAlign = "start";
        ctx.fillStyle = "orange";
        ctx.fillText("start对齐", 300, 230);
        
        // end对齐(从左到右文字环境下=right)
        ctx.textAlign = "end";
        ctx.fillStyle = "brown";
        ctx.fillText("end对齐", 300, 280);
        
        // 添加说明
        ctx.textAlign = "left";
        ctx.font = "14px Arial";
        ctx.fillStyle = "black";
        ctx.fillText("红色垂直线为对齐参考线", 50, 320);
        ctx.fillText("各文本以此线为基准对齐", 50, 340);
    </script>
</body>
</html>

绘制效果

  • 红色垂直线为参考线

  • 五条水平线展示不同对齐方式

  • left 在参考线左侧,center 居中,right 在右侧

示例分析

  • left:文本左边缘对齐参考点

  • center:文本中心对齐参考点

  • right:文本右边缘对齐参考点

  • start:在 LTR 语言中同 left

  • end:在 LTR 语言中同 right

3. textBaseline - 垂直对齐

项目内容
属性/方法ctx.textBaseline
含义设置文本的垂直基线对齐方式
词源text(文本) + baseline(基线)- 文本基线
参数及说明tophangingmiddlealphabetic(默认)、ideographicbottom

完整示例

<!DOCTYPE html>
<html>
<head>
    <title>textBaseline 最简单直观的示例</title>
    <style>
        body { font-family: Arial; }
        canvas { border: 1px solid #333; margin: 10px; }
    </style>
</head>
<body>
    <h3>textBaseline 最简单直观的示例</h3>
    <canvas id="myCanvas" width="600" height="400"></canvas>
    
    <script>
        const canvas = document.getElementById('myCanvas');
        const ctx = canvas.getContext('2d');
        
        // === 1. 先画一条粗粗的红色横线(参考线)===
        ctx.strokeStyle = "red";
        ctx.lineWidth = 3;
        ctx.beginPath();
        ctx.moveTo(50, 200);
        ctx.lineTo(550, 200);
        ctx.stroke();
        
        // 在线上画一个红点,强调这个位置
        ctx.fillStyle = "red";
        ctx.beginPath();
        ctx.arc(100, 200, 6, 0, Math.PI * 2);
        ctx.fill();
        
        // 添加说明文字
        ctx.font = "16px Arial";
        ctx.fillStyle = "red";
        ctx.fillText("← 这条红线就是参考线", 120, 180);
        ctx.fillText("红点位置 = (100, 200)", 120, 210);
        
        // === 2. 现在用不同的基线来写文字 ===
        ctx.font = "30px Arial";  // 大字号,看得清楚
        
        // 2.1 top - 顶部对齐
        ctx.textBaseline = "top";
        ctx.fillStyle = "blue";
        ctx.fillText("① top", 100, 200);
        ctx.fillStyle = "black";
        ctx.font = "14px Arial";
        ctx.fillText("文字顶部对齐红线", 100, 160);
        
        // 2.2 middle - 中部对齐
        ctx.textBaseline = "middle";
        ctx.fillStyle = "green";
        ctx.fillText("② middle", 200, 200);
        ctx.fillStyle = "black";
        ctx.fillText("文字中间对齐红线", 200, 160);
        
        // 2.3 alphabetic - 字母基线(默认)
        ctx.textBaseline = "alphabetic";
        ctx.fillStyle = "purple";
        ctx.fillText("③ alphabetic", 300, 200);
        ctx.fillStyle = "black";
        ctx.fillText("字母底部对齐红线", 300, 160);
        
        // 2.4 bottom - 底部对齐
        ctx.textBaseline = "bottom";
        ctx.fillStyle = "orange";
        ctx.fillText("④ bottom", 420, 200);
        ctx.fillStyle = "black";
        ctx.fillText("文字底部对齐红线", 420, 160);
        
        // === 3. 画几条辅助线,显示每个文字的范围 ===
        ctx.strokeStyle = "#ccc";
        ctx.lineWidth = 0.5;
        ctx.setLineDash([5, 5]);
        
        // top文字的范围
        ctx.beginPath();
        ctx.moveTo(100, 200);
        ctx.lineTo(100, 200-30);
        ctx.stroke();
        
        // middle文字的范围
        ctx.beginPath();
        ctx.moveTo(200, 200-15);
        ctx.lineTo(200, 200+15);
        ctx.stroke();
        
        // alphabetic文字的范围
        ctx.beginPath();
        ctx.moveTo(300, 200-22);
        ctx.lineTo(300, 200+8);
        ctx.stroke();
        
        // bottom文字的范围
        ctx.beginPath();
        ctx.moveTo(420, 200-30);
        ctx.lineTo(420, 200);
        ctx.stroke();
        
        ctx.setLineDash([]);
        
        // === 4. 添加最终解释 ===
        ctx.font = "bold 18px Arial";
        ctx.fillStyle = "#333";
        ctx.fillText("【观察重点】", 50, 300);
        
        ctx.font = "16px Arial";
        ctx.fillText("① top:文字顶部正好在红线上(文字在线的下面)", 50, 330);
        ctx.fillText("② middle:文字中间正好在红线上(线穿过文字中间)", 50, 355);
        ctx.fillText("③ alphabetic:字母底部正好在红线上(像写英文的本子)", 50, 380);
        ctx.fillText("④ bottom:文字底部正好在红线上(文字在线的上面)", 50, 405);
    </script>
</body>
</html>

绘制效果

  • 红色水平线为参考线,红点为参考点

  • 五个文本分别使用 top、hanging、middle、alphabetic、bottom 基线

  • 观察每个文本相对于红点的垂直位置

示例分析

  • top:文本顶部对齐参考点

  • hanging:悬挂基线(用于印度文字等)

  • middle:文本中部对齐参考点

  • alphabetic:默认,字母基线(英文底部对齐)

  • bottom:文本底部对齐参考点

4. 文本度量 - measureText()

项目内容
属性/方法ctx.measureText(text)
含义返回文本的宽度信息
词源measure(测量)- 测量文本
返回TextMetrics 对象,包含文本宽度等度量信息

完整示例

<!DOCTYPE html>
<html>
<head>
    <title>文本度量示例</title>
</head>
<body>
    <canvas id="myCanvas" width="600" height="400" style="border:1px solid #ccc;"></canvas>
    
    <script>
        const canvas = document.getElementById('myCanvas');
        const ctx = canvas.getContext('2d');
        
        const text = "Canvas 度量示例";
        ctx.font = "30px Arial";
        
        // 测量文本宽度
        const metrics = ctx.measureText(text);
        const textWidth = metrics.width;
        
        // 绘制文本
        ctx.fillStyle = "#333";
        ctx.fillText(text, 100, 150);
        
        // 绘制文本边界框
        ctx.strokeStyle = "red";
        ctx.lineWidth = 2;
        ctx.strokeRect(100, 150 - 30, textWidth, 30);  // 假设字体高度约30px
        
        // 标记起点
        ctx.fillStyle = "blue";
        ctx.beginPath();
        ctx.arc(100, 150, 5, 0, Math.PI * 2);
        ctx.fill();
        
        // 显示测量信息
        ctx.font = "16px Arial";
        ctx.fillStyle = "green";
        ctx.fillText(`文本: "${text}"`, 100, 250);
        ctx.fillText(`字体: 30px Arial`, 100, 280);
        ctx.fillText(`测量宽度: ${textWidth.toFixed(2)}px`, 100, 310);
        
        // 演示不同文本的宽度比较
        const texts = [
            { txt: "iiiiii", color: "blue" },
            { txt: "MMMMMM", color: "red" },
            { txt: "WWWWWW", color: "green" }
        ];
        
        ctx.font = "24px Arial";
        texts.forEach((item, index) => {
            const w = ctx.measureText(item.txt).width;
            ctx.fillStyle = item.color;
            ctx.fillText(item.txt, 100 + index * 150, 350);
            
            ctx.fillStyle = "black";
            ctx.font = "12px Arial";
            ctx.fillText(`${w.toFixed(1)}px`, 100 + index * 150, 380);
        });
    </script>
</body>
</html>

绘制效果

  • 主文本显示并绘制边界框

  • 显示测量出的宽度数值

  • 底部对比不同字符的宽度差异

示例分析

  • measureText() 返回文本精确的像素宽度

  • 不同字符宽度不同(如 'i' 和 'W' 差异明显)

  • 可用于动态布局、文本居中、边界检测等

  • 目前主要返回 width 属性,未来可能扩展更多度量


七、综合示例

结合多种样式,创建一个丰富的综合示例:

<!DOCTYPE html>
<html>
<head>
    <title>Canvas 综合样式示例</title>
    <style>
        canvas { border: 1px solid #333; display: block; margin: 20px auto; }
    </style>
</head>
<body>
    <canvas id="demoCanvas" width="800" height="500"></canvas>
    
    <script>
        const canvas = document.getElementById('demoCanvas');
        const ctx = canvas.getContext('2d');
        
        // 1. 背景渐变
        const bgGradient = ctx.createLinearGradient(0, 0, 0, 500);
        bgGradient.addColorStop(0, '#87CEEB');
        bgGradient.addColorStop(0.5, '#B0E0E6');
        bgGradient.addColorStop(1, '#E0F6FF');
        ctx.fillStyle = bgGradient;
        ctx.fillRect(0, 0, 800, 500);
        
        // 2. 标题(带阴影)
        ctx.shadowColor = 'rgba(0,0,0,0.3)';
        ctx.shadowBlur = 10;
        ctx.shadowOffsetX = 5;
        ctx.shadowOffsetY = 5;
        ctx.font = 'bold 40px "Microsoft YaHei", sans-serif';
        ctx.fillStyle = '#2c3e50';
        ctx.fillText('Canvas 综合样式示例', 200, 80);
        
        // 3. 绘制带渐变和阴影的圆形
        ctx.shadowBlur = 15;
        ctx.shadowOffsetX = 8;
        ctx.shadowOffsetY = 8;
        
        const circleGradient = ctx.createRadialGradient(200, 200, 20, 200, 200, 80);
        circleGradient.addColorStop(0, '#FFD700');
        circleGradient.addColorStop(0.7, '#FF8C00');
        circleGradient.addColorStop(1, '#FF4500');
        
        ctx.beginPath();
        ctx.arc(200, 200, 80, 0, Math.PI * 2);
        ctx.fillStyle = circleGradient;
        ctx.fill();
        
        // 白色描边
        ctx.shadowBlur = 10;
        ctx.lineWidth = 5;
        ctx.strokeStyle = '#FFF';
        ctx.stroke();
        
        // 4. 虚线矩形(带不同阴影)
        ctx.shadowColor = 'rgba(0,100,0,0.5)';
        ctx.shadowBlur = 8;
        ctx.shadowOffsetX = 5;
        ctx.shadowOffsetY = 5;
        
        ctx.setLineDash([10, 5]);
        ctx.lineWidth = 4;
        ctx.strokeStyle = '#2E8B57';
        ctx.strokeRect(350, 150, 150, 100);
        
        // 5. 文字样式混合
        ctx.shadowColor = 'rgba(0,0,0,0.2)';
        ctx.shadowBlur = 5;
        ctx.shadowOffsetX = 3;
        ctx.shadowOffsetY = 3;
        
        ctx.font = '24px Arial';
        ctx.fillStyle = '#8B4513';
        ctx.fillText('丰富多彩的', 380, 200);
        
        ctx.font = 'italic 28px "Times New Roman", serif';
        ctx.fillStyle = '#FF69B4';
        ctx.fillText('Canvas样式', 380, 240);
        
        // 6. 图案填充
        ctx.shadowColor = 'transparent';  // 关闭阴影避免影响图案
        
        const patternCanvas = document.createElement('canvas');
        patternCanvas.width = 20;
        patternCanvas.height = 20;
        const pCtx = patternCanvas.getContext('2d');
        
        // 绘制方格图案
        pCtx.fillStyle = '#FFA07A';
        pCtx.fillRect(0, 0, 10, 10);
        pCtx.fillRect(10, 10, 10, 10);
        pCtx.fillStyle = '#CD5C5C';
        pCtx.fillRect(10, 0, 10, 10);
        pCtx.fillRect(0, 10, 10, 10);
        
        const pattern = ctx.createPattern(patternCanvas, 'repeat');
        ctx.fillStyle = pattern;
        ctx.fillRect(550, 150, 100, 100);
        
        // 7. 线条样式演示
        ctx.shadowColor = 'rgba(0,0,0,0.1)';
        ctx.shadowBlur = 5;
        ctx.shadowOffsetX = 2;
        ctx.shadowOffsetY = 2;
        
        // 不同线宽和端点
        ctx.lineWidth = 8;
        ctx.strokeStyle = '#3498db';
        
        ctx.lineCap = 'round';
        ctx.beginPath();
        ctx.moveTo(100, 350);
        ctx.lineTo(250, 350);
        ctx.stroke();
        
        ctx.lineCap = 'square';
        ctx.beginPath();
        ctx.moveTo(100, 380);
        ctx.lineTo(250, 380);
        ctx.stroke();
        
        ctx.lineCap = 'butt';
        ctx.beginPath();
        ctx.moveTo(100, 410);
        ctx.lineTo(250, 410);
        ctx.stroke();
        
        // 8. 虚线动画准备(静态示例)
        ctx.setLineDash([8, 8]);
        ctx.lineWidth = 3;
        ctx.strokeStyle = '#e74c3c';
        ctx.strokeRect(300, 350, 150, 80);
        
        // 9. 文本度量应用 - 居中文本
        const centerText = "居中文本";
        ctx.font = "20px Arial";
        const textWidth = ctx.measureText(centerText).width;
        
        ctx.shadowColor = 'rgba(0,0,0,0.3)';
        ctx.shadowBlur = 4;
        ctx.fillStyle = '#16a085';
        ctx.fillText(centerText, 550 - textWidth/2, 400);
        
        // 10. 图例说明
        ctx.shadowColor = 'transparent';
        ctx.font = '14px Arial';
        ctx.fillStyle = '#333';
        ctx.fillText('径向渐变圆', 150, 300);
        ctx.fillText('虚线矩形', 380, 300);
        ctx.fillText('图案填充', 580, 300);
        ctx.fillText('线条端点样式', 120, 440);
        ctx.fillText('虚线矩形', 320, 450);
        ctx.fillText('度量居中', 520, 420);
        
        // 重置阴影
        ctx.shadowColor = 'transparent';
        ctx.shadowBlur = 0;
        ctx.shadowOffsetX = 0;
        ctx.shadowOffsetY = 0;
        ctx.setLineDash([]);
    </script>
</body>
</html>

绘制效果

  • 渐变背景

  • 带阴影的标题

  • 径向渐变圆形(橙色渐变)

  • 绿色虚线矩形

  • 彩色文字混合

  • 方格图案填充

  • 三种线条端点样式

  • 虚线矩形

  • 居中文本(使用度量计算)

示例分析:这个综合示例展示了如何将多种样式组合使用:

  • 阴影增强了立体感

  • 渐变创造丰富的色彩过渡

  • 图案增加纹理

  • 线型样式使线条多样化

  • 文本度量实现精确布局


八、样式状态管理

save() 和 restore() - 状态栈

项目内容
属性/方法ctx.save()
ctx.restore()
含义保存当前绘图状态,恢复之前保存的状态
词源save(保存)、restore(恢复)
保存内容所有样式属性(fillStyle, strokeStyle, lineWidth, shadow等)、变换矩阵、裁剪区域

完整示例

<!DOCTYPE html>
<html>
<head>
    <title>save/restore 状态管理示例</title>
</head>
<body>
    <canvas id="myCanvas" width="700" height="400" style="border:1px solid #ccc;"></canvas>
    
    <script>
        const canvas = document.getElementById('myCanvas');
        const ctx = canvas.getContext('2d');
        
        // 初始状态
        ctx.fillStyle = 'red';
        ctx.lineWidth = 5;
        ctx.shadowColor = 'gray';
        ctx.shadowBlur = 5;
        ctx.shadowOffsetX = 3;
        ctx.shadowOffsetY = 3;
        
        // 保存初始状态 (状态1)
        ctx.save();
        
        // 修改样式 - 绘制第一个矩形
        ctx.fillStyle = 'blue';
        ctx.lineWidth = 10;
        ctx.shadowColor = 'rgba(0,0,255,0.5)';
        ctx.shadowBlur = 10;
        
        ctx.fillRect(50, 50, 120, 120);
        ctx.fillStyle = 'black';
        ctx.font = '14px Arial';
        ctx.fillText('蓝色 (修改后)', 50, 30);
        
        // 保存当前状态 (状态2)
        ctx.save();
        
        // 再次修改 - 绘制第二个矩形
        ctx.fillStyle = 'green';
        ctx.shadowColor = 'rgba(0,255,0,0.5)';
        ctx.lineWidth = 15;
        
        ctx.fillRect(200, 50, 120, 120);
        ctx.fillStyle = 'black';
        ctx.fillText('绿色 (再次修改)', 200, 30);
        
        // 恢复状态2
        ctx.restore();
        
        // 绘制第三个矩形 - 应该回到蓝色样式
        ctx.fillRect(350, 50, 120, 120);
        ctx.fillStyle = 'black';
        ctx.fillText('恢复后 (蓝色)', 350, 30);
        
        // 恢复状态1
        ctx.restore();
        
        // 绘制第四个矩形 - 应该回到红色样式
        ctx.fillRect(500, 50, 120, 120);
        ctx.fillStyle = 'black';
        ctx.fillText('恢复初始 (红色)', 500, 30);
        
        // 演示嵌套保存的应用场景
        ctx.font = '16px Arial';
        ctx.fillStyle = '#333';
        ctx.fillText('复杂图形中的状态管理:', 50, 220);
        
        function drawComplexShape(x, y) {
            ctx.save();  // 保存外部状态
            
            // 设置这个形状特有的样式
            ctx.fillStyle = 'purple';
            ctx.shadowColor = 'purple';
            ctx.shadowBlur = 8;
            
            // 绘制外框
            ctx.fillRect(x, y, 150, 100);
            
            ctx.save();  // 保存内部状态
            
            // 内部元素样式
            ctx.fillStyle = 'yellow';
            ctx.shadowColor = 'orange';
            ctx.shadowBlur = 5;
            ctx.shadowOffsetX = 2;
            ctx.shadowOffsetY = 2;
            
            // 绘制内部小矩形
            ctx.fillRect(x + 25, y + 25, 40, 40);
            
            ctx.restore();  // 恢复内部状态
            
            // 继续使用外部状态(紫色阴影)
            ctx.fillStyle = 'white';
            ctx.fillRect(x + 85, y + 25, 40, 40);
            
            ctx.restore();  // 恢复外部状态
        }
        
        // 绘制两个相同的复杂图形
        drawComplexShape(50, 250);
        drawComplexShape(250, 250);
        drawComplexShape(450, 250);
        
        // 说明
        ctx.font = '14px Arial';
        ctx.fillStyle = 'black';
        ctx.shadowColor = 'transparent';  // 关闭阴影写文字
        ctx.fillText('save/restore 确保每个图形独立', 50, 380);
        ctx.fillText('内部样式不影响外部', 300, 380);
    </script>
</body>
</html>

绘制效果

  • 顶部四个矩形展示状态栈的保存和恢复

  • 底部三个复杂图形展示嵌套 save/restore 的应用

示例分析

  • save():将当前所有样式属性压入栈中保存

  • restore():弹出栈顶状态,恢复所有样式属性

  • 嵌套使用:可以多层嵌套,像栈一样后进先出

  • 应用场景

    • 绘制多个独立图形,每个有不同样式

    • 临时修改样式,之后恢复

    • 在函数中绘制复杂图形,不影响外部环境

状态管理的好处

  • 避免手动重置每个属性

  • 代码更清晰,减少错误

  • 便于封装复用

  • 在变换(平移、旋转、缩放)中特别有用


九、性能优化建议

Canvas 性能优化示例:

<!DOCTYPE html>
<html>
<head>
    <title>Canvas 性能优化示例</title>
    <style>
        canvas { border: 1px solid #ccc; margin: 10px; }
        .stats { font-family: monospace; }
    </style>
</head>
<body>
    <h3>Canvas 性能对比</h3>
    <canvas id="badCanvas" width="500" height="300"></canvas>
    <canvas id="goodCanvas" width="500" height="300"></canvas>
    <div class="stats" id="stats"></div>
    
    <script>
        // 不好的做法:频繁设置样式
        function drawBad() {
            const canvas = document.getElementById('badCanvas');
            const ctx = canvas.getContext('2d');
            
            const startTime = performance.now();
            
            for (let i = 0; i < 1000; i++) {
                const x = Math.random() * 450;
                const y = Math.random() * 250;
                const size = 5 + Math.random() * 10;
                
                // 每次循环都设置样式
                ctx.fillStyle = `hsl(${Math.random() * 360}, 100%, 50%)`;
                ctx.shadowColor = 'gray';
                ctx.shadowBlur = 5;
                ctx.shadowOffsetX = 2;
                ctx.shadowOffsetY = 2;
                
                ctx.beginPath();
                ctx.arc(x, y, size, 0, Math.PI * 2);
                ctx.fill();
            }
            
            const endTime = performance.now();
            return endTime - startTime;
        }
        
        // 好的做法:批量设置,减少样式变更
        function drawGood() {
            const canvas = document.getElementById('goodCanvas');
            const ctx = canvas.getContext('2d');
            
            const startTime = performance.now();
            
            // 1. 先设置共用的阴影(只设置一次)
            ctx.shadowColor = 'gray';
            ctx.shadowBlur = 5;
            ctx.shadowOffsetX = 2;
            ctx.shadowOffsetY = 2;
            
            // 2. 生成所有点
            const points = [];
            for (let i = 0; i < 1000; i++) {
                points.push({
                    x: Math.random() * 450,
                    y: Math.random() * 250,
                    size: 5 + Math.random() * 10,
                    color: `hsl(${Math.random() * 360}, 100%, 50%)`
                });
            }
            
            // 3. 按颜色分组绘制
            const colorGroups = {};
            points.forEach(p => {
                if (!colorGroups[p.color]) {
                    colorGroups[p.color] = [];
                }
                colorGroups[p.color].push(p);
            });
            
            // 4. 每个颜色只设置一次 fillStyle
            for (const color in colorGroups) {
                ctx.fillStyle = color;
                const group = colorGroups[color];
                
                group.forEach(p => {
                    ctx.beginPath();
                    ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
                    ctx.fill();
                });
            }
            
            const endTime = performance.now();
            return endTime - startTime;
        }
        
        // 执行并显示结果
        setTimeout(() => {
            const badTime = drawBad();
            const goodTime = drawGood();
            
            document.getElementById('stats').innerHTML = `
                不好的做法: ${badTime.toFixed(2)}ms<br>
                优化的做法: ${goodTime.toFixed(2)}ms<br>
                性能提升: ${((badTime - goodTime) / badTime * 100).toFixed(1)}%
            `;
        }, 500);
        
        // 分层渲染示例
        function layeredRendering() {
            const canvas = document.createElement('canvas');
            canvas.width = 500;
            canvas.height = 200;
            const ctx = canvas.getContext('2d');
            
            // 创建背景层(离屏 canvas)
            const bgCanvas = document.createElement('canvas');
            bgCanvas.width = 500;
            bgCanvas.height = 200;
            const bgCtx = bgCanvas.getContext('2d');
            
            // 绘制静态背景
            const bgGradient = bgCtx.createLinearGradient(0, 0, 0, 200);
            bgGradient.addColorStop(0, '#87CEEB');
            bgGradient.addColorStop(1, '#E0F6FF');
            bgCtx.fillStyle = bgGradient;
            bgCtx.fillRect(0, 0, 500, 200);
            
            bgCtx.fillStyle = 'rgba(255,255,255,0.3)';
            for (let i = 0; i < 500; i += 20) {
                bgCtx.fillRect(i, 0, 10, 200);
            }
            
            // 动画循环只更新前景
            let frame = 0;
            function animate() {
                // 清除前景(但保留背景 - 通过绘制背景层实现)
                ctx.clearRect(0, 0, 500, 200);
                
                // 绘制背景层
                ctx.drawImage(bgCanvas, 0, 0);
                
                // 绘制动态前景
                ctx.fillStyle = 'red';
                ctx.beginPath();
                ctx.arc(100 + frame, 100, 30, 0, Math.PI * 2);
                ctx.fill();
                
                frame = (frame + 2) % 300;
                
                // 实际应用中这里会用 requestAnimationFrame 循环
                // 这里只是演示概念
            }
            
            // 每帧只更新前景,背景不变
            setInterval(animate, 30);
            
            return canvas;
        }
    </script>
    
    <h3>分层渲染示例</h3>
    <canvas id="layerCanvas" width="500" height="200" style="border:1px solid #ccc;"></canvas>
    <script>
        // 简单分层渲染演示
        const layerCanvas = document.getElementById('layerCanvas');
        const layerCtx = layerCanvas.getContext('2d');
        
        // 绘制静态背景
        const bgGradient = layerCtx.createLinearGradient(0, 0, 0, 200);
        bgGradient.addColorStop(0, '#87CEEB');
        bgGradient.addColorStop(1, '#E0F6FF');
        layerCtx.fillStyle = bgGradient;
        layerCtx.fillRect(0, 0, 500, 200);
        
        layerCtx.fillStyle = 'rgba(255,255,255,0.3)';
        for (let i = 0; i < 500; i += 20) {
            layerCtx.fillRect(i, 0, 10, 200);
        }
        
        // 动画前景
        let frame = 0;
        function animateLayer() {
            // 清除部分区域(只清除之前小球的位置)
            layerCtx.clearRect(frame - 5, 70, 40, 40);
            
            // 恢复背景(绘制背景的局部)
            layerCtx.fillStyle = bgGradient;
            layerCtx.fillRect(frame - 5, 70, 40, 40);
            
            // 绘制条纹(恢复背景条纹)
            for (let i = 0; i < 500; i += 20) {
                if (i >= frame - 5 && i <= frame + 35) {
                    layerCtx.fillStyle = 'rgba(255,255,255,0.3)';
                    layerCtx.fillRect(i, 70, 10, 40);
                }
            }
            
            // 绘制新小球
            layerCtx.fillStyle = 'red';
            layerCtx.shadowColor = 'gray';
            layerCtx.shadowBlur = 5;
            layerCtx.shadowOffsetX = 2;
            layerCtx.shadowOffsetY = 2;
            layerCtx.beginPath();
            layerCtx.arc(frame, 90, 15, 0, Math.PI * 2);
            layerCtx.fill();
            
            frame = (frame + 2) % 480;
            requestAnimationFrame(animateLayer);
        }
        
        // 启动动画
        setTimeout(() => {
            animateLayer();
        }, 1000);
    </script>
    
    <div class="stats">
        <p><strong>优化建议总结:</strong></p>
        <ul>
            <li>✅ 减少样式变更 - 批量绘制相同样式的图形</li>
            <li>✅ 合理使用阴影 - 动画中少用大模糊阴影</li>
            <li>✅ 缓存渐变/图案 - 重复使用同一对象</li>
            <li>✅ 分层渲染 - 静态背景用离屏Canvas</li>
            <li>✅ 避免不必要的 save/restore - 过多嵌套影响性能</li>
            <li>✅ 只更新变化的区域 - 使用 clearRect 精确清除</li>
        </ul>
    </div>
</body>
</html>

效果图:


十、总结

Canvas 的绘图样式系统为开发者提供了丰富而灵活的视觉表现手段:

类别核心要点主要应用
色彩与透明度颜色值、全局/局部透明基本着色、透明效果
线条样式粗细、端点、连接、虚线轮廓绘制、边框装饰
阴影效果颜色、模糊、偏移立体感、光效
渐变与图案线性/径向渐变、图像填充丰富表面、材质表现
文本样式字体、对齐、度量文字渲染、排版
状态管理save/restore样式隔离、代码简化

掌握这些样式,配合路径绘制,你就能在 Canvas 上创造出无限可能的视觉作品。从简单的统计图表到复杂的游戏画面,从数据可视化到艺术创作,Canvas 样式都是你表达创意的重要工具。

学习建议

  1. 从基础颜色和线条开始练习

  2. 逐步尝试渐变和阴影效果

  3. 用 save/restore 管理复杂图形的样式

  4. 注意性能优化,特别是在动画中

  5. 多动手实践,组合使用不同样式

您可能感兴趣的与本文相关的镜像

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值