在 HTML5 Canvas 中,为图形添加颜色、描边和投影等效果,主要通过配置其 2D 渲染上下文(CanvasRenderingContext2D)的属性来实现。下面将为你详细讲解这四个方面。
🎨 图形着色的区域
Canvas 中的图形着色主要分为两个独立的区域:
-
填充区域 (Fill Area)
- 指图形内部的区域。
- 通过
fill()、fillRect()、fillText()等方法进行绘制。 - 其样式由
fillStyle属性控制。
-
描边区域 (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();
🖌️ 图形着色的方式
着色的核心是 fillStyle 和 strokeStyle 这两个属性。它们可以设置为三种不同类型的值:纯色、渐变和图案。
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),你可以实现各种方向的渐变:
-
水平渐变(左→右):
y1和y2相同,x2 > x1。ctx.createLinearGradient(100, 100, 200, 100); -
垂直渐变(上→下):
x1和x2相同,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.0到1.0之间的浮点数,代表该颜色在渐变圆环上的相对位置。例如,0表示起始点,0.5表示半圈,1表示完整一圈。color:支持任何有效的 CSS 颜色值,如'red','#FF0000','rgba(255,0,0,0.5)'等。
⚙️ 实际应用步骤
- 获取上下文:从
<canvas>元素获取 2D 渲染上下文。 - 创建渐变:调用
createConicGradient()方法,传入起始角度和中心点坐标。 - 添加色标:使用
addColorStop()方法,按顺序添加颜色和它们的位置。 - 应用渐变:将创建好的渐变对象赋值给
fillStyle或strokeStyle。 - 绘制图形:调用绘图方法(如
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. 纹理
纹理可以将一张图片作为图形的底色,如下图所示:
纹理允许你使用一张图片来重复填充一个图形。
- 首先,创建一个
Image对象并加载图片。 - 图片加载完成后,使用
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中图形着色主要针对两个区域:
- 填充区域:图形内部的封闭区域
- 描边区域:图形的轮廓边界
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();
}
六、性能优化建议
- 避免频繁切换样式:批量绘制相同样式的图形
- 使用save/restore:管理样式状态,避免手动重置
- 缓存渐变和图案:重复使用时避免重复创建
- 投影谨慎使用:投影会增加渲染开销
// 优化示例
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图形效果。