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

多媒体:Canvas阴影 - 完整教程与代码示例

一、教程简介

Canvas 阴影效果可以为图形添加深度感和立体感,是提升视觉品质的重要手段。通过 shadowColorshadowBlurshadowOffsetXshadowOffsetY 四个属性,可以为任何绘制的图形(包括矩形、路径、文本和图像)添加阴影。本教程将详细介绍阴影属性的用法、效果控制以及实际应用技巧。


二、核心概念

阴影的四个属性

Canvas 阴影由四个属性共同控制:

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

阴影的工作原理

阴影是基于图形的轮廓生成的:

  1. 取图形的轮廓形状
  2. 根据 shadowOffsetXshadowOffsetY 偏移轮廓
  3. 使用 shadowColor 作为阴影颜色
  4. 根据 shadowBlur 对阴影边缘进行高斯模糊

阴影的应用范围

阴影会应用于所有后续的绘制操作,包括:

  • fillRect() / strokeRect() - 矩形

  • fill() / stroke() - 路径

  • fillText() / strokeText() - 文本

  • drawImage() - 图像


三、语法与用法

属性列表

属性 类型 说明
shadowColor string CSS 颜色值,支持透明度
shadowBlur number 模糊半径(非负数),值越大越模糊
shadowOffsetX number 水平偏移(正数向右,负数向左)
shadowOffsetY number 垂直偏移(正数向下,负数向上)

阴影效果对照

效果 属性设置
无阴影 shadowColor = 'rgba(0,0,0,0)'shadowBlur = 0
右下阴影 shadowOffsetX = 5, shadowOffsetY = 5
左上阴影 shadowOffsetX = -5, shadowOffsetY = -5
发光效果 shadowOffsetX = 0, shadowOffsetY = 0, shadowBlur = 20
内阴影 Canvas 不直接支持,需用其他技巧模拟

四、代码示例

阴影属性演示

代码示例

<!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="shadowCanvas" width="750" height="550"></canvas>

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

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

        // === shadowBlur 对比 ===
        ctx.fillStyle = '#8b949e';
        ctx.font = 'bold 16px "Microsoft YaHei"';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'top';
        ctx.shadowColor = 'transparent';
        ctx.fillText('shadowBlur 模糊程度对比', 375, 15);

        const blurValues = [0, 5, 10, 20, 30, 50];
        const boxW = 90;
        const boxH = 70;
        const startX = 30;
        const gap = 15;

        blurValues.forEach((blur, i) => {
            const x = startX + i * (boxW + gap);
            const y = 50;

            ctx.save();
            ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';
            ctx.shadowBlur = blur;
            ctx.shadowOffsetX = 5;
            ctx.shadowOffsetY = 5;

            ctx.fillStyle = '#f0883e';
            ctx.beginPath();
            ctx.roundRect(x, y, boxW, boxH, 8);
            ctx.fill();
            ctx.restore();

            ctx.fillStyle = '#8b949e';
            ctx.font = '12px Arial';
            ctx.textAlign = 'center';
            ctx.fillText(`blur: ${blur}`, x + boxW / 2, y + boxH + 10);
        });

        // === shadowOffset 对比 ===
        ctx.fillStyle = '#8b949e';
        ctx.font = 'bold 16px "Microsoft YaHei"';
        ctx.fillText('shadowOffset 偏移方向对比', 375, 175);

        const offsets = [
            { x: 0, y: 0, label: '0, 0' },
            { x: 10, y: 0, label: '10, 0' },
            { x: 0, y: 10, label: '0, 10' },
            { x: 10, y: 10, label: '10, 10' },
            { x: -10, y: -10, label: '-10, -10' },
            { x: 10, y: -10, label: '10, -10' }
        ];

        offsets.forEach((offset, i) => {
            const x = startX + i * (boxW + gap);
            const y = 210;

            ctx.save();
            ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';
            ctx.shadowBlur = 10;
            ctx.shadowOffsetX = offset.x;
            ctx.shadowOffsetY = offset.y;

            ctx.fillStyle = '#539bf5';
            ctx.beginPath();
            ctx.roundRect(x, y, boxW, boxH, 8);
            ctx.fill();
            ctx.restore();

            ctx.fillStyle = '#8b949e';
            ctx.font = '11px Arial';
            ctx.textAlign = 'center';
            ctx.fillText(`offset: ${offset.label}`, x + boxW / 2, y + boxH + 10);
        });

        // === shadowColor 对比 ===
        ctx.fillStyle = '#8b949e';
        ctx.font = 'bold 16px "Microsoft YaHei"';
        ctx.fillText('shadowColor 阴影颜色对比', 375, 335);

        const colors = [
            { color: 'rgba(0, 0, 0, 0.5)', label: '黑色半透明' },
            { color: 'rgba(255, 0, 0, 0.5)', label: '红色半透明' },
            { color: 'rgba(0, 255, 0, 0.5)', label: '绿色半透明' },
            { color: 'rgba(0, 100, 255, 0.5)', label: '蓝色半透明' },
            { color: 'rgba(255, 200, 0, 0.5)', label: '黄色半透明' },
            { color: 'rgba(200, 0, 255, 0.5)', label: '紫色半透明' }
        ];

        colors.forEach((item, i) => {
            const x = startX + i * (boxW + gap);
            const y = 370;

            ctx.save();
            ctx.shadowColor = item.color;
            ctx.shadowBlur = 15;
            ctx.shadowOffsetX = 5;
            ctx.shadowOffsetY = 5;

            ctx.fillStyle = '#c9d1d9';
            ctx.beginPath();
            ctx.roundRect(x, y, boxW, boxH, 8);
            ctx.fill();
            ctx.restore();

            ctx.fillStyle = '#8b949e';
            ctx.font = '11px "Microsoft YaHei"';
            ctx.textAlign = 'center';
            ctx.fillText(item.label, x + boxW / 2, y + boxH + 10);
        });

        // === 发光效果 ===
        ctx.fillStyle = '#8b949e';
        ctx.font = 'bold 16px "Microsoft YaHei"';
        ctx.textAlign = 'center';
        ctx.fillText('发光效果 (offset=0, blur>0)', 375, 490);

        const glowColors = ['#f47067', '#57ab5a', '#539bf5', '#c69026', '#b083f0'];
        const glowLabels = ['红色发光', '绿色发光', '蓝色发光', '黄色发光', '紫色发光'];

        glowColors.forEach((color, i) => {
            const x = 75 + i * 140;
            const y = 530;

            ctx.save();
            ctx.shadowColor = color;
            ctx.shadowBlur = 20;
            ctx.shadowOffsetX = 0;
            ctx.shadowOffsetY = 0;

            ctx.fillStyle = color;
            ctx.beginPath();
            ctx.arc(x, y, 15, 0, Math.PI * 2);
            ctx.fill();
            ctx.restore();

            ctx.fillStyle = '#8b949e';
            ctx.font = '11px "Microsoft YaHei"';
            ctx.textAlign = 'center';
            ctx.fillText(glowLabels[i], x, y + 30);
        });
    </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: #e8eaf6;
            font-family: "Microsoft YaHei", sans-serif;
        }
        h1 { text-align: center; color: #333; }
        canvas {
            display: block;
            margin: 20px auto;
            border-radius: 8px;
        }
    </style>
</head>
<body>
    <h1>阴影实战 - UI 组件效果</h1>
    <canvas id="uiCanvas" width="700" height="400"></canvas>

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

        // 背景
        ctx.fillStyle = '#e8eaf6';
        ctx.fillRect(0, 0, canvas.width, canvas.height);

        // === 卡片组件 ===
        function drawCard(x, y, w, h, elevation) {
            ctx.save();

            // 多层阴影模拟不同高度
            const shadowAlpha = 0.1 + elevation * 0.03;
            const shadowBlur = 5 + elevation * 5;
            const shadowOffsetY = 2 + elevation * 2;

            ctx.shadowColor = `rgba(0, 0, 0, ${shadowAlpha})`;
            ctx.shadowBlur = shadowBlur;
            ctx.shadowOffsetX = 0;
            ctx.shadowOffsetY = shadowOffsetY;

            ctx.fillStyle = '#fff';
            ctx.beginPath();
            ctx.roundRect(x, y, w, h, 12);
            ctx.fill();

            ctx.restore();

            // 卡片内容
            ctx.fillStyle = '#333';
            ctx.font = 'bold 16px "Microsoft YaHei"';
            ctx.textAlign = 'left';
            ctx.textBaseline = 'top';
            ctx.fillText(`Elevation ${elevation}`, x + 20, y + 20);

            ctx.fillStyle = '#888';
            ctx.font = '13px "Microsoft YaHei"';
            ctx.fillText(`shadowBlur: ${shadowBlur}`, x + 20, y + 48);
            ctx.fillText(`shadowOffsetY: ${shadowOffsetY}`, x + 20, y + 68);
            ctx.fillText(`shadowAlpha: ${shadowAlpha.toFixed(2)}`, x + 20, y + 88);
        }

        drawCard(30, 30, 200, 120, 1);
        drawCard(260, 30, 200, 120, 3);
        drawCard(490, 30, 200, 120, 5);

        // === 按钮组件 ===
        function drawButton(x, y, w, h, options = {}) {
            const {
                bgColor = '#6200ee',
                textColor = '#fff',
                text = '按钮',
                pressed = false,
                radius = h / 2
            } = options;

            ctx.save();

            if (pressed) {
                // 按下状态:阴影缩小
                ctx.shadowColor = 'rgba(98, 0, 238, 0.2)';
                ctx.shadowBlur = 4;
                ctx.shadowOffsetX = 0;
                ctx.shadowOffsetY = 1;
            } else {
                // 正常状态:较大阴影
                ctx.shadowColor = 'rgba(98, 0, 238, 0.3)';
                ctx.shadowBlur = 8;
                ctx.shadowOffsetX = 0;
                ctx.shadowOffsetY = 4;
            }

            ctx.fillStyle = bgColor;
            ctx.beginPath();
            ctx.roundRect(x, y, w, h, radius);
            ctx.fill();

            ctx.restore();

            // 文字
            ctx.fillStyle = textColor;
            ctx.font = 'bold 14px "Microsoft YaHei"';
            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';
            ctx.fillText(text, x + w / 2, y + h / 2);
        }

        drawButton(30, 200, 120, 40, { text: '正常状态' });
        drawButton(170, 200, 120, 40, { text: '按下状态', pressed: true });
        drawButton(310, 200, 120, 40, { bgColor: '#03dac6', text: '次要按钮' });
        drawButton(450, 200, 120, 40, { bgColor: '#fff', textColor: '#6200ee', text: '轮廓按钮' });

        // === 文本阴影 ===
        ctx.fillStyle = '#333';
        ctx.font = 'bold 16px "Microsoft YaHei"';
        ctx.textAlign = 'left';
        ctx.textBaseline = 'top';
        ctx.fillText('文本阴影效果', 30, 280);

        // 普通文本阴影
        ctx.save();
        ctx.shadowColor = 'rgba(0, 0, 0, 0.3)';
        ctx.shadowBlur = 4;
        ctx.shadowOffsetX = 2;
        ctx.shadowOffsetY = 2;
        ctx.font = 'bold 28px "Microsoft YaHei"';
        ctx.fillStyle = '#333';
        ctx.fillText('普通文本阴影', 30, 310);
        ctx.restore();

        // 发光文本
        ctx.save();
        ctx.shadowColor = '#6200ee';
        ctx.shadowBlur = 15;
        ctx.shadowOffsetX = 0;
        ctx.shadowOffsetY = 0;
        ctx.font = 'bold 28px "Microsoft YaHei"';
        ctx.fillStyle = '#6200ee';
        ctx.fillText('发光文本效果', 280, 310);
        ctx.restore();

        // 多层发光
        ctx.save();
        ctx.shadowColor = '#ff6f00';
        ctx.shadowBlur = 20;
        ctx.shadowOffsetX = 0;
        ctx.shadowOffsetY = 0;
        ctx.font = 'bold 28px "Microsoft YaHei"';
        ctx.fillStyle = '#fff';
        ctx.fillText('多层发光', 530, 310);
        // 再画一层增强发光
        ctx.fillText('多层发光', 530, 310);
        ctx.restore();

        // === 内阴影模拟 ===
        ctx.fillStyle = '#333';
        ctx.font = 'bold 16px "Microsoft YaHei"';
        ctx.fillText('内阴影模拟(使用渐变)', 30, 370);

        // 使用渐变模拟内阴影
        const innerX = 250, innerY = 360, innerW = 200, innerH = 30;
        ctx.save();
        ctx.beginPath();
        ctx.roundRect(innerX, innerY, innerW, innerH, 6);
        ctx.clip();

        // 先填充底色
        ctx.fillStyle = '#fff';
        ctx.fillRect(innerX, innerY, innerW, innerH);

        // 顶部内阴影
        const topShadow = ctx.createLinearGradient(innerX, innerY, innerX, innerY + 15);
        topShadow.addColorStop(0, 'rgba(0, 0, 0, 0.15)');
        topShadow.addColorStop(1, 'rgba(0, 0, 0, 0)');
        ctx.fillStyle = topShadow;
        ctx.fillRect(innerX, innerY, innerW, 15);

        // 左侧内阴影
        const leftShadow = ctx.createLinearGradient(innerX, innerY, innerX + 10, innerY);
        leftShadow.addColorStop(0, 'rgba(0, 0, 0, 0.1)');
        leftShadow.addColorStop(1, 'rgba(0, 0, 0, 0)');
        ctx.fillStyle = leftShadow;
        ctx.fillRect(innerX, innerY, 10, innerH);

        ctx.restore();

        // 边框
        ctx.strokeStyle = '#ccc';
        ctx.lineWidth = 1;
        ctx.beginPath();
        ctx.roundRect(innerX, innerY, innerW, innerH, 6);
        ctx.stroke();
    </script>
</body>
</html>

五、浏览器兼容性

浏览器 支持版本 备注
Chrome 1+ 完全支持
Firefox 1.5+ 完全支持
Safari 2+ 完全支持
Edge 12+ 完全支持
IE 9+ 完全支持

注意

不同浏览器的阴影渲染可能略有差异,特别是 shadowBlur 的模糊算法可能不同。


六、注意事项与最佳实践

1. 阴影会影响所有后续绘制

设置阴影属性后,所有后续的绘制操作都会带有阴影,直到重置阴影:

代码示例

// 重置阴影
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;

// 或者使用 save/restore
ctx.save();
ctx.shadowColor = 'rgba(0,0,0,0.5)';
ctx.shadowBlur = 10;
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 5;
// ... 绘制带阴影的图形
ctx.restore(); // 阴影设置被恢复

2. 阴影性能问题

阴影(特别是大 shadowBlur 值)会显著影响绘制性能:

代码示例

// 性能差:大模糊值
ctx.shadowBlur = 50; // 模糊计算量大

// 性能好:小模糊值
ctx.shadowBlur = 10;

// 优化:对静态内容使用离屏 Canvas 缓存
const offscreen = document.createElement('canvas');
// ... 在离屏 Canvas 上绘制带阴影的内容
// 然后用 drawImage 绘制到主 Canvas

3. shadowBlur 值的理解

shadowBlur 的值并不直接等于模糊的像素范围,它是一个高斯模糊的参数。实际模糊范围大约是 shadowBlur 值的 2-3 倍。

4. Canvas 不支持内阴影

Canvas 原生不支持内阴影效果,但可以通过以下方式模拟:

代码示例

// 方法一:使用渐变模拟
function drawInnerShadow(ctx, x, y, w, h, r) {
    ctx.save();
    ctx.beginPath();
    ctx.roundRect(x, y, w, h, r);
    ctx.clip();

    // 顶部阴影
    const topGrad = ctx.createLinearGradient(x, y, x, y + 10);
    topGrad.addColorStop(0, 'rgba(0,0,0,0.2)');
    topGrad.addColorStop(1, 'rgba(0,0,0,0)');
    ctx.fillStyle = topGrad;
    ctx.fillRect(x, y, w, 10);

    ctx.restore();
}

// 方法二:绘制一个稍大的阴影图形,然后用裁剪限制范围

5. 多层阴影效果

通过多次绘制同一图形,可以创建多层阴影效果:

代码示例

// 外层柔和阴影
ctx.save();
ctx.shadowColor = 'rgba(0, 0, 0, 0.1)';
ctx.shadowBlur = 30;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 10;
ctx.fillStyle = '#fff';
ctx.fillRect(50, 50, 200, 100);
ctx.restore();

// 内层锐利阴影
ctx.save();
ctx.shadowColor = 'rgba(0, 0, 0, 0.2)';
ctx.shadowBlur = 10;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 3;
ctx.fillStyle = '#fff';
ctx.fillRect(50, 50, 200, 100);
ctx.restore();

6. 阴影颜色使用半透明

阴影颜色通常使用半透明黑色,这样更自然:

代码示例

// 自然:半透明黑色
ctx.shadowColor = 'rgba(0, 0, 0, 0.3)';

// 不自然:不透明黑色
ctx.shadowColor = '#000000';

七、代码规范示例

代码示例

/**
 * 阴影工具类
 * 封装常用阴影效果的创建方法
 */
class ShadowHelper {
    constructor(ctx) {
        this.ctx = ctx;
    }

    /**
     * 设置阴影
     * @param {Object} options - 阴影选项
     */
    set(options = {}) {
        const {
            color = 'rgba(0, 0, 0, 0.3)',
            blur = 10,
            offsetX = 0,
            offsetY = 4
        } = options;

        const ctx = this.ctx;
        ctx.shadowColor = color;
        ctx.shadowBlur = blur;
        ctx.shadowOffsetX = offsetX;
        ctx.shadowOffsetY = offsetY;
    }

    /**
     * 清除阴影
     */
    clear() {
        const ctx = this.ctx;
        ctx.shadowColor = 'transparent';
        ctx.shadowBlur = 0;
        ctx.shadowOffsetX = 0;
        ctx.shadowOffsetY = 0;
    }

    /**
     * 绘制带阴影的图形
     * @param {Function} drawFn - 绘制函数
     * @param {Object} shadowOptions - 阴影选项
     */
    drawWithShadow(drawFn, shadowOptions = {}) {
        const ctx = this.ctx;
        ctx.save();
        this.set(shadowOptions);
        drawFn(ctx);
        ctx.restore();
    }

    /**
     * Material Design 风格阴影
     * @param {number} elevation - 高度等级 (1-24)
     * @returns {Object} 阴影选项
     */
    static materialElevation(elevation) {
        const alpha = Math.min(0.3, 0.05 + elevation * 0.02);
        const blur = 5 + elevation * 5;
        const offsetY = 2 + elevation * 2;
        return {
            color: `rgba(0, 0, 0, ${alpha})`,
            blur: blur,
            offsetX: 0,
            offsetY: offsetY
        };
    }

    /**
     * 发光效果
     * @param {string} color - 发光颜色
     * @param {number} intensity - 发光强度
     * @returns {Object} 阴影选项
     */
    static glow(color, intensity = 15) {
        return {
            color: color,
            blur: intensity,
            offsetX: 0,
            offsetY: 0
        };
    }

    /**
     * 多层阴影
     * @param {Function} drawFn - 绘制函数
     * @param {Array<Object>} shadowLayers - 阴影层配置
     */
    drawWithMultiShadow(drawFn, shadowLayers = []) {
        const ctx = this.ctx;
        shadowLayers.forEach(shadow => {
            ctx.save();
            this.set(shadow);
            drawFn(ctx);
            ctx.restore();
        });
        // 最后绘制无阴影的图形本身
        ctx.save();
        this.clear();
        drawFn(ctx);
        ctx.restore();
    }
}

// 使用示例
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
const shadow = new ShadowHelper(ctx);

// Material Design 阴影
shadow.drawWithShadow(
    (ctx) => {
        ctx.fillStyle = '#fff';
        ctx.beginPath();
        ctx.roundRect(50, 50, 200, 100, 8);
        ctx.fill();
    },
    ShadowHelper.materialElevation(4)
);

// 发光效果
shadow.drawWithShadow(
    (ctx) => {
        ctx.fillStyle = '#6200ee';
        ctx.beginPath();
        ctx.arc(400, 100, 30, 0, Math.PI * 2);
        ctx.fill();
    },
    ShadowHelper.glow('#6200ee', 20)
);

// 多层阴影
shadow.drawWithMultiShadow(
    (ctx) => {
        ctx.fillStyle = '#fff';
        ctx.fillRect(50, 200, 200, 100);
    },
    [
        { color: 'rgba(0,0,0,0.05)', blur: 30, offsetY: 15 },
        { color: 'rgba(0,0,0,0.1)', blur: 10, offsetY: 5 }
    ]
);

八、常见问题与解决方案

常见问题

阴影为什么影响后续所有绘制?

阴影属性是状态性的,设置后持续生效。解决方案是使用save()/restore()配对管理阴影状态,或在不需要阴影时手动重置shadowColor为transparent、shadowBlur为0。

Canvas阴影性能差怎么优化?

大shadowBlur值导致高斯模糊计算量大。优化方案:减小shadowBlur值,对静态内容使用离屏Canvas缓存带阴影的图形,然后在主Canvas上用drawImage绘制缓存。

阴影在图形边缘被裁切怎么办?

阴影偏移或模糊范围超出了Canvas边界。解决方案是留出足够的边距,或增大Canvas尺寸,确保阴影范围在画布内。

Canvas如何实现内阴影效果?

Canvas不原生支持内阴影,但可以通过渐变模拟:先clip裁剪到图形区域,然后用线性渐变从半透明黑色过渡到透明,绘制在图形边缘模拟内阴影。

如何创建多层阴影效果?

通过多次绘制同一图形,每次使用不同的阴影参数(如外层柔和阴影shadowBlur=30加内层锐利阴影shadowBlur=10),叠加出更真实的多层阴影效果。


九、总结

Canvas 阴影是增强视觉层次感的重要工具:

  • 四个属性shadowColorshadowBlurshadowOffsetXshadowOffsetY 共同控制阴影效果

  • 阴影颜色:默认完全透明(无阴影),需显式设置才能看到效果

  • 发光效果:偏移为 0、模糊值大时产生发光效果

  • 状态性:阴影设置会影响所有后续绘制,需用 save/restore 管理

  • 性能:大 shadowBlur 值影响性能,静态内容应缓存

  • 内阴影:Canvas 不原生支持,需用渐变等技巧模拟

  • 多层阴影:多次绘制同一图形叠加不同阴影,可创建更真实的效果

  • 实战应用:卡片阴影、按钮效果、文本发光、Material Design 等

标签: Canvas阴影 shadowColor shadowBlur 发光效果 内阴影 多层阴影

本文涉及AI创作

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

list快速访问

上一篇: 多媒体:图案 - 完整教程与代码示例 下一篇: 多媒体:Canvas图像处理 - 完整教程与代码示例

poll相关推荐