Skip to content

第4章:样式与颜色系统

4.1 章节概述

在前几章中,我们学习了如何绑制各种图形——矩形、圆形、路径、曲线等。但这些图形都是单色的,显得有些单调。在实际应用中,我们需要为图形添加丰富的视觉效果:漂亮的颜色、渐变、图案、阴影、各种线条样式……

本章将深入探索 Canvas 的样式系统,让你的图形从"能用"变成"好看"。

我们将学习:

  • 颜色表示法:RGB、RGBA、HSL、十六进制等
  • 填充与描边样式:fillStyle 和 strokeStyle 的各种用法
  • 线条样式:线宽、线端、连接、虚线
  • 渐变效果:线性渐变、径向渐变、锥形渐变
  • 图案填充:使用图像作为填充图案
  • 阴影效果:为图形添加阴影

学完本章后,你将能够创建视觉效果丰富的 Canvas 应用。


4.2 颜色表示法

在开始学习样式之前,我们需要了解 Canvas 中可以使用的各种颜色表示方法。

4.2.1 颜色的本质

在计算机图形学中,颜色通常用三原色(红、绿、蓝)的组合来表示。这称为 RGB 颜色模型

RGB 颜色模型:

     红 (Red)


        ●───────────── 绿 (Green)


   蓝 (Blue)

三种原色混合可以产生任意颜色:
- 红 + 绿 = 黄
- 红 + 蓝 = 品红
- 绿 + 蓝 = 青
- 红 + 绿 + 蓝 = 白
- 无颜色 = 黑

每种原色的强度通常用 0-255 的整数表示,所以一个颜色可以表示为三个数字的组合,比如 (255, 0, 0) 表示纯红色。

4.2.2 Canvas 支持的颜色格式

Canvas 的 fillStylestrokeStyle 属性支持多种颜色格式:

1. 颜色名称

CSS 定义了 147 种标准颜色名称,可以直接使用:

javascript
ctx.fillStyle = 'red';
ctx.fillStyle = 'blue';
ctx.fillStyle = 'green';
ctx.fillStyle = 'coral';
ctx.fillStyle = 'dodgerblue';
ctx.fillStyle = 'mediumseagreen';
ctx.fillStyle = 'transparent';  // 完全透明

常用颜色名称示例

颜色名称效果颜色名称效果
red红色blue蓝色
green绿色yellow黄色
orange橙色purple紫色
pink粉色cyan青色
black黑色white白色
gray灰色silver银色

2. 十六进制格式(Hexadecimal)

这是最常用的颜色表示法,格式为 #RRGGBB 或简写 #RGB

javascript
// 完整格式:#RRGGBB
ctx.fillStyle = '#FF0000';  // 红色
ctx.fillStyle = '#00FF00';  // 绿色
ctx.fillStyle = '#0000FF';  // 蓝色
ctx.fillStyle = '#FFFFFF';  // 白色
ctx.fillStyle = '#000000';  // 黑色
ctx.fillStyle = '#4D7CFF';  // 自定义蓝色

// 简写格式:#RGB(每个字符重复一次)
ctx.fillStyle = '#F00';     // 等同于 #FF0000
ctx.fillStyle = '#0F0';     // 等同于 #00FF00
ctx.fillStyle = '#00F';     // 等同于 #0000FF

// 带透明度:#RRGGBBAA 或 #RGBA
ctx.fillStyle = '#FF000080';  // 50% 透明的红色
ctx.fillStyle = '#F008';      // 简写形式

理解十六进制

十六进制使用 0-9 和 A-F 表示数字 0-15

  十进制:0   1   2  ... 9   10  11  12  13  14  15
  十六进制:0   1   2  ... 9   A   B   C   D   E   F

两位十六进制可以表示 0-255:
  00 = 0
  FF = 15 × 16 + 15 = 255
  80 = 8 × 16 + 0 = 128(约50%)
  
#RRGGBB 的结构:
  #  FF   00   00
     │    │    │
     │    │    └── Blue(蓝色分量)
     │    └── Green(绿色分量)
     └── Red(红色分量)

3. RGB 函数格式

使用 rgb() 函数,参数为 0-255 的整数:

javascript
ctx.fillStyle = 'rgb(255, 0, 0)';    // 红色
ctx.fillStyle = 'rgb(0, 255, 0)';    // 绿色
ctx.fillStyle = 'rgb(0, 0, 255)';    // 蓝色
ctx.fillStyle = 'rgb(128, 128, 128)'; // 灰色

// 也可以使用百分比
ctx.fillStyle = 'rgb(100%, 0%, 0%)';  // 红色
ctx.fillStyle = 'rgb(50%, 50%, 50%)'; // 灰色

4. RGBA 函数格式(带透明度)

rgba() 函数在 RGB 基础上增加了 Alpha(透明度)通道:

javascript
// 最后一个参数是透明度,范围 0-1
ctx.fillStyle = 'rgba(255, 0, 0, 1)';    // 完全不透明的红色
ctx.fillStyle = 'rgba(255, 0, 0, 0.5)';  // 50% 透明的红色
ctx.fillStyle = 'rgba(255, 0, 0, 0.1)';  // 90% 透明的红色
ctx.fillStyle = 'rgba(255, 0, 0, 0)';    // 完全透明

// 透明度的常见值
// 1.0 = 100% 不透明
// 0.75 = 75% 不透明
// 0.5 = 50% 不透明(半透明)
// 0.25 = 25% 不透明
// 0 = 完全透明

透明度的视觉效果

不同透明度的叠加效果:

alpha = 1.0      alpha = 0.5      alpha = 0.25
██████████       ▒▒▒▒▒▒▒▒▒▒       ░░░░░░░░░░
██████████       ▒▒▒▒▒▒▒▒▒▒       ░░░░░░░░░░
██████████       ▒▒▒▒▒▒▒▒▒▒       ░░░░░░░░░░
完全不透明       半透明           近乎透明

5. HSL 格式

HSL 是另一种颜色模型,代表 Hue(色相)、Saturation(饱和度)、Lightness(亮度)

javascript
// hsl(色相, 饱和度, 亮度)
ctx.fillStyle = 'hsl(0, 100%, 50%)';     // 红色
ctx.fillStyle = 'hsl(120, 100%, 50%)';   // 绿色
ctx.fillStyle = 'hsl(240, 100%, 50%)';   // 蓝色
ctx.fillStyle = 'hsl(60, 100%, 50%)';    // 黄色

// hsla 带透明度
ctx.fillStyle = 'hsla(0, 100%, 50%, 0.5)'; // 半透明红色

HSL 颜色模型详解

色相(Hue):颜色的种类,0-360 度
  0° / 360° = 红色
  60° = 黄色
  120° = 绿色
  180° = 青色
  240° = 蓝色
  300° = 品红

        0°(红)

    300° ●──● 60°(黄)
     (品红)│╲│
         │ ╲│
    240° ●──● 120°(绿)
    (蓝)    │
         180°(青)

饱和度(Saturation):颜色的鲜艳程度,0-100%
  0% = 灰色(无彩色)
  50% = 较淡
  100% = 最鲜艳

亮度(Lightness):颜色的明暗程度,0-100%
  0% = 黑色
  50% = 正常颜色
  100% = 白色

HSL vs RGB 的优势

HSL 更符合人类对颜色的直觉理解,便于做颜色变换:

javascript
// 使用 HSL 创建颜色变体非常容易

// 基础颜色:蓝色
const baseHue = 210;

// 调整饱和度创建系列颜色
const colors = [
    `hsl(${baseHue}, 100%, 50%)`,  // 鲜艳
    `hsl(${baseHue}, 70%, 50%)`,   // 较淡
    `hsl(${baseHue}, 40%, 50%)`,   // 更淡
    `hsl(${baseHue}, 10%, 50%)`,   // 接近灰色
];

// 调整亮度创建明暗系列
const shades = [
    `hsl(${baseHue}, 70%, 80%)`,   // 浅色
    `hsl(${baseHue}, 70%, 50%)`,   // 正常
    `hsl(${baseHue}, 70%, 30%)`,   // 深色
];

4.2.3 颜色工具函数

在实际开发中,我们经常需要进行颜色转换和计算:

javascript
/**
 * RGB 转十六进制
 */
function rgbToHex(r, g, b) {
    return '#' + [r, g, b]
        .map(x => x.toString(16).padStart(2, '0'))
        .join('');
}

/**
 * 十六进制转 RGB
 */
function hexToRgb(hex) {
    const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
    return result ? {
        r: parseInt(result[1], 16),
        g: parseInt(result[2], 16),
        b: parseInt(result[3], 16)
    } : null;
}

/**
 * RGB 转 HSL
 */
function rgbToHsl(r, g, b) {
    r /= 255;
    g /= 255;
    b /= 255;
    
    const max = Math.max(r, g, b);
    const min = Math.min(r, g, b);
    let h, s, l = (max + min) / 2;
    
    if (max === min) {
        h = s = 0;
    } else {
        const d = max - min;
        s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
        
        switch (max) {
            case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break;
            case g: h = ((b - r) / d + 2) / 6; break;
            case b: h = ((r - g) / d + 4) / 6; break;
        }
    }
    
    return {
        h: Math.round(h * 360),
        s: Math.round(s * 100),
        l: Math.round(l * 100)
    };
}

/**
 * 颜色混合
 */
function blendColors(color1, color2, ratio) {
    const c1 = hexToRgb(color1);
    const c2 = hexToRgb(color2);
    
    return rgbToHex(
        Math.round(c1.r * (1 - ratio) + c2.r * ratio),
        Math.round(c1.g * (1 - ratio) + c2.g * ratio),
        Math.round(c1.b * (1 - ratio) + c2.b * ratio)
    );
}

/**
 * 获取对比色
 */
function getContrastColor(hex) {
    const rgb = hexToRgb(hex);
    // 计算亮度
    const luminance = (0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b) / 255;
    return luminance > 0.5 ? '#000000' : '#FFFFFF';
}

// 使用示例
console.log(rgbToHex(255, 128, 0));      // '#ff8000'
console.log(hexToRgb('#4D7CFF'));        // { r: 77, g: 124, b: 255 }
console.log(blendColors('#FF0000', '#0000FF', 0.5)); // 紫色

4.3 填充与描边样式

4.3.1 fillStyle 属性

fillStyle 设置填充图形时使用的颜色或样式:

javascript
// 设置填充颜色
ctx.fillStyle = '#4D7CFF';

// 绘制填充矩形
ctx.fillRect(50, 50, 200, 100);

// 绘制填充圆形
ctx.beginPath();
ctx.arc(300, 100, 50, 0, Math.PI * 2);
ctx.fill();

fillStyle 可以接受:

  1. 颜色字符串:任何有效的 CSS 颜色值
  2. 渐变对象CanvasGradient(稍后详细介绍)
  3. 图案对象CanvasPattern(稍后详细介绍)

4.3.2 strokeStyle 属性

strokeStyle 设置描边时使用的颜色或样式:

javascript
// 设置描边颜色
ctx.strokeStyle = '#FF6B6B';
ctx.lineWidth = 3;

// 绘制描边矩形
ctx.strokeRect(50, 50, 200, 100);

// 绘制描边圆形
ctx.beginPath();
ctx.arc(300, 100, 50, 0, Math.PI * 2);
ctx.stroke();

4.3.3 样式的作用域

fillStylestrokeStyle状态属性,一旦设置就会一直生效,直到被修改或被 restore() 恢复:

javascript
// 设置蓝色填充
ctx.fillStyle = '#4D7CFF';
ctx.fillRect(50, 50, 100, 100);   // 蓝色

ctx.fillRect(200, 50, 100, 100);  // 仍然是蓝色!

// 修改为红色
ctx.fillStyle = '#FF6B6B';
ctx.fillRect(350, 50, 100, 100);  // 红色

使用 save/restore 管理样式

javascript
// 保存当前状态
ctx.save();

ctx.fillStyle = '#FF6B6B';
ctx.fillRect(50, 50, 100, 100);

// 恢复之前的状态
ctx.restore();
// fillStyle 恢复为之前的值

4.3.4 全局透明度

globalAlpha 属性设置全局透明度,影响后续所有绑制操作:

javascript
// 默认值是 1(完全不透明)
ctx.globalAlpha = 1;

// 设置 50% 透明
ctx.globalAlpha = 0.5;
ctx.fillStyle = '#FF0000';
ctx.fillRect(50, 50, 100, 100);  // 半透明红色

// 设置 25% 透明
ctx.globalAlpha = 0.25;
ctx.fillRect(100, 100, 100, 100);  // 更透明的红色

// 记得恢复
ctx.globalAlpha = 1;

globalAlpha vs rgba

属性作用范围使用场景
globalAlpha影响所有后续绘制需要临时改变所有内容的透明度
rgba()只影响设置的颜色需要精确控制单个颜色的透明度
javascript
// globalAlpha 影响所有内容
ctx.globalAlpha = 0.5;
ctx.fillStyle = '#FF0000';
ctx.fillRect(50, 50, 100, 100);  // 半透明
ctx.fillStyle = '#0000FF';
ctx.fillRect(200, 50, 100, 100); // 也是半透明

// rgba 只影响设置的颜色
ctx.globalAlpha = 1;  // 恢复
ctx.fillStyle = 'rgba(255, 0, 0, 0.5)';
ctx.fillRect(50, 200, 100, 100);  // 半透明红色
ctx.fillStyle = '#0000FF';
ctx.fillRect(200, 200, 100, 100); // 不透明蓝色

4.4 线条样式

Canvas 提供了丰富的线条样式控制。

4.4.1 lineWidth - 线条宽度

lineWidth 设置线条的粗细,单位是像素:

javascript
// 不同线宽示例
const widths = [1, 2, 4, 8, 16];

widths.forEach((width, index) => {
    ctx.lineWidth = width;
    ctx.beginPath();
    ctx.moveTo(50, 50 + index * 40);
    ctx.lineTo(300, 50 + index * 40);
    ctx.stroke();
    
    // 标注
    ctx.fillStyle = '#333';
    ctx.font = '14px sans-serif';
    ctx.fillText(`lineWidth: ${width}`, 310, 55 + index * 40);
});

线宽的注意事项

  1. 默认值是 1
  2. 可以是小数:如 0.51.5
  3. 必须大于 0:设置为 0 或负数无效
  4. 线条以路径为中心扩展:一半在内,一半在外
lineWidth = 10 的线条:

路径位置(无宽度):────────────

实际渲染:
           │ 5px
   ════════════════
           │ 5px
   
线条向两边各扩展 lineWidth/2

4.4.2 lineCap - 线端样式

lineCap 控制线条端点的形状:

javascript
const caps = ['butt', 'round', 'square'];

caps.forEach((cap, index) => {
    ctx.lineCap = cap;
    ctx.lineWidth = 20;
    ctx.strokeStyle = '#4D7CFF';
    
    ctx.beginPath();
    ctx.moveTo(100, 50 + index * 60);
    ctx.lineTo(300, 50 + index * 60);
    ctx.stroke();
    
    // 显示实际线条位置
    ctx.strokeStyle = '#FF6B6B';
    ctx.lineWidth = 1;
    ctx.setLineDash([5, 5]);
    ctx.beginPath();
    ctx.moveTo(100, 50 + index * 60);
    ctx.lineTo(300, 50 + index * 60);
    ctx.stroke();
    ctx.setLineDash([]);
    
    ctx.fillStyle = '#333';
    ctx.font = '14px sans-serif';
    ctx.fillText(cap, 320, 55 + index * 60);
});

三种 lineCap 的区别

butt(默认):
端点与线条端点齐平
──────────────────
100              300

round:
端点是半圆
●──────────────────●
90                310
(向两端各延伸 lineWidth/2)

square:
端点是方形
■──────────────────■
90                310
(向两端各延伸 lineWidth/2)

什么时候使用不同的 lineCap?

效果适用场景
butt平直端点精确对齐、网格线
round圆形端点手绘风格、平滑感
square方形端点类似 round 但保持直角

4.4.3 lineJoin - 连接样式

lineJoin 控制两条线段连接处的形状:

javascript
const joins = ['miter', 'round', 'bevel'];

joins.forEach((join, index) => {
    ctx.lineJoin = join;
    ctx.lineWidth = 20;
    ctx.strokeStyle = '#4D7CFF';
    
    const y = 100 + index * 120;
    
    ctx.beginPath();
    ctx.moveTo(50, y);
    ctx.lineTo(150, y - 50);
    ctx.lineTo(250, y);
    ctx.stroke();
    
    ctx.fillStyle = '#333';
    ctx.font = '14px sans-serif';
    ctx.fillText(join, 270, y);
});

三种 lineJoin 的区别

miter(默认)- 尖角:
         ╱╲
        ╱  ╲
       ╱    ╲

round - 圆角:
         ╱╲
        ╱  ╲
       (    )

bevel - 斜角:
         ╱╲
        ╱__╲

4.4.4 miterLimit - 斜接限制

lineJoinmiter 时,如果两条线的夹角很小,尖角会变得非常长。miterLimit 用于限制尖角的最大长度:

javascript
ctx.lineWidth = 10;
ctx.lineJoin = 'miter';

// miterLimit 的作用
// 当尖角长度 / 线宽 > miterLimit 时,自动改用 bevel

ctx.miterLimit = 10;  // 默认值
// 如果尖角过长,会自动变成 bevel 样式

ctx.miterLimit = 1;   // 几乎总是 bevel
ctx.miterLimit = 100; // 允许很长的尖角

miterLimit 的数学含义

              ╱ 尖角长度


        ───╱───
         ╲│╱

          │ 线宽
          
miterLimit = 尖角长度 / 线宽

当这个比值超过 miterLimit 时,使用 bevel 替代 miter

4.4.5 虚线 - setLineDash

setLineDash(segments) 方法设置虚线样式:

javascript
// segments 是一个数组,描述实线和空白的长度交替

// 简单虚线:10px 实线,10px 空白
ctx.setLineDash([10, 10]);

// 不同实线和空白长度:20px 实线,5px 空白
ctx.setLineDash([20, 5]);

// 复杂模式:长-短-长模式
ctx.setLineDash([20, 5, 5, 5]);

// 取消虚线(恢复实线)
ctx.setLineDash([]);

虚线模式详解

javascript
// [实线, 空白] 交替重复

// [10, 10]:
// ████████──────────████████──────────

// [20, 5]:
// ████████████████████─────████████████████████─────

// [20, 5, 5, 5]:
// ████████████████████─────█████─────████████████████████─────█████─────

// [15, 5, 5, 5, 5, 5]:
// 长-短-短 模式

获取当前虚线设置

javascript
ctx.setLineDash([10, 5]);
const currentDash = ctx.getLineDash();
console.log(currentDash);  // [10, 5]

4.4.6 lineDashOffset - 虚线偏移

lineDashOffset 设置虚线的起始偏移量,可用于创建动画效果:

javascript
// 静态偏移
ctx.setLineDash([10, 10]);
ctx.lineDashOffset = 5;  // 虚线向后偏移 5px

// 动画效果:蚂蚁线
let offset = 0;

function animateDash() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    ctx.setLineDash([10, 5]);
    ctx.lineDashOffset = offset;
    
    ctx.strokeRect(50, 50, 200, 100);
    
    offset++;
    if (offset > 15) offset = 0;  // 重置
    
    requestAnimationFrame(animateDash);
}

animateDash();

蚂蚁线(Marching Ants)效果

这种效果常用于选区边框,看起来像一群蚂蚁在边缘行走:

javascript
class MarchingAnts {
    constructor(ctx) {
        this.ctx = ctx;
        this.offset = 0;
        this.dashPattern = [4, 4];
        this.speed = 0.5;
    }
    
    draw(x, y, width, height) {
        const { ctx } = this;
        
        ctx.save();
        ctx.strokeStyle = '#000';
        ctx.lineWidth = 1;
        ctx.setLineDash(this.dashPattern);
        ctx.lineDashOffset = this.offset;
        ctx.strokeRect(x, y, width, height);
        ctx.restore();
        
        // 更新偏移
        this.offset -= this.speed;
    }
}

const ants = new MarchingAnts(ctx);

function animate() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ants.draw(100, 100, 200, 150);
    requestAnimationFrame(animate);
}

animate();

4.4.7 综合示例:自定义线条

javascript
/**
 * 绘制带样式的线条
 */
function drawStyledLine(ctx, x1, y1, x2, y2, options = {}) {
    const {
        color = '#333',
        width = 1,
        cap = 'butt',
        dash = [],
        dashOffset = 0
    } = options;
    
    ctx.save();
    ctx.strokeStyle = color;
    ctx.lineWidth = width;
    ctx.lineCap = cap;
    ctx.setLineDash(dash);
    ctx.lineDashOffset = dashOffset;
    
    ctx.beginPath();
    ctx.moveTo(x1, y1);
    ctx.lineTo(x2, y2);
    ctx.stroke();
    
    ctx.restore();
}

// 实线
drawStyledLine(ctx, 50, 50, 300, 50, {
    color: '#4D7CFF',
    width: 3
});

// 虚线
drawStyledLine(ctx, 50, 80, 300, 80, {
    color: '#FF6B6B',
    width: 2,
    dash: [10, 5]
});

// 点线
drawStyledLine(ctx, 50, 110, 300, 110, {
    color: '#4ECDC4',
    width: 3,
    cap: 'round',
    dash: [0, 10]
});

// 点划线
drawStyledLine(ctx, 50, 140, 300, 140, {
    color: '#9B59B6',
    width: 2,
    dash: [20, 5, 5, 5]
});

4.5 渐变效果

渐变是从一种颜色平滑过渡到另一种颜色的效果。Canvas 支持三种渐变类型。

4.5.1 线性渐变(Linear Gradient)

线性渐变沿一条直线方向变化颜色。

创建线性渐变

javascript
const gradient = ctx.createLinearGradient(x0, y0, x1, y1);

参数定义了渐变的方向和范围:

参数含义
x0, y0渐变起点坐标
x1, y1渐变终点坐标

添加颜色停止点

javascript
gradient.addColorStop(offset, color);
参数含义
offset位置,0-1 之间的数值
color该位置的颜色

基础示例

javascript
// 创建从左到右的渐变
const gradient = ctx.createLinearGradient(0, 0, 400, 0);

// 添加颜色停止点
gradient.addColorStop(0, '#FF6B6B');    // 起点:红色
gradient.addColorStop(1, '#4D7CFF');    // 终点:蓝色

// 使用渐变填充
ctx.fillStyle = gradient;
ctx.fillRect(50, 50, 400, 100);

渐变方向图解

javascript
// 从左到右
createLinearGradient(0, 0, 400, 0);
// ████████████████████████████████████
// 红                              蓝

// 从上到下
createLinearGradient(0, 0, 0, 200);
// ████ 红
// ████
// ████
// ████
// ████ 蓝

// 对角线(左上到右下)
createLinearGradient(0, 0, 400, 200);
// 红 ╲
//    ╲╲
//     ╲╲
//      ╲╲ 蓝

多色渐变

javascript
const rainbow = ctx.createLinearGradient(0, 0, 600, 0);
rainbow.addColorStop(0, '#FF0000');      // 红
rainbow.addColorStop(0.17, '#FF7F00');   // 橙
rainbow.addColorStop(0.33, '#FFFF00');   // 黄
rainbow.addColorStop(0.5, '#00FF00');    // 绿
rainbow.addColorStop(0.67, '#0000FF');   // 蓝
rainbow.addColorStop(0.83, '#4B0082');   // 靛
rainbow.addColorStop(1, '#9400D3');      // 紫

ctx.fillStyle = rainbow;
ctx.fillRect(50, 50, 600, 100);

4.5.2 径向渐变(Radial Gradient)

径向渐变从一个圆向外扩散(或从外向内收缩)。

创建径向渐变

javascript
const gradient = ctx.createRadialGradient(x0, y0, r0, x1, y1, r1);
参数含义
x0, y0, r0内圆的圆心和半径
x1, y1, r1外圆的圆心和半径

基础示例

javascript
// 创建同心圆渐变
const gradient = ctx.createRadialGradient(200, 200, 0, 200, 200, 150);

gradient.addColorStop(0, '#FF6B6B');    // 中心:红色
gradient.addColorStop(1, '#4D7CFF');    // 边缘:蓝色

ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(200, 200, 150, 0, Math.PI * 2);
ctx.fill();

径向渐变图解

同心圆渐变(常见):

        ╭──────────╮
       ╱   ╭────╮   ╲
      ╱   ╱ ●红  ╲   ╲
     │   │  中心  │   │
      ╲   ╲      ╱   ╱
       ╲   ╰────╯   ╱ 蓝
        ╰──────────╯

内外圆不同心(光照效果):

        ╭──────────╮
       ╱  ●红       ╲
      ╱   ↘         ╲
     │      ↘        │
      ╲       ↘     ╱ 蓝
       ╲        ↘  ╱
        ╰──────────╯

创建 3D 球体效果

javascript
function draw3DSphere(ctx, cx, cy, radius, color) {
    // 计算高光位置(左上方)
    const highlightX = cx - radius * 0.3;
    const highlightY = cy - radius * 0.3;
    
    // 创建径向渐变
    const gradient = ctx.createRadialGradient(
        highlightX, highlightY, radius * 0.1,  // 内圆:高光点
        cx, cy, radius                          // 外圆:球体边缘
    );
    
    // 从白色高光到颜色到深色
    gradient.addColorStop(0, '#FFFFFF');
    gradient.addColorStop(0.2, color);
    gradient.addColorStop(1, darkenColor(color, 0.3));
    
    ctx.fillStyle = gradient;
    ctx.beginPath();
    ctx.arc(cx, cy, radius, 0, Math.PI * 2);
    ctx.fill();
}

// 辅助函数:颜色变暗
function darkenColor(hex, amount) {
    const rgb = hexToRgb(hex);
    return rgbToHex(
        Math.round(rgb.r * (1 - amount)),
        Math.round(rgb.g * (1 - amount)),
        Math.round(rgb.b * (1 - amount))
    );
}

// 绘制一组 3D 球体
draw3DSphere(ctx, 100, 150, 60, '#FF6B6B');
draw3DSphere(ctx, 250, 150, 60, '#4ECDC4');
draw3DSphere(ctx, 400, 150, 60, '#45B7D1');

4.5.3 锥形渐变(Conic Gradient)

锥形渐变围绕中心点旋转变化颜色,类似于色轮。

注意createConicGradient 是较新的 API,需要检查浏览器支持。

javascript
// 检查支持
if (typeof ctx.createConicGradient === 'function') {
    const gradient = ctx.createConicGradient(startAngle, cx, cy);
    
    gradient.addColorStop(0, '#FF0000');
    gradient.addColorStop(0.33, '#00FF00');
    gradient.addColorStop(0.66, '#0000FF');
    gradient.addColorStop(1, '#FF0000');  // 回到起始颜色
    
    ctx.fillStyle = gradient;
    ctx.beginPath();
    ctx.arc(200, 200, 100, 0, Math.PI * 2);
    ctx.fill();
}

锥形渐变图解

               0° (红)

      紫 ────────●──────── 绿

               180° (蓝)

颜色围绕中心点旋转分布

手动实现锥形渐变(兼容性方案):

javascript
function drawConicGradient(ctx, cx, cy, radius, colors) {
    const segmentAngle = (Math.PI * 2) / colors.length;
    
    colors.forEach((color, index) => {
        const startAngle = index * segmentAngle - Math.PI / 2;
        const endAngle = startAngle + segmentAngle;
        
        // 为每个扇形创建径向渐变,模拟平滑过渡
        ctx.fillStyle = color;
        ctx.beginPath();
        ctx.moveTo(cx, cy);
        ctx.arc(cx, cy, radius, startAngle, endAngle);
        ctx.closePath();
        ctx.fill();
    });
}

// 创建色轮
const hueColors = [];
for (let i = 0; i < 360; i += 10) {
    hueColors.push(`hsl(${i}, 100%, 50%)`);
}
drawConicGradient(ctx, 200, 200, 100, hueColors);

4.5.4 渐变的高级技巧

1. 渐变是相对于 Canvas 坐标系的

渐变的坐标是相对于整个 Canvas 的,而不是相对于绘制的图形:

javascript
// 创建一个覆盖整个 Canvas 的渐变
const gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
gradient.addColorStop(0, 'red');
gradient.addColorStop(1, 'blue');

ctx.fillStyle = gradient;

// 绘制两个矩形,它们显示渐变的不同部分
ctx.fillRect(50, 50, 100, 100);   // 显示渐变的左边部分(偏红)
ctx.fillRect(350, 50, 100, 100);  // 显示渐变的右边部分(偏蓝)

2. 为每个图形创建独立渐变

如果希望每个图形都有完整的渐变效果:

javascript
function fillWithGradient(ctx, x, y, width, height, colors) {
    // 为这个图形创建专属渐变
    const gradient = ctx.createLinearGradient(x, y, x + width, y);
    colors.forEach((color, index) => {
        gradient.addColorStop(index / (colors.length - 1), color);
    });
    
    ctx.fillStyle = gradient;
    ctx.fillRect(x, y, width, height);
}

// 每个矩形都有完整的红到蓝渐变
fillWithGradient(ctx, 50, 50, 100, 100, ['red', 'blue']);
fillWithGradient(ctx, 200, 50, 100, 100, ['red', 'blue']);
fillWithGradient(ctx, 350, 50, 100, 100, ['red', 'blue']);

3. 创建发光效果

javascript
function drawGlow(ctx, x, y, radius, color) {
    // 创建多层渐变,模拟发光
    const layers = 5;
    
    for (let i = layers; i >= 0; i--) {
        const layerRadius = radius * (1 + i * 0.2);
        const alpha = 1 / (i + 1);
        
        const gradient = ctx.createRadialGradient(
            x, y, 0,
            x, y, layerRadius
        );
        
        gradient.addColorStop(0, `rgba(${hexToRgbString(color)}, ${alpha})`);
        gradient.addColorStop(1, `rgba(${hexToRgbString(color)}, 0)`);
        
        ctx.fillStyle = gradient;
        ctx.beginPath();
        ctx.arc(x, y, layerRadius, 0, Math.PI * 2);
        ctx.fill();
    }
}

function hexToRgbString(hex) {
    const rgb = hexToRgb(hex);
    return `${rgb.r}, ${rgb.g}, ${rgb.b}`;
}

// 绘制发光效果
drawGlow(ctx, 200, 200, 50, '#4D7CFF');

4.6 图案填充

除了纯色和渐变,Canvas 还支持使用图像作为填充图案。

4.6.1 createPattern 方法

javascript
const pattern = ctx.createPattern(image, repetition);
参数说明
image图像源(Image、Canvas、Video 等)
repetition重复方式

重复方式选项

效果
'repeat'水平和垂直方向都重复(默认)
'repeat-x'只在水平方向重复
'repeat-y'只在垂直方向重复
'no-repeat'不重复

4.6.2 使用图像作为图案

javascript
// 加载图像
const img = new Image();
img.onload = function() {
    // 创建图案
    const pattern = ctx.createPattern(img, 'repeat');
    
    // 使用图案填充
    ctx.fillStyle = pattern;
    ctx.fillRect(0, 0, canvas.width, canvas.height);
};
img.src = 'pattern.png';

完整示例

javascript
function loadImage(src) {
    return new Promise((resolve, reject) => {
        const img = new Image();
        img.onload = () => resolve(img);
        img.onerror = reject;
        img.src = src;
    });
}

async function drawWithPattern() {
    const img = await loadImage('texture.png');
    const pattern = ctx.createPattern(img, 'repeat');
    
    ctx.fillStyle = pattern;
    
    // 填充矩形
    ctx.fillRect(50, 50, 300, 200);
    
    // 填充圆形
    ctx.beginPath();
    ctx.arc(500, 150, 100, 0, Math.PI * 2);
    ctx.fill();
}

drawWithPattern();

4.6.3 使用 Canvas 作为图案

可以用另一个 Canvas 作为图案源,这允许创建动态图案:

javascript
// 创建图案 Canvas
function createPatternCanvas(size, color1, color2) {
    const patternCanvas = document.createElement('canvas');
    patternCanvas.width = size;
    patternCanvas.height = size;
    const pCtx = patternCanvas.getContext('2d');
    
    // 绘制棋盘格图案
    pCtx.fillStyle = color1;
    pCtx.fillRect(0, 0, size, size);
    
    pCtx.fillStyle = color2;
    pCtx.fillRect(0, 0, size / 2, size / 2);
    pCtx.fillRect(size / 2, size / 2, size / 2, size / 2);
    
    return patternCanvas;
}

// 使用图案
const checkerCanvas = createPatternCanvas(20, '#FFFFFF', '#CCCCCC');
const pattern = ctx.createPattern(checkerCanvas, 'repeat');

ctx.fillStyle = pattern;
ctx.fillRect(50, 50, 300, 200);

4.6.4 图案变换

图案可以通过 setTransform 方法进行变换:

javascript
const img = await loadImage('texture.png');
const pattern = ctx.createPattern(img, 'repeat');

// 旋转图案
const matrix = new DOMMatrix();
matrix.rotateSelf(45);  // 旋转 45 度
pattern.setTransform(matrix);

ctx.fillStyle = pattern;
ctx.fillRect(50, 50, 300, 200);

4.6.5 实用图案生成器

javascript
/**
 * 创建条纹图案
 */
function createStripePattern(ctx, width, color1, color2, angle = 0) {
    const patternCanvas = document.createElement('canvas');
    patternCanvas.width = width * 2;
    patternCanvas.height = width * 2;
    const pCtx = patternCanvas.getContext('2d');
    
    pCtx.fillStyle = color1;
    pCtx.fillRect(0, 0, width * 2, width * 2);
    
    pCtx.fillStyle = color2;
    pCtx.fillRect(0, 0, width, width * 2);
    
    const pattern = ctx.createPattern(patternCanvas, 'repeat');
    
    if (angle !== 0) {
        const matrix = new DOMMatrix();
        matrix.rotateSelf(angle);
        pattern.setTransform(matrix);
    }
    
    return pattern;
}

/**
 * 创建圆点图案
 */
function createDotPattern(ctx, spacing, radius, bgColor, dotColor) {
    const patternCanvas = document.createElement('canvas');
    patternCanvas.width = spacing;
    patternCanvas.height = spacing;
    const pCtx = patternCanvas.getContext('2d');
    
    pCtx.fillStyle = bgColor;
    pCtx.fillRect(0, 0, spacing, spacing);
    
    pCtx.fillStyle = dotColor;
    pCtx.beginPath();
    pCtx.arc(spacing / 2, spacing / 2, radius, 0, Math.PI * 2);
    pCtx.fill();
    
    return ctx.createPattern(patternCanvas, 'repeat');
}

/**
 * 创建网格图案
 */
function createGridPattern(ctx, size, lineWidth, bgColor, lineColor) {
    const patternCanvas = document.createElement('canvas');
    patternCanvas.width = size;
    patternCanvas.height = size;
    const pCtx = patternCanvas.getContext('2d');
    
    pCtx.fillStyle = bgColor;
    pCtx.fillRect(0, 0, size, size);
    
    pCtx.strokeStyle = lineColor;
    pCtx.lineWidth = lineWidth;
    pCtx.beginPath();
    pCtx.moveTo(size, 0);
    pCtx.lineTo(size, size);
    pCtx.moveTo(0, size);
    pCtx.lineTo(size, size);
    pCtx.stroke();
    
    return ctx.createPattern(patternCanvas, 'repeat');
}

// 使用示例
ctx.fillStyle = createStripePattern(ctx, 10, '#4D7CFF', '#FFFFFF', 45);
ctx.fillRect(50, 50, 150, 150);

ctx.fillStyle = createDotPattern(ctx, 20, 4, '#FFFFFF', '#FF6B6B');
ctx.fillRect(220, 50, 150, 150);

ctx.fillStyle = createGridPattern(ctx, 20, 1, '#F5F5F5', '#CCCCCC');
ctx.fillRect(390, 50, 150, 150);

4.7 阴影效果

Canvas 可以为绘制的图形添加阴影效果。

4.7.1 阴影属性

属性说明默认值
shadowColor阴影颜色'rgba(0, 0, 0, 0)'(透明)
shadowBlur模糊程度0
shadowOffsetX水平偏移0
shadowOffsetY垂直偏移0

4.7.2 基础阴影

javascript
// 设置阴影
ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';  // 半透明黑色
ctx.shadowBlur = 10;                      // 模糊半径
ctx.shadowOffsetX = 5;                    // 水平偏移
ctx.shadowOffsetY = 5;                    // 垂直偏移

// 绘制带阴影的矩形
ctx.fillStyle = '#4D7CFF';
ctx.fillRect(50, 50, 200, 100);

// 注意:阴影会影响后续所有绘制
// 需要重置阴影
ctx.shadowColor = 'transparent';
// 或
ctx.shadowBlur = 0;

4.7.3 阴影参数详解

shadowBlur - 模糊程度

javascript
// 不同模糊程度的效果
const blurs = [0, 5, 10, 20, 40];

blurs.forEach((blur, index) => {
    ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';
    ctx.shadowBlur = blur;
    ctx.shadowOffsetX = 5;
    ctx.shadowOffsetY = 5;
    
    ctx.fillStyle = '#4D7CFF';
    ctx.fillRect(50 + index * 120, 50, 80, 80);
    
    // 重置
    ctx.shadowColor = 'transparent';
    
    // 标注
    ctx.fillStyle = '#333';
    ctx.font = '12px sans-serif';
    ctx.fillText(`blur: ${blur}`, 50 + index * 120, 160);
});
shadowBlur 效果对比:

blur: 0      blur: 5      blur: 10     blur: 20
┌────┐       ┌────┐       ┌────┐       ┌────┐
│    │▌      │    │░░     │    │▒▒▒    │    │▓▓▓▓
│    │▌      │    │░░     │    │▒▒▒    │    │▓▓▓▓
└────┘▌      └────┘░░     └────┘▒▒▒    └────┘▓▓▓▓
 ▀▀▀▀▀        ░░░░░░       ▒▒▒▒▒▒▒      ▓▓▓▓▓▓▓▓
锐利边缘      轻微模糊      中等模糊      大幅模糊

shadowOffset - 偏移方向

javascript
// 不同偏移方向的效果

// 右下阴影(常见)
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 5;

// 左下阴影
ctx.shadowOffsetX = -5;
ctx.shadowOffsetY = 5;

// 右上阴影
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = -5;

// 四周阴影(偏移为 0,只有模糊)
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = 15;

4.7.4 彩色阴影

阴影不一定是黑色的,可以使用任何颜色:

javascript
function drawColorfulShadow(ctx, x, y, size, color) {
    ctx.shadowColor = color;
    ctx.shadowBlur = 20;
    ctx.shadowOffsetX = 0;
    ctx.shadowOffsetY = 10;
    
    ctx.fillStyle = color;
    ctx.fillRect(x, y, size, size);
    
    ctx.shadowColor = 'transparent';
}

drawColorfulShadow(ctx, 50, 50, 100, '#FF6B6B');
drawColorfulShadow(ctx, 200, 50, 100, '#4ECDC4');
drawColorfulShadow(ctx, 350, 50, 100, '#FFD93D');

4.7.5 内阴影效果

Canvas 原生不支持内阴影,但可以通过裁剪技巧模拟:

javascript
function drawInnerShadow(ctx, x, y, width, height, shadowBlur, shadowColor) {
    ctx.save();
    
    // 创建裁剪区域
    ctx.beginPath();
    ctx.rect(x, y, width, height);
    ctx.clip();
    
    // 绘制大于裁剪区域的阴影
    ctx.shadowColor = shadowColor;
    ctx.shadowBlur = shadowBlur;
    ctx.shadowOffsetX = 0;
    ctx.shadowOffsetY = 0;
    
    // 绘制一个包围裁剪区域的路径
    ctx.beginPath();
    ctx.rect(x - shadowBlur * 2, y - shadowBlur * 2, 
             width + shadowBlur * 4, shadowBlur * 2);  // 上
    ctx.rect(x - shadowBlur * 2, y + height, 
             width + shadowBlur * 4, shadowBlur * 2);  // 下
    ctx.rect(x - shadowBlur * 2, y, 
             shadowBlur * 2, height);  // 左
    ctx.rect(x + width, y, 
             shadowBlur * 2, height);  // 右
    
    ctx.fillStyle = shadowColor;
    ctx.fill();
    
    ctx.restore();
}

// 使用
ctx.fillStyle = '#F5F5F5';
ctx.fillRect(50, 50, 200, 100);
drawInnerShadow(ctx, 50, 50, 200, 100, 15, 'rgba(0,0,0,0.3)');

4.7.6 阴影性能考虑

阴影渲染是相对耗费性能的操作。在高频绑制场景(如动画)中需要注意:

javascript
// 不推荐:每帧都绘制阴影
function badAnimate() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    ctx.shadowColor = 'rgba(0,0,0,0.5)';
    ctx.shadowBlur = 20;
    ctx.fillRect(x, y, 100, 100);  // 每帧计算阴影
    
    requestAnimationFrame(badAnimate);
}

// 推荐:预渲染带阴影的图形到离屏 Canvas
const shadowCache = document.createElement('canvas');
shadowCache.width = 140;  // 100 + 阴影范围
shadowCache.height = 140;
const sCtx = shadowCache.getContext('2d');

sCtx.shadowColor = 'rgba(0,0,0,0.5)';
sCtx.shadowBlur = 20;
sCtx.fillStyle = '#4D7CFF';
sCtx.fillRect(20, 20, 100, 100);

function goodAnimate() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.drawImage(shadowCache, x - 20, y - 20);  // 直接绘制缓存
    requestAnimationFrame(goodAnimate);
}

4.8 综合实战

4.8.1 创建按钮组件

javascript
function drawButton(ctx, x, y, width, height, text, options = {}) {
    const {
        bgColor = '#4D7CFF',
        textColor = '#FFFFFF',
        borderRadius = 8,
        shadow = true,
        gradient = true
    } = options;
    
    ctx.save();
    
    // 阴影
    if (shadow) {
        ctx.shadowColor = 'rgba(0, 0, 0, 0.2)';
        ctx.shadowBlur = 10;
        ctx.shadowOffsetY = 4;
    }
    
    // 创建圆角路径
    roundRect(ctx, x, y, width, height, borderRadius);
    
    // 渐变背景
    if (gradient) {
        const grad = ctx.createLinearGradient(x, y, x, y + height);
        grad.addColorStop(0, lightenColor(bgColor, 0.1));
        grad.addColorStop(1, bgColor);
        ctx.fillStyle = grad;
    } else {
        ctx.fillStyle = bgColor;
    }
    
    ctx.fill();
    
    // 重置阴影
    ctx.shadowColor = 'transparent';
    
    // 文字
    ctx.fillStyle = textColor;
    ctx.font = '16px sans-serif';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText(text, x + width / 2, y + height / 2);
    
    ctx.restore();
}

// 辅助函数
function lightenColor(hex, amount) {
    const rgb = hexToRgb(hex);
    return rgbToHex(
        Math.min(255, Math.round(rgb.r + (255 - rgb.r) * amount)),
        Math.min(255, Math.round(rgb.g + (255 - rgb.g) * amount)),
        Math.min(255, Math.round(rgb.b + (255 - rgb.b) * amount))
    );
}

// 使用
drawButton(ctx, 50, 50, 120, 44, '确定');
drawButton(ctx, 190, 50, 120, 44, '取消', { bgColor: '#FF6B6B' });
drawButton(ctx, 330, 50, 120, 44, '禁用', { bgColor: '#CCCCCC', shadow: false });

4.8.2 创建卡片组件

javascript
function drawCard(ctx, x, y, width, height, options = {}) {
    const {
        backgroundColor = '#FFFFFF',
        shadowColor = 'rgba(0, 0, 0, 0.1)',
        shadowBlur = 20,
        borderRadius = 12,
        title = '',
        content = ''
    } = options;
    
    ctx.save();
    
    // 卡片阴影
    ctx.shadowColor = shadowColor;
    ctx.shadowBlur = shadowBlur;
    ctx.shadowOffsetX = 0;
    ctx.shadowOffsetY = 4;
    
    // 卡片背景
    roundRect(ctx, x, y, width, height, borderRadius);
    ctx.fillStyle = backgroundColor;
    ctx.fill();
    
    // 重置阴影
    ctx.shadowColor = 'transparent';
    
    // 标题
    if (title) {
        ctx.fillStyle = '#333';
        ctx.font = 'bold 18px sans-serif';
        ctx.textAlign = 'left';
        ctx.textBaseline = 'top';
        ctx.fillText(title, x + 20, y + 20);
        
        // 分割线
        ctx.strokeStyle = '#EEEEEE';
        ctx.lineWidth = 1;
        ctx.beginPath();
        ctx.moveTo(x + 20, y + 50);
        ctx.lineTo(x + width - 20, y + 50);
        ctx.stroke();
    }
    
    // 内容
    if (content) {
        ctx.fillStyle = '#666';
        ctx.font = '14px sans-serif';
        wrapText(ctx, content, x + 20, y + 70, width - 40, 20);
    }
    
    ctx.restore();
}

// 文本换行
function wrapText(ctx, text, x, y, maxWidth, lineHeight) {
    const words = text.split('');
    let line = '';
    let currentY = y;
    
    for (let i = 0; i < words.length; i++) {
        const testLine = line + words[i];
        const metrics = ctx.measureText(testLine);
        
        if (metrics.width > maxWidth && i > 0) {
            ctx.fillText(line, x, currentY);
            line = words[i];
            currentY += lineHeight;
        } else {
            line = testLine;
        }
    }
    ctx.fillText(line, x, currentY);
}

// 使用
drawCard(ctx, 50, 50, 300, 200, {
    title: '卡片标题',
    content: '这是卡片的内容区域,可以显示一些描述文字。支持自动换行功能。'
});

4.8.3 创建数据可视化图表

javascript
function drawPieChart(ctx, cx, cy, radius, data, options = {}) {
    const {
        showLabels = true,
        showLegend = true,
        legendX = cx + radius + 50,
        legendY = cy - radius
    } = options;
    
    const total = data.reduce((sum, item) => sum + item.value, 0);
    let currentAngle = -Math.PI / 2;
    
    data.forEach((item, index) => {
        const sliceAngle = (item.value / total) * Math.PI * 2;
        
        // 绘制扇形
        ctx.save();
        ctx.shadowColor = 'rgba(0, 0, 0, 0.1)';
        ctx.shadowBlur = 5;
        ctx.shadowOffsetX = 2;
        ctx.shadowOffsetY = 2;
        
        ctx.beginPath();
        ctx.moveTo(cx, cy);
        ctx.arc(cx, cy, radius, currentAngle, currentAngle + sliceAngle);
        ctx.closePath();
        
        ctx.fillStyle = item.color;
        ctx.fill();
        ctx.restore();
        
        // 标签
        if (showLabels) {
            const labelAngle = currentAngle + sliceAngle / 2;
            const labelRadius = radius * 0.7;
            const labelX = cx + Math.cos(labelAngle) * labelRadius;
            const labelY = cy + Math.sin(labelAngle) * labelRadius;
            
            const percentage = Math.round(item.value / total * 100);
            
            ctx.fillStyle = '#FFFFFF';
            ctx.font = 'bold 14px sans-serif';
            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';
            ctx.fillText(`${percentage}%`, labelX, labelY);
        }
        
        currentAngle += sliceAngle;
    });
    
    // 图例
    if (showLegend) {
        data.forEach((item, index) => {
            const y = legendY + index * 25;
            
            // 颜色块
            ctx.fillStyle = item.color;
            ctx.fillRect(legendX, y, 16, 16);
            
            // 文字
            ctx.fillStyle = '#333';
            ctx.font = '14px sans-serif';
            ctx.textAlign = 'left';
            ctx.textBaseline = 'middle';
            ctx.fillText(item.label, legendX + 24, y + 8);
        });
    }
}

// 使用
const pieData = [
    { label: '产品A', value: 35, color: '#FF6B6B' },
    { label: '产品B', value: 25, color: '#4ECDC4' },
    { label: '产品C', value: 20, color: '#45B7D1' },
    { label: '产品D', value: 15, color: '#96CEB4' },
    { label: '其他', value: 5, color: '#CCCCCC' }
];

drawPieChart(ctx, 200, 200, 120, pieData);

4.9 本章小结

本章详细介绍了 Canvas 的样式系统:

核心知识

主题要点
颜色格式颜色名、十六进制、rgb()、rgba()、hsl()、hsla()
填充/描边fillStyle、strokeStyle、globalAlpha
线条样式lineWidth、lineCap、lineJoin、miterLimit
虚线setLineDash()、lineDashOffset
渐变线性、径向、锥形渐变
图案createPattern、重复模式、变换
阴影shadowColor、shadowBlur、shadowOffset

颜色选择建议

场景推荐格式
简单颜色十六进制 #RRGGBB
需要透明度rgba()
颜色计算/动画hsl() / hsla()
CSS 兼容任意格式

性能提示

  1. 阴影开销大:尽量使用离屏 Canvas 缓存
  2. 渐变复用:相同渐变可以复用
  3. 图案缓存:使用 Canvas 作为图案源可动态更新

4.10 练习题

基础练习

  1. 创建一个颜色选择器,显示 HSL 色轮

  2. 绘制一组不同线条样式的示例图

  3. 创建一个渐变按钮,hover 时颜色变化

进阶练习

  1. 实现一个卡片组件:

    • 支持阴影
    • 支持渐变背景
    • 支持圆角
  2. 创建一个图案生成器:

    • 支持条纹、圆点、网格
    • 支持自定义颜色和大小

挑战练习

  1. 实现一个完整的饼图组件:
    • 支持动画效果
    • 支持点击交互
    • 支持图例和标签

下一章预告:在第5章中,我们将学习 Canvas 的变换系统——平移、旋转、缩放和矩阵变换,这是创建复杂动画和交互的基础。


文档版本:v1.0
字数统计:约 18,000 字
代码示例:50+ 个

如有转载或 CV 的请标注本站原文地址