样式

3 阅读22分钟

在 HTML5 Canvas 中,为图形添加颜色、描边和投影等效果,主要通过配置其 2D 渲染上下文(CanvasRenderingContext2D)的属性来实现。下面将为你详细讲解这四个方面。

🎨 图形着色的区域

Canvas 中的图形着色主要分为两个独立的区域:

  1. 填充区域 (Fill Area)

    • 指图形内部的区域。
    • 通过 fill()fillRect()fillText() 等方法进行绘制。
    • 其样式由 fillStyle 属性控制。
  2. 描边区域 (Stroke Area)

    • 指图形的轮廓线。
    • 通过 stroke()strokeRect()strokeText() 等方法进行绘制。
    • 其样式由 strokeStyle 属性控制。

这两个区域可以独立设置样式和进行绘制。

    const canvas = document.getElementById('myCanvas');
    canvas.width = 1000;
    canvas.height = 800;
    const ctx = canvas.getContext('2d');
    // 绘制区域
    ctx.beginPath();
    ctx.rect(0, 0, 100, 100);
    ctx.fillStyle = 'red';
    ctx.fill();
    // 绘制描边
    ctx.beginPath();
    ctx.rect(150, 20, 100, 100);
    ctx.lineWidth = 40;//canvas 的描边是以路径为中心向两侧各延伸一半的 lineWidth
    // 矩形路径定义	100 × 100
    // 描边后视觉外边界	140 × 140
    // 描边后内部空白区域	60 × 60 (100 - 20×2)
    ctx.strokeStyle = 'skyblue';
    ctx.stroke();

🖌️ 图形着色的方式

着色的核心是 fillStylestrokeStyle 这两个属性。它们可以设置为三种不同类型的值:纯色、渐变和图案。

1. 纯色

这是最基本的着色方式,可以使用任何有效的 CSS 颜色值。

  • 颜色名称: 如 'red', 'blue', 'green'
  • 十六进制: 如 '#FF0000', '#00ff00'
  • RGB/RGBA: 如 'rgb(255, 0, 0)', 'rgba(255, 0, 0, 0.5)' (RGBA 中的 A 代表透明度)。

示例:

const ctx = canvas.getContext('2d');

// 设置填充颜色为蓝色
ctx.fillStyle = 'blue';
ctx.fillRect(10, 10, 100, 80); // 绘制一个填充矩形

// 设置描边颜色为半透明红色
ctx.strokeStyle = 'rgba(255, 0, 0, 0.5)';
ctx.lineWidth = 5;
ctx.strokeRect(120, 10, 100, 80); // 绘制一个描边矩形

2. 渐变

渐变可以在颜色之间创建平滑的过渡效果,分为线性渐变和径向渐变。

  • 线性渐变 (createLinearGradient(x1, y1, x2, y2)):沿一条直线变化的渐变。
  • 径向渐变 (createRadialGradient(x1, y1, r1, x2, y2, r2)):沿一个圆形或椭圆形变化的渐变。
  • 创建渐变后,需要使用 addColorStop(position, color) 方法来定义渐变的颜色节点。position 是一个 0 到 1 之间的值,代表渐变轴上的位置。

2.1线性渐变

ctx.createLinearGradient(x1, y1, x2, y2) 中的四个参数定义了渐变线的起点和终点,也就是颜色变化的方向和范围

简单来说,这四个坐标定义了一条“颜色变化轴线”。

  • (x1, y1):渐变起点坐标。
  • (x2, y2):渐变终点坐标。

沿这条从起点到终点的直线,颜色会均匀变化垂直于这条线的方向上,颜色是恒定不变的


核心理解:不是填充整个矩形

理解它最关键的一点是:渐变的范围不受图形大小和位置影响,只由这四个坐标决定的直线决定

  • 位于起点和终点之间的区域,会显示完整的渐变过渡。
  • 位于起点之前的区域,显示起点的纯色。
  • 位于终点之后的区域,显示终点的纯色。
具体例子

假设我们有一个从 (50, 50) 到 (150, 50) 的渐变。

// 创建一个从 (50,50) 到 (150,50) 的水平渐变
var gradient = ctx.createLinearGradient(50, 50, 150, 50);
gradient.addColorStop(0, 'red');   // 0% 位置为红色
gradient.addColorStop(1, 'blue');  // 100% 位置为蓝色

用这个渐变去填充不同大小的矩形:

1. 填充一个正好匹配(矩形x1,x2和渐变线的起点和终点的x坐标)渐变线的矩形

ctx.fillStyle = gradient;
ctx.fillRect(50, 50, 150, 150);
// 矩形 [50, 50] 到 [150, 150]

效果:矩形从左到右显示从红到蓝的完整、平滑渐变。

2. 填充一个比渐变线大的矩形

ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 200, 200);

效果:

  • 矩形左半部分(0到50px):纯红色。
  • 矩形中间部分(50到150px):从红到蓝渐变。
  • 矩形右半部分(150到200px):纯蓝色。

3. 填充一个在渐变线内部的矩形

ctx.fillStyle = gradient;
ctx.fillRect(75, 75, 50, 50);

效果:矩形只会显示渐变线上从 x=75 到 x=125 这一段对应的颜色,大约是某种红紫色到紫蓝色的样子。

方向与常用写法

通过改变 (x1, y1) 和 (x2, y2),你可以实现各种方向的渐变:

  • 水平渐变(左→右)y1y2 相同,x2 > x1

    ctx.createLinearGradient(100, 100, 200, 100);
    
  • 垂直渐变(上→下)x1x2 相同,y2 > y1

    ctx.createLinearGradient(100, 100, 100, 200);
    
  • 对角线渐变(左上→右下)

    ctx.createLinearGradient(100, 100, 200, 200);
    
最佳实践:使用相对坐标

为了让渐变能自适应不同大小的图形,建议将渐变线的坐标与图形的坐标和尺寸关联起来

比如,想为位置在 (x, y),宽 w、高 h 的矩形添加从左到右的渐变:

// 方式1:直接使用变量
var gradient = ctx.createLinearGradient(x, y, x + w, y);

// 方式2:总是从图形的左边缘到右边缘
var rect = {x: 50, y: 50, w: 200, h: 100};
var gradient = ctx.createLinearGradient(rect.x, rect.y, rect.x + rect.w, rect.y);
gradient.addColorStop(0, 'red');
gradient.addColorStop(1, 'blue');
ctx.fillStyle = gradient;
ctx.fillRect(rect.x, rect.y, rect.w, rect.h);

这样,无论矩形位置和大小如何,都能保证渐变从左边缘开始,到右边缘结束,效果符合直觉。

总结
  • (x1, y1) 是渐变起点,对应颜色停止点的 0 位置。
  • (x2, y2) 是渐变终点,对应颜色停止点的 1 位置。
  • 渐变线定义了颜色变化的方向和距离
  • 超出渐变线范围的区域,会使用端点处的纯色。
  • 为了让渐变和图形对齐,建议使用与图形坐标相关的变量。

2.2 径向渐变

ctx.createRadialGradient(x1, y1, r1, x2, y2, r2) 的六个参数定义了两个圆,颜色在从第一个圆(起点圆)过渡到第二个圆(终点圆)的过程中发生变化。

简单理解:颜色从一个圆圈渐变到另一个圆圈

  • (x1, y1, r1):起点圆的圆心坐标和半径。
  • (x2, y2, r2):终点圆的圆心坐标和半径。
核心理解:两个圆之间的渐变

当两圆圆心重合时(标准辐射渐变):

  • 起点圆的圆周上(r1 位置),对应颜色停止点的 0 位置
  • 终点圆的圆周上(r2 位置),对应颜色停止点的 1 位置
  • 两个圆之间的区域,颜色会平滑过渡
  • 起点圆内部,显示起点圆圆周的颜色(0 位置的颜色)
  • 终点圆外部,显示终点圆圆周的颜色(1 位置的颜色)

createRadialGradient(x1, y1, r1, x2, y2, r2) 的实际计算逻辑:

径向渐变定义了一个从起点圆到终点圆的圆锥台渐变空间。

  • t=0 对应起点圆的圆周(或圆心,当 r1=0 时)
  • t=1 对应终点圆的圆周

当两圆圆心重合时(同心圆):

  • 等色线是以共同圆心为中心的同心圆
  • t = (像素到圆心的距离 - r1) / (r2 - r1)
  • 当 r1=0 时:t = 距离 / r2

当两圆圆心不重合时

  • 等色线不再是同心圆,会在两圆心连线方向上扭曲
  • t=1 的位置是终点圆的圆周(到终点圆心距离=r2)
  • 视觉效果:渐变被"拉向"终点圆心方向
典型用法示例
1. 从圆心向外辐射(最常见)
// 两个圆心相同,起点圆半径=0,终点圆半径>0
const gradient = ctx.createRadialGradient(100, 100, 0, 100, 100, 50);
gradient.addColorStop(0, 'red');    // 圆心处红色
gradient.addColorStop(1, 'blue');   // 半径50px处蓝色

效果:从圆心(红色)向外辐射渐变到圆周(蓝色)。

这是最常用的形式,用于创建球体、聚光灯效果等。

2. 偏移光源效果
// 圆心不同,模拟偏移的光源
const gradient = ctx.createRadialGradient(80, 80, 0, 100, 100, 60);
gradient.addColorStop(0, 'red');
gradient.addColorStop(1, 'blue');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 200, 200);

关键:两个圆的位置关系

终点圆:圆心 (100,100), 半径 60
┌─────────────────────────┐
│    · (80,80)            │  ← 起点圆在终点圆内部
│         ╲               │
│          ╲ 渐变区        │
│           ● (100,100)   │  ← 终点圆心
│          ╱│╲            │
│         ╱ │ ╲ r2=60     │
│        ╱ 蓝色区域        │
└─────────────────────────┘

总结你的效果图(圆心不重合的情况):

  • 红色高光点在起点圆心 (80,80)
  • 等色线发生扭曲,被拉向终点圆心 (100,100) 方向
  • 在终点圆的圆周上(到 (100,100) 距离=60px 的地方)t=1,显示蓝色
  • 终点圆外部(到 (100,100) 距离 > 60px)全是蓝色

⚠️ 关键:圆心不重合时,等色线不再是同心圆。渐变沿两圆心连线方向扭曲 的边界与终点圆圆周重合。

圆心不重合时,这五条规则需要重新理解:

1️⃣ 起点圆(退化成一个点)

  • 起点圆:圆心 (80,80), 半径 r1=0
  • r1=0,所以起点圆退化成一个点 (80,80)
  • 这个点显示 colorStop(0) = 红色
  • 图中最红的那个点就是 (80,80) ✅ 这条仍然适用

2️⃣ 终点圆的圆周(t=1 的边界)

  • 终点圆:圆心 (100,100), 半径 r2=60
  • 在这个圆的圆周上(到 (100,100) 距离=60px 的地方)
  • t=1,显示 colorStop(1) = 蓝色 ✅ 这条仍然适用——终点圆的圆周是到终点圆心距离为 r2 的圆
t=1 的边界(终点圆圆周)在哪里?

坐标系:原点在左上角,x 向右→,y 向下↓

                    (100,40) ← t=1 边界的顶点
                      ↑
                      │ y=40
                      │
(40,100) ←───────────●───────────→ (160,100)
  t=1 边界    (100,100) 终点圆心    t=1 边界
                      │
                      │
                      ↓
                    (100,160) ← t=1 边界的底点

t=1 的圆周经过的 4 个关键点:
- 顶部:(100, 40)   ← 100-60
- 底部:(100, 160)  ← 100+60
- 左侧:(40, 100)   ← 100-60
- 右侧:(160, 100)  ← 100+60

起点圆 (80,80) 在这个圆的内部:
- (80,80) 到 (100,100) 的距离 ≈ 28.3px < 60px ✓

3️⃣ "两个圆之间的区域,颜色会平滑过渡"

  • ✅ 等色线发生扭曲,从起点圆 (80,80) 过渡到终点圆圆周

等色线 = 颜色值相同的点连成的线(类似地图上的等高线)

  • 同心圆时:等色线是完美的同心圆
  • 圆心不重合时:等色线发生扭曲,不再是圆

t 值的含义(对 Canvas 上每个像素点而言):

  • t=0:在起点圆的圆周上(r1=0 时就是起点圆心 (80,80))
  • t=1:在终点圆的圆周上(到终点圆心距离=r2 的圆周)
  • t=0.5:在两个圆中间的位置,颜色是 colorStop(0) 和 colorStop(1) 的 50% 混合
  • t<0:在起点圆内部,显示 colorStop(0) 的颜色
  • t>1:通常在终点圆外部,显示 colorStop(1) 的颜色 ⚠️ 注意:当圆心不重合时,t 值的计算涉及像素相对于两个圆的位置关系,不是简单的距离公式。t=1 的等色线与终点圆圆周重合,这是定义决定的。

通俗理解:Canvas 会给每个像素点计算一个 t 值,t 值决定该点显示什么颜色。

对于 Canvas 上的任意像素点 (x, y):

  • 计算它相对于起点圆和终点圆的位置
  • 得出一个 t 值
  • 根据 t 值在 colorStop 中插值得到颜色
视觉效果:渐变被拉向右下角的终点圆心

    (80,80) 红点
        │ ╲
        │  ╲  渐变被拉向 (100,100)
        │   ╲
        ↓    ● (100,100) 终点圆心
      等色线弯曲

4️⃣ "起点圆内部,显示起点圆圆周的颜色(0 位置的颜色)"

  • r1=0,起点圆内部就是 (80,80) 这个点本身
  • → 显示红色 ✅ 这条仍然适用

5️⃣ "终点圆外部,显示终点圆圆周的颜色(1 位置的颜色)"

  • 终点圆外部 = 到 (100,100) 距离 > 60px 的区域
  • t≥1,显示蓝色 ✅ 这条仍然适用

效果:高光点不在正中心,像侧上方打光。

3. 环形/管状效果
// 两个圆心相同,起点圆半径>0
const gradient = ctx.createRadialGradient(100, 100, 30, 100, 100, 60);
gradient.addColorStop(0, 'red');
gradient.addColorStop(1, 'blue');

效果

  • 半径 < 30px 的区域:纯红色
  • 半径 30-60px 的区域:红到蓝渐变
  • 半径 > 60px 的区域:纯蓝色

这可以创建环形渐变或管状效果。

关键要点
1. 颜色停止点映射到圆周上

addColorStop(0, color) 表示起点圆的圆周上的颜色 addColorStop(1, color) 表示终点圆的圆周上的颜色 中间的值对应两个圆之间的位置

2. 圆的顺序很重要

第一个圆是渐变的起点(colorStop 0) 第二个圆是渐变的终点(colorStop 1)

3. 超出范围的处理

颜色由像素点相对于从起点圆到终点圆方向的位置决定。

当两圆圆心重合时(标准辐射渐变):

  • 起点圆内部(到圆心距离 < r1):显示 colorStop 0 的颜色
  • 两圆之间(r1 ≤ 距离 ≤ r2):显示渐变过渡色
  • 终点圆外部(距离 > r2):显示 colorStop 1 的颜色

当两圆圆心不重合时: 渐变的主轴是从起点圆指向终点圆的方向,颜色分布沿此方向变化:

  • 背离终点圆方向的区域 → 趋向 colorStop 0
  • 两圆之间区域 → 渐变过渡
  • 超出终点圆方向的区域 → 趋向 colorStop 1
实际应用技巧
创建 3D 球体效果
const x = 150, y = 150, radius = 60;
const gradient = ctx.createRadialGradient(
    x - radius * 0.3,  // 高光偏移
    y - radius * 0.3, 
    0,                 // 起点半径0
    x, y, 
    radius             // 终点半径
);
gradient.addColorStop(0, 'white');
gradient.addColorStop(0.3, 'lightblue');
gradient.addColorStop(1, 'darkblue');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 300, 300);
创建聚光灯效果
const gradient = ctx.createRadialGradient(
    150, 150, 0,    // 光源位置
    150, 150, 100   // 影响范围
);
gradient.addColorStop(0, 'rgba(255,255,255,0.8)');
gradient.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 300, 300);
总结
参数含义
x1, y1起点圆圆心
r1起点圆半径(colorStop 0 的位置)
x2, y2终点圆圆心
r2终点圆半径(colorStop 1 的位置)
  • 最常用createRadialGradient(x, y, 0, x, y, r) 创建标准辐射渐变
  • 核心概念:在两个圆的圆周之间进行颜色插值
  • 记住:渐变基于而不是基于矩形,与图形的位置和大小无关

2.3 圆锥渐变

圆锥渐变(Conic Gradient,也叫锥形渐变),是Canvas 2D API中的一种填充方式。它的颜色会围绕一个中心点,沿着圆周方向旋转渐变,很适合用来绘制饼图、仪表盘或色轮。

📜 基本语法与参数

createConicGradient() 是创建圆锥渐变的核心方法。其基本语法如下:

createConicGradient(startAngle, x, y)

该方法接收三个参数,创建一个 CanvasGradient 对象:

  • startAngle:一个弧度值,定义渐变的起始角度。注意,0弧度(即0°)方向是水平向右(三点钟方向),而不是常见的垂直向上。
  • x, y:渐变的中心点坐标。

创建渐变对象后,需要用 addColorStop() 方法添加色标:

gradient.addColorStop(offset, color);
  • offset:是一个介于 0.01.0 之间的浮点数,代表该颜色在渐变圆环上的相对位置。例如,0 表示起始点,0.5 表示半圈,1 表示完整一圈。
  • color:支持任何有效的 CSS 颜色值,如 'red', '#FF0000', 'rgba(255,0,0,0.5)' 等。
⚙️ 实际应用步骤
  1. 获取上下文:从 <canvas> 元素获取 2D 渲染上下文。
  2. 创建渐变:调用 createConicGradient() 方法,传入起始角度和中心点坐标。
  3. 添加色标:使用 addColorStop() 方法,按顺序添加颜色和它们的位置。
  4. 应用渐变:将创建好的渐变对象赋值给 fillStylestrokeStyle
  5. 绘制图形:调用绘图方法(如 fillRect(), fill(), stroke() 等),图形就会被圆锥渐变填充。
💡 代码示例

这是一个绘制饼图的简单示例。

<canvas id="pieChartCanvas" width="400" height="400"></canvas>

<script>
  const canvas = document.getElementById('pieChartCanvas');
  const ctx = canvas.getContext('2d');

    // 创建圆锥渐变(角度, x, y)
    // 设置画布中心点和半径
    const centerX = canvas.width / 2;
    const centerY = canvas.height / 2;
    const radius = 150;

    // 1. 创建圆锥渐变,从 -90°(即12点钟方向)开始 顺时针
    const startAngleInRadians = -0.5 * Math.PI;
    const gradient = ctx.createConicGradient(startAngleInRadians, centerX, centerY);

    // 2. 定义饼图的数据
    const data = [
        { color: '#FF6384', percent: 0.5 }, // 50%
        { color: '#36A2EB', percent: 0.25 },// 25%
        { color: '#FFCE56', percent: 0.1 }, // 20%
        { color: '#4BC0C0', percent: 0.05 },// 15%
        { color: '#9966FF', percent: 0.1 }   // 10%
    ];

    // 3. 根据数据动态添加色标
    let accumulatedPercent = 0;
    for (const item of data) {
        // 在当前扇形区间的起始位置,设置为该扇形的颜色
        gradient.addColorStop(accumulatedPercent, item.color);
        // 在当前扇形区间的结束位置,再次设置为该扇形的颜色,形成硬边界
        accumulatedPercent += item.percent;
        gradient.addColorStop(accumulatedPercent, item.color);
    }

    // 4. 应用渐变并绘制圆形
    ctx.fillStyle = gradient;
    ctx.beginPath();
    ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI);
    ctx.fill();
</script>
🧩 与其他Canvas渐变的对比

Canvas 提供了三种渐变方式,你可以通过下表快速了解它们的区别:

渐变类型创建方法渐变方向常见用途
圆锥渐变createConicGradient()绕中心点旋转饼图、色轮、仪表盘
线性渐变createLinearGradient()沿直线方向按钮、背景、高光效果
径向渐变createRadialGradient()从内向外辐射光源效果、圆形按钮

需要注意的是,圆锥渐变(createConicGradient)目前是Canvas标准的一部分,但从 2023 年 4 月开始,它才被所有主流浏览器(Chrome, Edge, Firefox, Safari)广泛支持,不再需要任何实验性标志。

📌 注意事项
  • CSS兼容性问题html2canvas 这类截图库目前不支持 CSS 的 conic-gradient()。如果你的项目需要截图功能,最好使用 Canvas 原生的 createConicGradient() 来替代,以确保截图能正常生成。
  • 坐标方向差异:Canvas 圆锥渐变的 0° 起点是三点钟方向,和 CSS 圆锥渐变(十二点钟方向)不同。绘制时如果需要从顶部开始,记得用 -0.5 * Math.PI 调整起始角度。

3. 纹理

纹理可以将一张图片作为图形的底色,如下图所示:

纹理允许你使用一张图片来重复填充一个图形。

  1. 首先,创建一个 Image 对象并加载图片。
  2. 图片加载完成后,使用 ctx.createPattern(image, repetition) 方法创建图案对象。repetition 参数可以是 'repeat', 'repeat-x', 'repeat-y', 或 'no-repeat'

示例:

const ctx = canvas.getContext('2d');
const img = new Image();
img.src = 'path/to/your/image.png';

img.onload = function() {
  // 创建可重复的图案
  const pattern = ctx.createPattern(img, 'repeat');
  ctx.fillStyle = pattern;
  ctx.fillRect(0, 0, 300, 150); // 用图案填充矩形
};

4. 描边的样式

描边的样式主要通过以下几个属性来控制:

属性说明默认值
strokeStyle描边的颜色、渐变或图案。'#000' (黑色)
lineWidth描边的线条宽度,单位为像素。1
lineCap线条末端的样式 ('butt', 'round', 'square')。'butt'
lineJoin线条交汇处的样式 ('round', 'bevel', 'miter')。'miter'
lineDash设置虚线样式,接收一个数组,如 ``[[source_group_web_9]]。[] (实线)

1. 基础描边属性

ctx.lineWidth = 5;              // 线条宽度(像素)
ctx.strokeStyle = '#FF0000';    // 描边颜色
ctx.lineCap = 'round';          // 线条端点样式
ctx.lineJoin = 'round';         // 线条连接样式

2. 线条端点样式(lineCap)

// 'butt'(默认)- 平直端点
ctx.lineCap = 'butt';
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(150, 50);
ctx.stroke();

// 'round' - 圆形端点
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(50, 100);
ctx.lineTo(150, 100);
ctx.stroke();

// 'square' - 方形端点(突出半个线宽)
ctx.lineCap = 'square';
ctx.beginPath();
ctx.moveTo(50, 150);
ctx.lineTo(150, 150);
ctx.stroke();

3. 线条连接样式(lineJoin)

// 'miter'(默认)- 尖角连接
ctx.lineJoin = 'miter';
ctx.lineWidth = 10;
ctx.strokeRect(50, 50, 100, 100);

// 'round' - 圆角连接
ctx.lineJoin = 'round';
ctx.strokeRect(200, 50, 100, 100);

// 'bevel' - 平切连接
ctx.lineJoin = 'bevel';
ctx.strokeRect(350, 50, 100, 100);

4. 虚线描边(setLineDash)

// 设置虚线模式([线段长度, 间隔长度])
ctx.setLineDash([10, 5]);

// 绘制虚线矩形
ctx.strokeRect(50, 50, 150, 100);

// 设置虚线偏移
ctx.lineDashOffset = 5; // 虚线起始偏移

// 重置为实线
ctx.setLineDash([]);

5. 复杂描边示例

// 组合使用多种描边样式
ctx.save();
ctx.lineWidth = 8;
ctx.strokeStyle = 'purple';
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.setLineDash([15, 10]);
ctx.lineDashOffset = 0;

ctx.beginPath();
ctx.moveTo(50, 200);
ctx.lineTo(150, 150);
ctx.lineTo(250, 200);
ctx.lineTo(350, 150);
ctx.stroke();
ctx.restore();

💡 投影

Canvas 可以为任何绘制的图形(包括形状、文本和图片)添加阴影效果。阴影由以下四个属性共同控制,设置后会影响之后绘制的所有图形。

属性说明默认值
shadowColor阴影的颜色,支持任何 CSS 颜色格式。'black'
shadowBlur阴影的模糊程度,值越大越模糊。0
shadowOffsetX阴影的水平偏移距离。正值向右,负值向左。0
shadowOffsetY阴影的垂直偏移距离。正值向下,负值向上。0

示例:

const ctx = canvas.getContext('2d');

// 设置阴影效果
ctx.shadowColor = 'rgba(0, 0, 0, 0.5)'; // 半透明黑色阴影
ctx.shadowBlur = 15;                   // 模糊程度
ctx.shadowOffsetX = 10;                // 向右偏移
ctx.shadowOffsetY = 10;                // 向下偏移

// 绘制一个带阴影的矩形
ctx.fillStyle = 'hotpink';
ctx.fillRect(50, 50, 100, 100);

// 绘制带阴影的文字
ctx.font = '24px Arial';
ctx.fillStyle = 'white';
ctx.fillText('带阴影的文字', 60, 120);

重要提示: 阴影设置是全局性的。如果你只想为特定图形添加阴影,可以使用 ctx.save() 在设置阴影前保存画布状态,并在绘制完成后使用 ctx.restore() 恢复状态,这样就不会影响到后续的绘图。

以下是Canvas图形着色与样式相关的详细讲解,涵盖填充区域、着色方式、描边样式和投影效果。

一、图形着色的区域

Canvas中图形着色主要针对两个区域:

  1. 填充区域:图形内部的封闭区域
  2. 描边区域:图形的轮廓边界
const ctx = canvas.getContext('2d');

// 填充区域示例
ctx.beginPath();
ctx.rect(50, 50, 100, 100);
ctx.fillStyle = 'blue';
ctx.fill(); // 填充内部区域

// 描边区域示例
ctx.beginPath();
ctx.rect(200, 50, 100, 100);
ctx.strokeStyle = 'red';
ctx.lineWidth = 3;
ctx.stroke(); // 只描边轮廓

二、图形着色的方式

1. 纯色填充(fillStyle / strokeStyle)

// 颜色关键字
ctx.fillStyle = 'red';
ctx.fillStyle = 'blue';

// 十六进制
ctx.fillStyle = '#FF5733';
ctx.fillStyle = '#F53';

// RGB/RGBA
ctx.fillStyle = 'rgb(255, 87, 51)';
ctx.fillStyle = 'rgba(255, 87, 51, 0.5)';

// HSL/HSLA
ctx.fillStyle = 'hsl(120, 100%, 50%)';
ctx.fillStyle = 'hsla(120, 100%, 50%, 0.3)';

2. 渐变着色

线性渐变(Linear Gradient)
// 创建线性渐变(x0, y0, x1, y1)
const linearGrad = ctx.createLinearGradient(0, 0, 200, 0);

// 添加颜色断点
linearGrad.addColorStop(0, 'red');
linearGrad.addColorStop(0.5, 'yellow');
linearGrad.addColorStop(1, 'blue');

ctx.fillStyle = linearGrad;
ctx.fillRect(50, 50, 200, 100);
径向渐变(Radial Gradient)
// 创建径向渐变(x0, y0, r0, x1, y1, r1)
const radialGrad = ctx.createRadialGradient(150, 150, 20, 150, 150, 80);

radialGrad.addColorStop(0, 'white');
radialGrad.addColorStop(0.5, 'orange');
radialGrad.addColorStop(1, 'red');

ctx.fillStyle = radialGrad;
ctx.fillRect(50, 50, 200, 200);
圆锥渐变(Conic Gradient - Chrome 86+)
// 创建圆锥渐变(角度, x, y)
const conicGrad = ctx.createConicGradient(0, 150, 150);

conicGrad.addColorStop(0, 'red');
conicGrad.addColorStop(0.25, 'yellow');
conicGrad.addColorStop(0.5, 'green');
conicGrad.addColorStop(0.75, 'blue');
conicGrad.addColorStop(1, 'red');

ctx.fillStyle = conicGrad;
ctx.fillRect(50, 50, 200, 200);

3. 图案着色(Pattern)

// 创建图案(图片, 重复方式)
const img = new Image();
img.src = 'pattern.jpg';
img.onload = () => {
    const pattern = ctx.createPattern(img, 'repeat');
    ctx.fillStyle = pattern;
    ctx.fillRect(0, 0, 400, 400);
};

// 重复方式:'repeat', 'repeat-x', 'repeat-y', 'no-repeat'

三、描边的样式

1. 基础描边属性

ctx.lineWidth = 5;              // 线条宽度(像素)
ctx.strokeStyle = '#FF0000';    // 描边颜色
ctx.lineCap = 'round';          // 线条端点样式
ctx.lineJoin = 'round';         // 线条连接样式

2. 线条端点样式(lineCap)

// 'butt'(默认)- 平直端点
ctx.lineCap = 'butt';
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(150, 50);
ctx.stroke();

// 'round' - 圆形端点
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(50, 100);
ctx.lineTo(150, 100);
ctx.stroke();

// 'square' - 方形端点(突出半个线宽)
ctx.lineCap = 'square';
ctx.beginPath();
ctx.moveTo(50, 150);
ctx.lineTo(150, 150);
ctx.stroke();

3. 线条连接样式(lineJoin)

// 'miter'(默认)- 尖角连接
ctx.lineJoin = 'miter';
ctx.lineWidth = 10;
ctx.strokeRect(50, 50, 100, 100);

// 'round' - 圆角连接
ctx.lineJoin = 'round';
ctx.strokeRect(200, 50, 100, 100);

// 'bevel' - 平切连接
ctx.lineJoin = 'bevel';
ctx.strokeRect(350, 50, 100, 100);

4. 虚线描边(setLineDash)

// 设置虚线模式([线段长度, 间隔长度])
ctx.setLineDash([10, 5]);

// 绘制虚线矩形
ctx.strokeRect(50, 50, 150, 100);

// 设置虚线偏移
ctx.lineDashOffset = 5; // 虚线起始偏移

// 重置为实线
ctx.setLineDash([]);

5. 复杂描边示例

// 组合使用多种描边样式
ctx.save();
ctx.lineWidth = 8;
ctx.strokeStyle = 'purple';
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.setLineDash([15, 10]);
ctx.lineDashOffset = 0;

ctx.beginPath();
ctx.moveTo(50, 200);
ctx.lineTo(150, 150);
ctx.lineTo(250, 200);
ctx.lineTo(350, 150);
ctx.stroke();
ctx.restore();

四、投影(Shadow)

1. 投影属性

ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';  // 投影颜色(必须带透明度)
ctx.shadowOffsetX = 10;                   // 水平偏移(正数向右)
ctx.shadowOffsetY = 10;                   // 垂直偏移(正数向下)
ctx.shadowBlur = 5;                       // 模糊程度(值越大越模糊)

2. 基本投影示例

// 设置投影
ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 5;
ctx.shadowBlur = 5;

// 绘制带投影的矩形
ctx.fillStyle = 'blue';
ctx.fillRect(50, 50, 150, 100);

// 绘制带投影的文字
ctx.font = '30px Arial';
ctx.fillStyle = 'red';
ctx.fillText('Shadow Text', 50, 200);

3. 不同投影效果

// 向下投影
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 10;
ctx.shadowBlur = 5;
ctx.fillRect(50, 50, 100, 100);

// 发光效果
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = 15;
ctx.shadowColor = 'rgba(0, 255, 0, 0.8)';
ctx.fillRect(200, 50, 100, 100);

// 多重投影(需要多次绘制)
// 由于Canvas不支持多重投影,需要手动多次绘制
for(let i = 3; i > 0; i--) {
    ctx.shadowBlur = i * 2;
    ctx.fillStyle = `rgba(0, 0, 0, ${0.2 / i})`;
    ctx.fillRect(350, 50, 100, 100);
}

4. 取消投影

// 方法1:逐个重置
ctx.shadowColor = 'transparent';
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = 0;

// 方法2:使用save/restore
ctx.save();
// ... 设置投影并绘制 ...
ctx.restore(); // 恢复投影设置前状态

五、综合示例

// 完整示例:创建带渐变、描边和投影的图形
function drawComplexShape(ctx) {
    // 保存当前状态
    ctx.save();
    
    // 清除画布
    ctx.clearRect(0, 0, 800, 400);
    
    // 1. 设置投影
    ctx.shadowColor = 'rgba(0, 0, 0, 0.3)';
    ctx.shadowOffsetX = 8;
    ctx.shadowOffsetY = 8;
    ctx.shadowBlur = 6;
    
    // 2. 创建渐变填充
    const gradient = ctx.createLinearGradient(50, 50, 250, 150);
    gradient.addColorStop(0, '#ff6b6b');
    gradient.addColorStop(0.5, '#4ecdc4');
    gradient.addColorStop(1, '#45b7d1');
    ctx.fillStyle = gradient;
    
    // 3. 设置描边样式
    ctx.strokeStyle = '#2c3e50';
    ctx.lineWidth = 4;
    ctx.lineJoin = 'round';
    ctx.setLineDash([10, 5]);
    
    // 4. 绘制矩形
    ctx.beginPath();
    ctx.rect(50, 50, 200, 150);
    ctx.fill();
    ctx.stroke();
    
    // 5. 重置虚线(实线描边)
    ctx.setLineDash([]);
    
    // 6. 绘制圆形
    ctx.beginPath();
    ctx.arc(400, 125, 70, 0, Math.PI * 2);
    ctx.fillStyle = '#ffe66d';
    ctx.fill();
    ctx.stroke();
    
    // 恢复状态
    ctx.restore();
}

六、性能优化建议

  1. 避免频繁切换样式:批量绘制相同样式的图形
  2. 使用save/restore:管理样式状态,避免手动重置
  3. 缓存渐变和图案:重复使用时避免重复创建
  4. 投影谨慎使用:投影会增加渲染开销
// 优化示例
ctx.fillStyle = 'blue';
for(let i = 0; i < 100; i++) {
    ctx.fillRect(i * 10, 0, 5, 100);
}

// 避免这样
for(let i = 0; i < 100; i++) {
    ctx.fillStyle = 'blue'; // 重复设置
    ctx.fillRect(i * 10, 0, 5, 100);
}

这些是Canvas图形着色的核心知识点,掌握它们可以实现丰富的2D图形效果。