pin_drop当前位置:知识文库 ❯ 图文

多媒体:Canvas合成 - 完整教程与代码示例

一、教程简介

Canvas 合成模式(Compositing)决定了新绘制的图形如何与已有内容进行混合。通过 globalCompositeOperation 属性,可以实现各种视觉效果,如透明叠加、遮罩、裁切、混合模式等。本教程将详细介绍所有合成模式的原理、效果以及实际应用场景。


二、核心概念

源与目标

在合成操作中,有两个关键概念:

  • 源(Source):即将绘制的新图形

  • 目标(Destination):画布上已有的内容

代码示例

┌─────────────┐
│  Destination │  ← 已有内容(目标)
│    ┌─────┐  │
│    │Source│  │  ← 新绘制内容(源)
│    └─────┘  │
└─────────────┘

合成模式的分类

类别 模式 说明
基本模式 source-over 等 控制源和目标的显示优先级
混合模式 multiply 等 类似 Photoshop 的图层混合
复合模式 xor 等 基于逻辑运算的合成

三、语法与用法

属性

语法 说明
ctx.globalCompositeOperation = mode 设置合成模式

所有合成模式

基本合成模式

模式 说明
source-over 默认值,源覆盖在目标之上
source-in 只显示源与目标重叠的部分(源颜色)
source-out 只显示源不与目标重叠的部分
source-atop 源绘制在目标之上,但只在目标区域内可见
destination-over 目标覆盖在源之上
destination-in 只保留目标与源重叠的部分(目标颜色)
destination-out 只保留目标不与源重叠的部分
destination-atop 目标绘制在源之上,但只在源区域内可见
lighter 源和目标颜色值相加
copy 只显示源,忽略目标
xor 源和目标重叠部分变透明

混合模式

模式 说明
multiply 正片叠底,颜色相乘,结果更暗
screen 滤色,颜色反相相乘再反相,结果更亮
overlay 叠加,暗色用 multiply,亮色用 screen
darken 取两者中较暗的颜色
lighten 取两者中较亮的颜色
color-dodge 颜色减淡
color-burn 颜色加深
hard-light 强光,类似 overlay 但以源为基准
soft-light 柔光,柔和版本的 hard-light
difference 差值,取两者差的绝对值
exclusion 排除,类似 difference 但对比度更低
hue 使用源的色相,目标的饱和度和亮度
saturation 使用源的饱和度,目标的色相和亮度
color 使用源的色相和饱和度,目标的亮度
luminosity 使用源的亮度,目标的色相和饱和度

四、代码示例

基本合成模式对比

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Canvas 合成模式</title>
    <style>
        body {
            margin: 0;
            padding: 20px;
            background: #0d1117;
            font-family: "Microsoft YaHei", sans-serif;
            color: #c9d1d9;
        }
        h1 { text-align: center; color: #f0883e; }
        canvas {
            display: block;
            margin: 20px auto;
            border: 1px solid #30363d;
            border-radius: 8px;
        }
    </style>
</head>
<body>
    <h1>Canvas 合成模式对比</h1>
    <canvas id="compositeCanvas" width="750" height="700"></canvas>

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

        ctx.fillStyle = '#161b22';
        ctx.fillRect(0, 0, canvas.width, canvas.height);

        const modes = [
            'source-over', 'source-in', 'source-out', 'source-atop',
            'destination-over', 'destination-in', 'destination-out', 'destination-atop',
            'lighter', 'copy', 'xor', 'multiply',
            'screen', 'overlay', 'darken', 'lighten',
            'color-dodge', 'color-burn', 'hard-light', 'soft-light',
            'difference', 'exclusion', 'hue', 'color'
        ];

        const cols = 4;
        const cellW = 170;
        const cellH = 130;
        const gap = 10;

        modes.forEach((mode, i) => {
            const col = i % cols;
            const row = Math.floor(i / cols);
            const x = 15 + col * (cellW + gap);
            const y = 15 + row * (cellH + gap);

            // 背景
            ctx.fillStyle = '#0d1117';
            ctx.fillRect(x, y, cellW, cellH - 20);

            // 目标(蓝色矩形)
            ctx.globalCompositeOperation = 'source-over';
            ctx.fillStyle = '#539bf5';
            ctx.beginPath();
            ctx.arc(x + cellW * 0.38, y + (cellH - 20) * 0.45, 35, 0, Math.PI * 2);
            ctx.fill();

            // 源(红色矩形)- 使用指定合成模式
            ctx.globalCompositeOperation = mode;
            ctx.fillStyle = '#f47067';
            ctx.beginPath();
            ctx.arc(x + cellW * 0.62, y + (cellH - 20) * 0.55, 35, 0, Math.PI * 2);
            ctx.fill();

            // 重置合成模式
            ctx.globalCompositeOperation = 'source-over';

            // 标签
            ctx.fillStyle = '#8b949e';
            ctx.font = '11px "Microsoft YaHei"';
            ctx.textAlign = 'center';
            ctx.fillText(mode, x + cellW / 2, y + cellH - 5);
        });
    </script>
</body>
</html>

合成模式实战 - 遮罩与擦除效果

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>合成模式实战</title>
    <style>
        body {
            margin: 0;
            padding: 20px;
            background: #1a1a2e;
            font-family: "Microsoft YaHei", sans-serif;
            color: #e0e0e0;
        }
        h1 { text-align: center; color: #e94560; }
        .demo-row {
            display: flex;
            justify-content: center;
            gap: 30px;
            flex-wrap: wrap;
            margin-top: 20px;
        }
        canvas {
            border: 1px solid #333;
            border-radius: 8px;
            cursor: crosshair;
        }
        .label { text-align: center; color: #888; font-size: 14px; margin-top: 8px; }
    </style>
</head>
<body>
    <h1>合成模式实战 - 遮罩与擦除</h1>
    <div class="demo-row">
        <div>
            <canvas id="maskCanvas" width="300" height="300"></canvas>
            <p class="label">destination-in 遮罩效果</p>
        </div>
        <div>
            <canvas id="eraseCanvas" width="300" height="300"></canvas>
            <p class="label">destination-out 擦除效果(点击拖拽)</p>
        </div>
    </div>

    <script>
        // === 遮罩效果 ===
        const maskCanvas = document.getElementById('maskCanvas');
        const maskCtx = maskCanvas.getContext('2d');

        // 绘制彩色背景
        const grad = maskCtx.createLinearGradient(0, 0, 300, 300);
        grad.addColorStop(0, '#e94560');
        grad.addColorStop(0.5, '#0f3460');
        grad.addColorStop(1, '#533483');
        maskCtx.fillStyle = grad;
        maskCtx.fillRect(0, 0, 300, 300);

        // 添加一些装饰
        for (let i = 0; i < 20; i++) {
            maskCtx.beginPath();
            maskCtx.arc(Math.random() * 300, Math.random() * 300, Math.random() * 20 + 5, 0, Math.PI * 2);
            maskCtx.fillStyle = `rgba(255,255,255,${Math.random() * 0.3})`;
            maskCtx.fill();
        }

        // 使用 destination-in 创建遮罩
        maskCtx.globalCompositeOperation = 'destination-in';
        maskCtx.beginPath();
        maskCtx.arc(150, 150, 120, 0, Math.PI * 2);
        maskCtx.fill();
        maskCtx.globalCompositeOperation = 'source-over';

        // 添加边框
        maskCtx.strokeStyle = '#e94560';
        maskCtx.lineWidth = 3;
        maskCtx.beginPath();
        maskCtx.arc(150, 150, 120, 0, Math.PI * 2);
        maskCtx.stroke();

        // === 擦除效果 ===
        const eraseCanvas = document.getElementById('eraseCanvas');
        const eraseCtx = eraseCanvas.getContext('2d');

        // 绘制初始内容
        const eGrad = eraseCtx.createRadialGradient(150, 150, 0, 150, 150, 200);
        eGrad.addColorStop(0, '#ffd93d');
        eGrad.addColorStop(0.5, '#4CAF50');
        eGrad.addColorStop(1, '#2196F3');
        eraseCtx.fillStyle = eGrad;
        eraseCtx.fillRect(0, 0, 300, 300);

        eraseCtx.fillStyle = '#fff';
        eraseCtx.font = 'bold 20px "Microsoft YaHei"';
        eraseCtx.textAlign = 'center';
        eraseCtx.textBaseline = 'middle';
        eraseCtx.fillText('拖拽擦除', 150, 150);

        let isErasing = false;

        eraseCanvas.addEventListener('mousedown', (e) => {
            isErasing = true;
            erase(e);
        });

        eraseCanvas.addEventListener('mousemove', (e) => {
            if (isErasing) erase(e);
        });

        eraseCanvas.addEventListener('mouseup', () => { isErasing = false; });
        eraseCanvas.addEventListener('mouseleave', () => { isErasing = false; });

        function erase(e) {
            const rect = eraseCanvas.getBoundingClientRect();
            const x = e.clientX - rect.left;
            const y = e.clientY - rect.top;

            eraseCtx.globalCompositeOperation = 'destination-out';
            eraseCtx.beginPath();
            eraseCtx.arc(x, y, 20, 0, Math.PI * 2);
            eraseCtx.fill();
            eraseCtx.globalCompositeOperation = 'source-over';
        }
    </script>
</body>
</html>

五、浏览器兼容性

浏览器 基本模式 混合模式
Chrome 1+ 35+
Firefox 1.5+ 20+
Safari 2+ 8+
Edge 12+ 79+
IE 9+ 不支持混合模式

六、注意事项与最佳实践

1. 合成模式影响所有后续绘制

设置 globalCompositeOperation 后会影响所有后续绘制操作,务必在使用后重置:

代码示例

ctx.globalCompositeOperation = 'multiply';
// ... 绘制
ctx.globalCompositeOperation = 'source-over'; // 重置

2. source-in 和 destination-in 的透明背景

source-indestination-in 会将非重叠区域变为透明,如果 Canvas 背景是绘制上去的,也会被影响:

代码示例

// 先绘制背景(作为目标的一部分)
ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, canvas.width, canvas.height);

// 绘制目标图形
ctx.fillStyle = 'blue';
ctx.fillRect(50, 50, 100, 100);

// source-in:只保留重叠部分
ctx.globalCompositeOperation = 'source-in';
ctx.fillStyle = 'red';
ctx.fillRect(80, 80, 100, 100);
// 结果:白色背景也会被裁切!

3. 混合模式与透明度

混合模式在半透明颜色下效果更明显:

代码示例

ctx.globalAlpha = 0.5;
ctx.globalCompositeOperation = 'multiply';
// 效果更柔和

4. 性能考虑

某些合成模式(如混合模式)比 source-over 更耗性能,在大量绘制时需注意。

5. 擦除效果的最佳实践

使用 destination-out 实现擦除时,确保擦除的"画笔"颜色是不透明的:

代码示例

ctx.globalCompositeOperation = 'destination-out';
ctx.fillStyle = 'rgba(0,0,0,1)'; // 必须完全不透明
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fill();

七、代码规范示例

代码示例

/**
 * 合成模式工具类
 */
class CompositeHelper {
    constructor(ctx) {
        this.ctx = ctx;
    }

    /**
     * 使用指定合成模式绘制
     * @param {string} mode - 合成模式
     * @param {Function} drawFn - 绘制函数
     */
    withMode(mode, drawFn) {
        const ctx = this.ctx;
        ctx.save();
        ctx.globalCompositeOperation = mode;
        drawFn(ctx);
        ctx.restore();
    }

    /**
     * 创建遮罩效果
     * @param {Function} contentFn - 绘制内容
     * @param {Function} maskFn - 绘制遮罩形状
     */
    mask(contentFn, maskFn) {
        const ctx = this.ctx;
        ctx.save();
        contentFn(ctx);
        ctx.globalCompositeOperation = 'destination-in';
        maskFn(ctx);
        ctx.restore();
        ctx.globalCompositeOperation = 'source-over';
    }

    /**
     * 擦除区域
     * @param {number} x - X 坐标
     * @param {number} y - Y 坐标
     * @param {number} radius - 擦除半径
     */
    erase(x, y, radius) {
        const ctx = this.ctx;
        ctx.save();
        ctx.globalCompositeOperation = 'destination-out';
        ctx.beginPath();
        ctx.arc(x, y, radius, 0, Math.PI * 2);
        ctx.fill();
        ctx.restore();
    }
}

八、常见问题与解决方案

常见问题

source-in 后图形消失怎么办?

原因是源和目标没有重叠区域。解决方案是确保源和目标有重叠部分,调整绘制位置使两者产生交集。

混合模式效果不明显怎么解决?

原因是颜色太暗或太亮,或透明度设置不当。解决方案是使用更饱和的颜色和适当的透明度,半透明颜色下混合模式效果更明显。

擦除后无法恢复怎么办?

原因是 destination-out 是破坏性操作。解决方案是使用离屏 Canvas 保存原始内容,或在使用擦除前通过 save/restore 管理状态。

合成模式在 IE 中不工作如何处理?

IE 不支持混合模式(multiply、screen 等),需要检测浏览器支持情况并提供降级方案,可以使用基本的 source-over 等模式作为替代。


九、总结

Canvas 合成模式是创建高级视觉效果的重要工具:

  • globalCompositeOperation:控制新绘制内容与已有内容的混合方式

  • 基本模式:source-over(默认)、source-in、destination-out 等

  • 混合模式:multiply、screen、overlay 等,类似 Photoshop 图层混合

  • 遮罩效果:使用 destination-in 限制内容显示区域

  • 擦除效果:使用 destination-out 擦除已有内容

  • lighter:颜色值相加,适合发光效果

  • xor:重叠区域变透明,适合创建镂空效果

  • 务必重置:使用后重置为 source-over

小贴士

Canvas 合成模式与 Photoshop 的图层混合模式原理相同。如果你在 Photoshop 中熟悉正片叠底(multiply)、滤色(screen)等混合模式,那么在 Canvas 中使用它们会非常直观。建议在开发工具中实际测试每种模式的效果,以便在项目中选择最合适的合成方式。

标签: Canvas合成 globalCompositeOperation 混合模式 遮罩效果 擦除效果 Canvas教程

本文涉及AI创作

内容由AI创作,请仔细甄别

list快速访问

上一篇: 多媒体:Canvas变换 - 完整教程与代码示例 下一篇: 多媒体:Canvas裁剪 - 完整教程与代码示例

poll相关推荐