前言
Canvas 作为 HTML5 的核心绘图技术,不仅能够绘制基础图形,还提供了丰富的视觉效果和交互能力。本文将探讨 Canvas 的进阶特性,从渐变效果到视频绘制,带你了解 Canvas 技术栈。
1. 渐变效果:让图形更生动
1.1 线性渐变
线性渐变是最常用的渐变类型,通过定义起始点和结束点来创建渐变效果:
// 创建线性渐变对象
// createLinearGradient(x0, y0, x1, y1) - 定义渐变的起始点和结束点
// x0, y0: 渐变起始点坐标
// x1, y1: 渐变结束点坐标
const gradient = ctx.createLinearGradient(100, 200, 400, 500);
// 添加颜色停止点
// addColorStop(offset, color) - 在渐变线上添加颜色
// offset: 0-1之间的值,表示在渐变线上的位置
// color: 该位置的颜色
gradient.addColorStop(0, "red"); // 起始位置为红色
gradient.addColorStop(0.5, "#ffcccc"); // 中间位置为浅红色
gradient.addColorStop(1, "blue"); // 结束位置为蓝色
// 将渐变设置为填充样式
ctx.fillStyle = gradient;
// 绘制矩形,应用渐变填充
ctx.fillRect(100, 200, 300, 300);
技术要点:
createLinearGradient(x0, y0, x1, y1)定义渐变方向addColorStop(offset, color)添加颜色停止点- 支持动画效果,通过改变
addColorStop的位置实现
1.2 径向渐变
径向渐变从中心点向外扩散,常用于创建球体效果:
// 创建径向渐变对象
// createRadialGradient(x0, y0, r0, x1, y1, r1)
// x0, y0, r0: 内圆的圆心和半径
// x1, y1, r1: 外圆的圆心和半径
const radialGradient = ctx.createRadialGradient(300, 200, 0, 300, 200, 100);
// 添加颜色停止点
radialGradient.addColorStop(0, "white"); // 中心为白色
radialGradient.addColorStop(0.7, "yellow"); // 70%位置为黄色
radialGradient.addColorStop(1, "orange"); // 边缘为橙色
// 应用渐变
ctx.fillStyle = radialGradient;
// 绘制圆形
ctx.beginPath();
ctx.arc(300, 200, 100, 0, Math.PI * 2); // 圆心(300,200),半径100
ctx.fill();
1.3 圆锥渐变
圆锥渐变按角度分布颜色,适合创建色轮效果:
// 创建圆锥渐变对象
// createConicGradient(startAngle, x, y)
// startAngle: 起始角度(弧度制)
// x, y: 圆锥渐变的中心点坐标
const conicGradient = ctx.createConicGradient(Math.PI / 4, 300, 200);
// 添加颜色停止点
conicGradient.addColorStop(0, "red"); // 起始角度(45度)为红色
conicGradient.addColorStop(0.5, "yellow"); // 中间位置(225度)为黄色
conicGradient.addColorStop(1, "blue"); // 结束位置(405度)为蓝色
// 应用渐变
ctx.fillStyle = conicGradient;
ctx.fillRect(0, 0, 600, 400);
2. 图案填充:重复的艺术
2.1 基础图案创建
// 创建图片对象用于图案
const img = new Image();
img.src = "./imgs/money.png"; // 设置图片源路径
// 图片加载完成后的回调函数
// 注意:必须在图片加载完成后才能创建pattern
img.onload = function () {
// 创建图案对象
// createPattern(image, repetition)
// image: 图片对象(可以是Image对象,也可以是Canvas对象)
// repetition: 重复方式
// - "repeat": 默认值,在水平和垂直方向都重复
// - "repeat-x": 只在水平方向重复
// - "repeat-y": 只在垂直方向重复
// - "no-repeat": 不重复,只显示一次
const pattern = ctx.createPattern(img, "repeat");
// 将图案设置为填充样式
ctx.fillStyle = pattern;
// 绘制矩形,应用图案填充
ctx.fillRect(0, 0, 600, 400);
};
重复模式选项:
"repeat":默认值,水平和垂直都重复"repeat-x":只在水平方向重复"repeat-y":只在垂直方向重复"no-repeat":不重复,只显示一次
2.2 动态图案效果
// 创建离屏Canvas用于生成图案
const canvas = document.createElement("canvas");
const patternCtx = canvas.getContext("2d");
// 设置Canvas尺寸
canvas.width = 50;
canvas.height = 50;
// 在离屏Canvas上绘制图案内容
// 绘制蓝色方块
patternCtx.fillStyle = "#3498db";
patternCtx.fillRect(0, 0, 25, 25);
// 绘制红色方块
patternCtx.fillStyle = "#e74c3c";
patternCtx.fillRect(25, 25, 25, 25);
// 使用Canvas作为图案源
// 这样可以创建动态的、可编程的图案
const pattern = ctx.createPattern(canvas, "repeat");
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, 600, 400);
3. 线条样式:细节决定品质
3.1 线条宽度和端点
// 设置线条样式属性
// lineWidth: 设置线条宽度,默认值为1px
ctx.lineWidth = 20;
// lineCap: 设置线条端点样式
// - "butt": 平齐端点(默认值)
// - "round": 圆形端点
// - "square": 方形端点
ctx.lineCap = "round";
// lineJoin: 设置线段连接处的样式
// - "miter": 尖角连接(默认值)
// - "round": 圆角连接
// - "bevel": 斜角连接
ctx.lineJoin = "miter";
// miterLimit: 设置斜接面限制值
// 当lineJoin为"miter"时,如果斜接面长度超过此值,会变成"bevel"
ctx.miterLimit = 10;
// 绘制路径
ctx.beginPath(); // 开始新路径
ctx.moveTo(50, 50); // 移动到起始点
ctx.lineTo(150, 100); // 绘制第一条线段
ctx.lineTo(250, 50); // 绘制第二条线段
ctx.stroke(); // 描边绘制
3.2 虚线效果
// 设置虚线样式
// setLineDash(pattern): 设置虚线模式
// 参数是一个数组,表示虚线的模式:[实线长度, 空白长度, 实线长度, 空白长度, ...]
ctx.setLineDash([20, 10]); // 20像素实线,10像素空白
// lineDashOffset: 设置虚线偏移量
// 正值向右偏移,负值向左偏移,用于创建动画效果
ctx.lineDashOffset = 0;
// 动画虚线效果
let offset = 0;
function animateDash() {
offset++; // 每次增加偏移量
ctx.lineDashOffset = offset;
// 请求下一帧动画
// requestAnimationFrame(): 在浏览器下一次重绘之前调用指定函数
requestAnimationFrame(animateDash);
}
// 开始动画
animateDash();
4. 阴影效果:增加层次感
4.1 基础阴影设置
// 设置阴影效果属性
// shadowOffsetX: 阴影水平偏移量(正值向右,负值向左)
ctx.shadowOffsetX = 10;
// shadowOffsetY: 阴影垂直偏移量(正值向下,负值向上)
ctx.shadowOffsetY = 10;
// shadowBlur: 阴影模糊半径(0表示无模糊,值越大越模糊)
ctx.shadowBlur = 5;
// shadowColor: 阴影颜色(支持所有CSS颜色格式)
ctx.shadowColor = "rgba(0, 0, 0, 0.5)"; // 半透明黑色
// 绘制带阴影的图形
ctx.fillStyle = "#3498db";
ctx.fillRect(100, 100, 200, 100);
4.2 复杂阴影效果
// 创建心形路径对象
// Path2D: 用于封装路径,可以重复使用
const heartPath = new Path2D();
// 绘制心形路径
// moveTo(x, y): 移动到指定坐标点
heartPath.moveTo(300, 200); // 移动到心形起始点
// bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y): 绘制贝塞尔三次曲线
// 第一条曲线:从(300,200)到(300,250)
// 第一个控制点:(350,150) - 控制曲线的开始方向
// 第二个控制点:(400,200) - 控制曲线的结束方向
heartPath.bezierCurveTo(350, 150, 400, 200, 300, 250);
// 第二条曲线:从(300,250)到(300,200)
// 第一个控制点:(200,200) - 控制曲线的开始方向
// 第二个控制点:(250,150) - 控制曲线的结束方向
heartPath.bezierCurveTo(200, 200, 250, 150, 300, 200);
// 应用阴影效果
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 5;
ctx.shadowBlur = 10;
ctx.shadowColor = "rgba(255, 100, 100, 0.8)"; // 半透明红色阴影
// 填充绘制心形路径(继承阴影设置)
ctx.fill(heartPath);
5. 图片绘制:多媒体支持
5.1 基础图片绘制
// 创建图片对象
const img = new Image();
img.src = "./imgs/image.png"; // 设置图片源路径
// 图片加载完成后的回调函数
// 注意:必须在图片加载完成后才能绘制
img.onload = function () {
console.log("图片加载成功,尺寸:", img.width, "x", img.height);
// 绘制图片的三种方式:
// 第一种:直接绘制(保持原尺寸)
// drawImage(image, x, y)
// 参数说明:
// image: 图片对象
// x, y: 图片在画布上的位置
ctx.drawImage(img, 0, 0);
// 第二种:缩放绘制(指定目标尺寸)
// drawImage(image, x, y, width, height)
// 参数说明:
// image: 图片对象
// x, y: 图片在画布上的位置
// width, height: 图片在画布上的尺寸
ctx.drawImage(img, 0, 0, 600, 400);
// 第三种:裁剪绘制(从源图片裁剪部分区域)
// drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
// 参数说明:
// image: 图片对象
// sx, sy: 源图片裁剪的起始位置
// sWidth, sHeight: 源图片裁剪的宽度和高度
// dx, dy: 目标画布的位置
// dWidth, dHeight: 目标画布的宽度和高度
ctx.drawImage(img, 100, 100, 200, 200, 0, 0, 300, 300);
};
// 图片加载失败的处理
img.onerror = function () {
console.log("图片加载失败,请检查路径是否正确");
// 绘制一个占位符
ctx.fillStyle = "#f0f0f0";
ctx.fillRect(0, 0, 600, 400);
ctx.fillStyle = "#666";
ctx.font = "20px Arial";
ctx.textAlign = "center";
ctx.fillText("图片加载失败", 300, 200);
};
5.2 图片处理技巧
// 图片滤镜效果
function applyFilter(imageData) {
// 获取图片数据
const data = imageData.data;
// 遍历每个像素
for (let i = 0; i < data.length; i += 4) {
// data[i]: 红色分量 (0-255)
// data[i+1]: 绿色分量 (0-255)
// data[i+2]: 蓝色分量 (0-255)
// data[i+3]: 透明度 (0-255)
// 计算灰度值
// 使用标准灰度转换公式:R*0.299 + G*0.587 + B*0.114
const gray = data[i] * 0.299 + data[i + 1] * 0.587 + data[i + 2] * 0.114;
// 应用灰度效果
data[i] = gray; // 红色分量设为灰度值
data[i + 1] = gray; // 绿色分量设为灰度值
data[i + 2] = gray; // 蓝色分量设为灰度值
// data[i + 3] 保持不变(透明度)
}
return imageData;
}
// 使用滤镜效果
function applyImageFilter() {
// 获取Canvas上的图片数据
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// 应用滤镜
const filteredData = applyFilter(imageData);
// 将处理后的数据绘制回Canvas
ctx.putImageData(filteredData, 0, 0);
}
6. 视频绘制:动态内容
6.1 视频播放器
// 获取视频元素
const video = document.querySelector("video");
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
// 渲染函数 - 将视频帧绘制到Canvas并叠加水印
function render() {
// 绘制视频帧到Canvas
// drawImage(video, x, y, width, height)
// 参数说明:
// video: 视频对象(可以是video元素或Image对象)
// x, y: 视频在画布上的位置
// width, height: 视频在画布上的尺寸
ctx.drawImage(video, 0, 0, 600, 400);
// 绘制水印图片
// drawImage(image, x, y, width, height)
// 参数说明:
// image: 图片对象
// x, y: 水印在画布上的位置(右下角)
// width, height: 水印在画布上的尺寸
ctx.drawImage(logo, 400, 350, 200, 50);
// 请求下一帧动画
// requestAnimationFrame(): 在浏览器下一次重绘之前调用指定函数
// 这样可以实现流畅的视频播放效果
requestAnimationFrame(render);
}
// 播放控制
document.getElementById("playBtn").onclick = function () {
if (video.paused) {
// 如果视频暂停,则播放视频并开始渲染
video.play();
render(); // 开始渲染循环
} else {
// 如果视频播放,则暂停视频
video.pause();
}
};
6.2 视频特效
// 视频滤镜效果
function applyVideoFilter() {
// 获取Canvas上的图片数据
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
// 遍历每个像素
for (let i = 0; i < data.length; i += 4) {
// 增加对比度
// 将RGB值乘以1.2,但不超过255
data[i] = Math.min(255, data[i] * 1.2); // 红色分量
data[i + 1] = Math.min(255, data[i + 1] * 1.2); // 绿色分量
data[i + 2] = Math.min(255, data[i + 2] * 1.2); // 蓝色分量
// data[i + 3] 保持不变(透明度)
}
// 将处理后的数据绘制回Canvas
ctx.putImageData(imageData, 0, 0);
}
7. 性能优化技巧
7.1 离屏 Canvas
// 创建离屏Canvas
// 离屏Canvas可以提高性能,避免在主Canvas上进行复杂计算
const offscreenCanvas = document.createElement("canvas");
const offscreenCtx = offscreenCanvas.getContext("2d");
// 设置离屏Canvas尺寸
offscreenCanvas.width = 600;
offscreenCanvas.height = 400;
// 在离屏Canvas上绘制复杂图形
function drawComplexShape() {
// 创建渐变
const gradient = offscreenCtx.createLinearGradient(0, 0, 600, 400);
gradient.addColorStop(0, "#3498db");
gradient.addColorStop(1, "#e74c3c");
// 在离屏Canvas上进行复杂的绘制操作
offscreenCtx.fillStyle = gradient;
offscreenCtx.fillRect(0, 0, 600, 400);
// 将结果复制到主Canvas
// 这样可以避免在主Canvas上重复进行复杂计算
ctx.drawImage(offscreenCanvas, 0, 0);
}
7.2 图层管理
// 分层绘制
// 将背景和前景分离,提高渲染效率
const backgroundLayer = document.createElement("canvas");
const foregroundLayer = document.createElement("canvas");
// 设置图层尺寸
backgroundLayer.width = 600;
backgroundLayer.height = 400;
foregroundLayer.width = 600;
foregroundLayer.height = 400;
function renderLayers() {
// 背景层 - 绘制视频
const bgCtx = backgroundLayer.getContext("2d");
bgCtx.drawImage(video, 0, 0, 600, 400);
// 前景层 - 绘制水印
const fgCtx = foregroundLayer.getContext("2d");
fgCtx.drawImage(logo, 400, 350, 200, 50);
// 合并图层
// 先绘制背景层
ctx.drawImage(backgroundLayer, 0, 0);
// 再绘制前景层
ctx.drawImage(foregroundLayer, 0, 0);
}
8. 实际应用场景
8.1 数据可视化
// 渐变图表
function drawGradientChart(data) {
// 创建垂直渐变
const gradient = ctx.createLinearGradient(0, 0, 0, 400);
gradient.addColorStop(0, "#3498db"); // 顶部为蓝色
gradient.addColorStop(1, "#e74c3c"); // 底部为红色
// 绘制数据柱状图
data.forEach((value, index) => {
// 计算柱状图高度(相对于最大值)
const height = (value / Math.max(...data)) * 300;
// 设置填充样式为渐变
ctx.fillStyle = gradient;
// 绘制柱状图
// x位置:index * 60(每个柱子间隔60像素)
// y位置:400 - height(从底部向上绘制)
// 宽度:50像素
// 高度:计算出的height
ctx.fillRect(index * 60, 400 - height, 50, height);
});
}
// 使用示例
const chartData = [10, 25, 15, 30, 20];
drawGradientChart(chartData);
8.2 游戏开发
// 游戏背景
function drawGameBackground() {
// 创建天空渐变
// 从上到下的渐变效果
const skyGradient = ctx.createLinearGradient(0, 0, 0, 200);
skyGradient.addColorStop(0, "#87CEEB"); // 顶部为天蓝色
skyGradient.addColorStop(1, "#E0F6FF"); // 底部为浅蓝色
// 绘制天空
ctx.fillStyle = skyGradient;
ctx.fillRect(0, 0, 800, 200);
// 创建地面渐变
// 从上到下的渐变效果
const groundGradient = ctx.createLinearGradient(0, 200, 0, 400);
groundGradient.addColorStop(0, "#90EE90"); // 顶部为浅绿色
groundGradient.addColorStop(1, "#228B22"); // 底部为深绿色
// 绘制地面
ctx.fillStyle = groundGradient;
ctx.fillRect(0, 200, 800, 200);
}
总结
Canvas 的进阶特性为 Web 开发提供了强大的图形处理能力。从基础的渐变效果到复杂的视频处理,Canvas 能够满足各种视觉需求。掌握这些技术,你将能够创建出更加丰富和生动的 Web 应用。
关键技术点:
- 渐变效果:线性、径向、圆锥渐变的灵活运用
- 图案填充:重复模式的巧妙设计
- 线条样式:细节处理的品质保证
- 阴影效果:层次感的视觉增强
- 多媒体支持:图片和视频的完整处理
- 性能优化:离屏 Canvas 和图层管理