当前位置: 首页 > news >正文

Canvas渲染管线解析:从API调用到像素落地的全过程

1.Canvas 基本定义 ★ 了解

Canvas 是 HTML5 提供的一个通过 JavaScript 来绘制图形的元素。它提供了一个空白的绘图区域,开发者可以使用 JavaScript 脚本在其中绘制各种图形、动画、游戏画面等。

2.Canvas 使用场景 ★ 了解

  1. 数据可视化:绘制图表、图形等

  2. 游戏开发:HTML5 游戏

  3. 图像处理:滤镜、像素操作

  4. 动画效果:创建动态视觉效果

  5. 交互式图形:绘图应用、设计工具

  6. 教育演示:数学函数可视化等

3.Canvas 示例代码  ★ 基础

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Canvas 示例</title>
    <style>
        body {
            display: flex;
            flex-direction: column;
            align-items: center;
            font-family: Arial, sans-serif;
        }
        canvas {
            border: 1px solid #ccc;
            margin: 10px;
        }
    </style>
</head>
<body>
    <h1>Canvas 使用示例</h1>
    
    <!-- 示例1:基本形状绘制 -->
    <h2>1. 基本形状绘制</h2>
    <canvas id="basicShapes" width="400" height="200"></canvas>
    
    <!-- 示例2:路径绘制 -->
    <h2>2. 路径绘制</h2>
    <canvas id="pathDrawing" width="400" height="200"></canvas>
    
    <!-- 示例3:文本绘制 -->
    <h2>3. 文本绘制</h2>
    <canvas id="textDrawing" width="400" height="100"></canvas>
    
    <!-- 示例4:简单动画 -->
    <h2>4. 简单动画</h2>
    <canvas id="animation" width="400" height="200"></canvas>
    
    <script>
        // 示例1:基本形状绘制
        const basicCanvas = document.getElementById('basicShapes');
        const basicCtx = basicCanvas.getContext('2d');
        
        // 绘制填充矩形
        basicCtx.fillStyle = 'rgba(255, 0, 0, 0.5)';
        basicCtx.fillRect(50, 50, 100, 80);
        
        // 绘制描边矩形
        basicCtx.strokeStyle = 'blue';
        basicCtx.lineWidth = 3;
        basicCtx.strokeRect(200, 50, 100, 80);
        
        // 绘制圆形
        basicCtx.beginPath();
        basicCtx.fillStyle = 'green';
        basicCtx.arc(350, 90, 40, 0, Math.PI * 2);
        basicCtx.fill();
        
        // 示例2:路径绘制
        const pathCanvas = document.getElementById('pathDrawing');
        const pathCtx = pathCanvas.getContext('2d');
        
        // 绘制三角形
        pathCtx.beginPath();
        pathCtx.moveTo(50, 50);
        pathCtx.lineTo(150, 50);
        pathCtx.lineTo(100, 150);
        pathCtx.closePath();
        pathCtx.fillStyle = 'purple';
        pathCtx.fill();
        
        // 绘制复杂路径
        pathCtx.beginPath();
        pathCtx.moveTo(200, 50);
        pathCtx.lineTo(250, 150);
        pathCtx.lineTo(300, 50);
        pathCtx.lineTo(350, 150);
        pathCtx.lineTo(400, 50);
        pathCtx.strokeStyle = 'orange';
        pathCtx.lineWidth = 2;
        pathCtx.stroke();
        
        // 示例3:文本绘制
        const textCanvas = document.getElementById('textDrawing');
        const textCtx = textCanvas.getContext('2d');
        
        textCtx.font = '30px Arial';
        textCtx.fillStyle = 'red';
        textCtx.fillText('填充文本', 50, 50);
        
        textCtx.font = '25px Verdana';
        textCtx.strokeStyle = 'blue';
        textCtx.lineWidth = 1;
        textCtx.strokeText('描边文本', 50, 90);
        
        // 示例4:简单动画
        const animCanvas = document.getElementById('animation');
        const animCtx = animCanvas.getContext('2d');
        
        let x = 0;
        let y = 100;
        let dx = 2;
        
        function animate() {
            // 清除画布
            animCtx.clearRect(0, 0, animCanvas.width, animCanvas.height);
            
            // 绘制小球
            animCtx.beginPath();
            animCtx.arc(x, y, 20, 0, Math.PI * 2);
            animCtx.fillStyle = 'red';
            animCtx.fill();
            
            // 更新位置
            x += dx;
            
            // 边界检测
            if (x > animCanvas.width - 20 || x < 20) {
                dx = -dx;
            }
            
            requestAnimationFrame(animate);
        }
        
        animate();
    </script>
</body>
</html>

4.Canvas 2D API 完整列表与示例 

4.1 Canvas 2D API 完整列表 ★ 重点

1. 绘图状态属性

属性类型描述默认值
fillStyleString/Gradient/Pattern填充颜色或样式'#000000'
strokeStyleString/Gradient/Pattern描边颜色或样式'#000000'
lineWidthNumber线条宽度(像素)1.0
lineCapString线条末端样式('butt''round''square')'butt'
lineJoinString线条连接样式('round''bevel''miter')'miter'
miterLimitNumber斜接长度限制10.0
lineDashOffsetNumber虚线偏移量0.0
shadowBlurNumber阴影模糊程度0
shadowColorString阴影颜色'rgba(0, 0, 0, 0)'
shadowOffsetXNumber阴影水平偏移0
shadowOffsetYNumber阴影垂直偏移0
globalAlphaNumber全局透明度(0.0-1.0)1.0
globalCompositeOperationString图形合成方式'source-over'
fontString文本字体样式'10px sans-serif'
textAlignString文本水平对齐('start''end''left''right''center')'start'
textBaselineString文本垂直对齐('top''hanging''middle''alphabetic''ideographic''bottom')'alphabetic'

2. 路径绘制方法

方法参数描述
beginPath()-开始新路径
closePath()-闭合当前路径
moveTo(x, y)x, y移动绘制起点
lineTo(x, y)x, y绘制直线到点
arc(x, y, radius, startAngle, endAngle, anticlockwise)x, y, radius, startAngle, endAngle, anticlockwise绘制圆弧
arcTo(x1, y1, x2, y2, radius)x1, y1, x2, y2, radius绘制弧线
quadraticCurveTo(cpx, cpy, x, y)cpx, cpy, x, y二次贝塞尔曲线
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)cp1x, cp1y, cp2x, cp2y, x, y三次贝塞尔曲线
rect(x, y, width, height)x, y, width, height添加矩形路径
fill()[fillRule]填充当前路径
stroke()-描边当前路径
clip()[fillRule]创建裁剪区域
isPointInPath(x, y)x, y判断点是否在路径内

3. 绘图方法

方法参数描述
fillRect(x, y, width, height)x, y, width, height绘制填充矩形
strokeRect(x, y, width, height)x, y, width, height绘制描边矩形
clearRect(x, y, width, height)x, y, width, height清除矩形区域

4. 文本方法

方法参数描述
fillText(text, x, y [, maxWidth])text, x, y, maxWidth绘制填充文本
strokeText(text, x, y [, maxWidth])text, x, y, maxWidth绘制描边文本
measureText(text)text测量文本宽度

5. 图像方法

方法参数描述
drawImage(image, dx, dy)image, dx, dy绘制图像
drawImage(image, dx, dy, dWidth, dHeight)image, dx, dy, dWidth, dHeight绘制缩放图像
drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight绘制裁剪图像

6. 像素操作

方法参数描述
createImageData(width, height)width, height创建空白ImageData
createImageData(imagedata)imagedata复制ImageData
getImageData(sx, sy, sw, sh)sx, sy, sw, sh获取像素数据
putImageData(imagedata, dx, dy)imagedata, dx, dy放置像素数据
putImageData(imagedata, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight)imagedata, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight放置部分像素数据

7. 变换方法

方法参数描述
scale(x, y)x, y缩放绘图
rotate(angle)angle旋转绘图
translate(x, y)x, y移动原点
transform(a, b, c, d, e, f)a, b, c, d, e, f矩阵变换
setTransform(a, b, c, d, e, f)a, b, c, d, e, f重置并变换
resetTransform()-重置变换

8. 渐变和图案

方法参数描述
createLinearGradient(x0, y0, x1, y1)x0, y0, x1, y1创建线性渐变
createRadialGradient(x0, y0, r0, x1, y1, r1)x0, y0, r0, x1, y1, r1创建径向渐变
createPattern(image, repetition)image, repetition创建图案
addColorStop(position, color)position, color渐变添加色标

9. 状态管理

方法参数描述
save()-保存当前状态
restore()-恢复之前状态

10. 其他方法

方法参数描述
setLineDash(segments)segments设置虚线样式
getLineDash()-获取虚线样式

4.2 Canvas 2D完整HTML示例 ★ 了解

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Canvas 2D API 完整示例</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 20px;
            display: flex;
            flex-direction: column;
            align-items: center;
            background-color: #f5f5f5;
        }
        h1 {
            color: #333;
            margin-bottom: 20px;
        }
        .canvas-container {
            margin: 20px 0;
            border: 1px solid #ddd;
            background-color: white;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        canvas {
            display: block;
        }
        .controls {
            margin: 20px 0;
            display: flex;
            gap: 10px;
        }
        button {
            padding: 8px 16px;
            background-color: #4CAF50;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
        button:hover {
            background-color: #45a049;
        }
        .code-block {
            background-color: #f8f8f8;
            padding: 15px;
            border-radius: 5px;
            font-family: monospace;
            white-space: pre-wrap;
            margin: 20px 0;
            border: 1px solid #ddd;
        }
    </style>
</head>
<body>
    <h1>Canvas 2D API 演示</h1>
    
    <div class="canvas-container">
        <canvas id="demoCanvas" width="500" height="300"></canvas>
    </div>
    
    <div class="controls">
        <button id="drawBtn">绘制图形</button>
        <button id="clearBtn">清除画布</button>
    </div>
    
    <div class="code-block">
        // 获取Canvas元素和上下文
        const canvas = document.getElementById('demoCanvas');
        const ctx = canvas.getContext('2d');
        
        // 绘制矩形
        ctx.fillStyle = 'rgba(255, 0, 0, 0.5)';
        ctx.fillRect(50, 50, 100, 80);
        
        // 绘制圆形
        ctx.beginPath();
        ctx.arc(300, 100, 50, 0, Math.PI * 2);
        ctx.fillStyle = 'blue';
        ctx.fill();
    </div>

    <script>
        // 等待DOM完全加载
        document.addEventListener('DOMContentLoaded', function() {
            // 获取Canvas元素和上下文
            const canvas = document.getElementById('demoCanvas');
            const ctx = canvas.getContext('2d');
            const drawBtn = document.getElementById('drawBtn');
            const clearBtn = document.getElementById('clearBtn');
            
            // 初始化Canvas
            function initCanvas() {
                // 设置默认样式
                ctx.fillStyle = '#f8f8f8';
                ctx.fillRect(0, 0, canvas.width, canvas.height);
                
                // 设置绘制样式
                ctx.strokeStyle = '#333';
                ctx.lineWidth = 1;
                ctx.font = '14px Arial';
                ctx.fillStyle = '#333';
                
                // 绘制网格背景
                drawGrid();
            }
            
            // 绘制网格背景
            function drawGrid() {
                ctx.save(); // 保存当前状态
                ctx.strokeStyle = '#eee';
                
                // 绘制垂直线
                for (let x = 0; x <= canvas.width; x += 20) {
                    ctx.beginPath();
                    ctx.moveTo(x, 0);
                    ctx.lineTo(x, canvas.height);
                    ctx.stroke();
                }
                
                // 绘制水平线
                for (let y = 0; y <= canvas.height; y += 20) {
                    ctx.beginPath();
                    ctx.moveTo(0, y);
                    ctx.lineTo(canvas.width, y);
                    ctx.stroke();
                }
                
                ctx.restore(); // 恢复之前状态
            }
            
            // 绘制示例图形
            function drawShapes() {
                // 1. 绘制基本形状
                ctx.save();
                ctx.fillStyle = 'rgba(255, 0, 0, 0.5)';
                ctx.fillRect(50, 50, 100, 80);
                
                ctx.strokeStyle = 'blue';
                ctx.lineWidth = 3;
                ctx.strokeRect(50, 150, 100, 80);
                
                // 2. 绘制路径(三角形)
                ctx.beginPath();
                ctx.moveTo(200, 50);
                ctx.lineTo(250, 150);
                ctx.lineTo(150, 150);
                ctx.closePath();
                ctx.fillStyle = 'green';
                ctx.fill();
                
                // 3. 绘制圆形
                ctx.beginPath();
                ctx.arc(350, 100, 50, 0, Math.PI * 2);
                ctx.fillStyle = 'purple';
                ctx.fill();
                
                // 4. 绘制贝塞尔曲线
                ctx.beginPath();
                ctx.moveTo(200, 200);
                ctx.bezierCurveTo(250, 150, 300, 250, 350, 200);
                ctx.strokeStyle = 'orange';
                ctx.lineWidth = 2;
                ctx.stroke();
                
                // 5. 绘制文本
                ctx.font = '20px Arial';
                ctx.fillStyle = 'black';
                ctx.fillText('Canvas 2D API 示例', 150, 250);
                
                // 6. 使用渐变
                const gradient = ctx.createLinearGradient(0, 0, 0, 300);
                gradient.addColorStop(0, 'red');
                gradient.addColorStop(0.5, 'yellow');
                gradient.addColorStop(1, 'blue');
                
                ctx.fillStyle = gradient;
                ctx.fillRect(400, 0, 100, 300);
                
                ctx.restore();
            }
            
            // 清除画布
            function clearCanvas() {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                initCanvas();
            }
            
            // 事件监听
            drawBtn.addEventListener('click', drawShapes);
            clearBtn.addEventListener('click', clearCanvas);
            
            // 初始化
            initCanvas();
        });
    </script>
</body>
</html>

5.Canvas 的兼容性分析 ★ 关注

Canvas 作为 HTML5 的核心特性之一,在现代浏览器中有很好的支持,但也存在一些兼容性注意事项。

5.1 浏览器支持情况

主流浏览器支持

  • Chrome:完全支持(包括移动版)

  • Firefox:完全支持(包括移动版)

  • Safari:完全支持(包括 iOS 版)

  • Edge:完全支持(基于 Chromium 的新版)

  • Opera:完全支持

旧版浏览器支持

  • IE 9+:基本支持 Canvas,但某些高级特性可能不支持

  • IE 8 及更早版本:不支持 Canvas

5.2 兼容性注意事项

  1. 基本 Canvas 支持检测

    if (document.createElement('canvas').getContext) {
        // 支持 Canvas
    } else {
        // 不支持 Canvas,提供回退方案
    }
  2. 文本 API 差异

    • 某些浏览器对 measureText() 方法的实现略有不同

    • 文本渲染质量在不同浏览器/操作系统上可能有差异

  3. 图像导出格式

    • toDataURL() 和 toBlob() 方法支持的图像格式可能不同

    • 某些浏览器可能不支持 PNG 以外的格式

  4. 性能差异

    • 不同浏览器/设备的 Canvas 渲染性能差异较大

    • 移动设备上的性能通常低于桌面设备

5.3 常见兼容性问题解决方案

1. 针对 IE 8 及更早版本的回退方案

<canvas id="myCanvas">
    您的浏览器不支持 Canvas,以下是替代内容:
    <img src="fallback.png" alt="替代图像">
</canvas>

2. 跨浏览器前缀处理

某些 API 可能需要浏览器前缀:

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d') || 
            canvas.getContext('experimental-webgl');

3. 功能检测而非浏览器检测

// 检测特定功能是否可用
function isCanvasSupported() {
    const elem = document.createElement('canvas');
    return !!(elem.getContext && elem.getContext('2d'));
}

5.4 实际兼容性数据

根据 Can I Use 网站的最新数据(截至2023年):

浏览器/版本支持情况
Chrome 4+✅ 完全支持
Firefox 2+✅ 完全支持
Safari 3.1+✅ 完全支持
Edge 12+✅ 完全支持
IE 9+✅ 基本支持
IE 6-8❌ 不支持
Opera 9+✅ 完全支持
iOS Safari 3.2+✅ 完全支持
Android Browser 2.1+✅ 完全支持

5.5 高级特性的兼容性

  1. WebGL (3D Canvas)

    • 需要额外检测 getContext('webgl') 或 getContext('experimental-webgl')

    • 移动设备支持有限

  2. 混合模式

    • globalCompositeOperation 的某些模式在不同浏览器中表现可能不同

  3. 滤镜效果

    • 非标准滤镜(如 filter: blur())支持不一致

5.6 最佳实践建议

  1. 始终进行特性检测:不要假设 Canvas 可用

  2. 为旧版浏览器提供替代内容:如图片或文字说明

  3. 性能敏感应用进行能力检测:检测渲染性能

  4. 考虑使用 polyfill:如 FlashCanvas 为 IE 6-8 提供支持

  5. 测试不同设备和浏览器:特别是移动设备

5.7 兼容性测试示例代码

<!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>
        /* 结果展示区域的样式 */
        #result {
            padding: 10px;
            margin: 10px;
            border: 1px solid #ccc;
            background-color: #f8f8f8;
        }
        /* 支持的功能样式 */
        .supported {
            color: green;
            font-weight: bold;
        }
        /* 不支持的功能样式 */
        .unsupported {
            color: red;
            font-weight: bold;
        }
        /* Canvas容器的样式 */
        #canvasContainer {
            margin: 20px 0;
            text-align: center;
        }
        /* Canvas元素本身的样式 */
        #testCanvas {
            border: 1px solid #000;
            background-color: #fff;
        }
        /* 整体页面样式 */
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            line-height: 1.6;
        }
        h1 {
            color: #333;
            text-align: center;
        }
    </style>
</head>
<body>
<h1>Canvas兼容性测试</h1>

<!-- 结果显示区域 -->
<div id="result">正在检测浏览器支持情况...</div>

<!-- Canvas容器,包含备用内容 -->
<div id="canvasContainer">
    <canvas id="testCanvas" width="200" height="100">
        您的浏览器不支持HTML5 Canvas,请升级到现代浏览器如Chrome、Firefox、Safari或Edge。
    </canvas>
</div>

<script>
    // 等待DOM完全加载后执行测试
    document.addEventListener('DOMContentLoaded', function() {
        // 获取DOM元素
        const resultDiv = document.getElementById('result');
        const canvas = document.getElementById('testCanvas');

        // 1. 基本支持检测
        // 检查浏览器是否支持Canvas元素和getContext方法
        if (!canvas || typeof canvas.getContext !== 'function') {
            resultDiv.innerHTML = '<span class="unsupported">您的浏览器不支持Canvas。</span>' +
                '<p>建议升级到最新版本的Chrome、Firefox、Safari或Edge。</p>';
            return;
        }

        // 尝试获取2D绘图上下文
        let ctx;
        try {
            ctx = canvas.getContext('2d');
            if (!ctx) {
                throw new Error('无法获取2D上下文');
            }
        } catch (e) {
            resultDiv.innerHTML = '<span class="unsupported">您的浏览器不支持Canvas 2D绘图。</span>';
            console.error('获取Canvas上下文失败:', e);
            return;
        }

        // 2. 测试基本绘图功能
        try {
            // 测试填充矩形
            ctx.fillStyle = 'rgba(0, 128, 0, 0.7)'; // 半透明绿色
            ctx.fillRect(10, 10, 50, 50);

            // 测试描边矩形
            ctx.strokeStyle = 'blue';
            ctx.lineWidth = 2;
            ctx.strokeRect(70, 10, 50, 50);

            // 测试圆形绘制
            ctx.fillStyle = 'red';
            ctx.beginPath();
            ctx.arc(150, 35, 25, 0, Math.PI * 2);
            ctx.fill();

            // 基本功能测试通过
            resultDiv.innerHTML = '<span class="supported">您的浏览器完全支持Canvas基本功能。</span>';

            // 3. 检测高级功能
            detectAdvancedFeatures();
        } catch (e) {
            resultDiv.innerHTML = '<span class="unsupported">您的浏览器部分支持Canvas,但某些功能无法使用。</span>' +
                '<p>错误信息: ' + e.message + '</p>';
            console.error('Canvas基本功能测试失败:', e);
        }

        /**
         * 检测Canvas高级功能支持情况
         */
        function detectAdvancedFeatures() {
            // 定义要检测的功能列表
            const features = {
                text: {
                    name: '文本绘制',
                    supported: false,
                    test: testTextSupport
                },
                shadows: {
                    name: '阴影效果',
                    supported: false,
                    test: testShadowSupport
                },
                gradients: {
                    name: '渐变填充',
                    supported: false,
                    test: testGradientSupport
                },
                imageData: {
                    name: '像素操作',
                    supported: false,
                    test: testImageDataSupport
                },
                lineDash: {
                    name: '虚线样式',
                    supported: false,
                    test: testLineDashSupport
                },
                globalAlpha: {
                    name: '全局透明度',
                    supported: false,
                    test: testGlobalAlphaSupport
                }
            };

            // 执行所有功能测试
            for (const feature in features) {
                if (features.hasOwnProperty(feature)) {
                    try {
                        features[feature].test();
                        features[feature].supported = true;
                    } catch (e) {
                        console.warn(features[feature].name + '测试失败:', e);
                    }
                }
            }

            // 显示检测结果
            let featuresHtml = '<h3>高级功能支持情况:</h3><ul>';
            for (const feature in features) {
                if (features.hasOwnProperty(feature)) {
                    featuresHtml += `<li>${features[feature].name}: ` +
                        `<span class="${features[feature].supported ? 'supported' : 'unsupported'}">` +
                        `${features[feature].supported ? '✓ 支持' : '✗ 不支持'}</span></li>`;
                }
            }
            featuresHtml += '</ul>';

            resultDiv.innerHTML += featuresHtml;

            // 测试文本支持
            function testTextSupport() {
                ctx.font = '14px Arial';
                ctx.fillStyle = 'black';
                ctx.fillText('文本测试', 10, 80);

                // 额外检测measureText方法
                if (typeof ctx.measureText !== 'function') {
                    throw new Error('measureText方法不支持');
                }
                const metrics = ctx.measureText('测试');
                if (typeof metrics.width !== 'number') {
                    throw new Error('文本测量功能异常');
                }
            }

            // 测试阴影支持
            function testShadowSupport() {
                ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';
                ctx.shadowBlur = 5;
                ctx.shadowOffsetX = 3;
                ctx.shadowOffsetY = 3;
                ctx.fillStyle = 'purple';
                ctx.fillRect(180, 10, 10, 10);

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

            // 测试渐变支持
            function testGradientSupport() {
                const gradient = ctx.createLinearGradient(0, 0, 100, 0);
                if (!gradient || typeof gradient.addColorStop !== 'function') {
                    throw new Error('渐变创建失败');
                }
                gradient.addColorStop(0, 'red');
                gradient.addColorStop(1, 'blue');

                // 测试径向渐变
                const radialGradient = ctx.createRadialGradient(50, 50, 5, 50, 50, 30);
                if (!radialGradient) {
                    throw new Error('径向渐变不支持');
                }
            }

            // 测试像素操作支持
            function testImageDataSupport() {
                const imageData = ctx.createImageData(1, 1);
                if (!imageData || !imageData.data || imageData.data.length !== 4) {
                    throw new Error('ImageData创建失败');
                }

                // 测试getImageData和putImageData
                const testData = ctx.getImageData(0, 0, 1, 1);
                ctx.putImageData(testData, 0, 0);
            }

            // 测试虚线样式支持
            function testLineDashSupport() {
                if (typeof ctx.setLineDash !== 'function' ||
                    typeof ctx.getLineDash !== 'function') {
                    throw new Error('虚线API不支持');
                }

                ctx.setLineDash([5, 3]);
                const dashPattern = ctx.getLineDash();
                if (!Array.isArray(dashPattern) || dashPattern.length !== 2) {
                    throw new Error('虚线功能异常');
                }

                // 重置为实线
                ctx.setLineDash([]);
            }

            // 测试全局透明度支持
            function testGlobalAlphaSupport() {
                const originalAlpha = ctx.globalAlpha;
                ctx.globalAlpha = 0.5;
                if (ctx.globalAlpha !== 0.5) {
                    throw new Error('全局透明度设置失败');
                }
                ctx.globalAlpha = originalAlpha;
            }
        }
    });
</script>
</body>
</html>

6.手划签名的案例 ★ 综合案例

最终效果展示

完整代码如下 

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <!-- 移动端视口设置,禁止缩放 -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>高清手写签名板</title>
    <style>
        /* 全局样式重置 */
        * {
            box-sizing: border-box;  /* 盒模型设置为border-box更易控制尺寸 */
            margin: 0;              /* 清除默认外边距 */
            padding: 0;             /* 清除默认内边距 */
            -webkit-tap-highlight-color: transparent; /* 移除移动端点击高亮效果 */
        }

        /* 页面主体样式 */
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
            margin: 0;
            padding: 20px;
            display: flex;
            flex-direction: column;  /* 垂直排列子元素 */
            align-items: center;    /* 水平居中 */
            touch-action: none;     /* 禁用浏览器默认触摸行为 */
            background-color: #f5f5f5; /* 浅灰色背景 */
            min-height: 100vh;     /* 最小高度为视口高度 */
        }

        /* 标题样式 */
        h1 {
            color: #333;           /* 深灰色文字 */
            margin-bottom: 20px;   /* 底部间距 */
            font-size: 1.5rem;     /* 响应式字体大小 */
            text-align: center;    /* 文字居中 */
        }

        /* Canvas容器样式 */
        .canvas-container {
            position: relative;
            margin-bottom: 20px;
            border: 2px solid #333; /* 深灰色边框 */
            background-color: #fff; /* 白色背景 */
            box-shadow: 0 2px 10px rgba(0,0,0,0.1); /* 轻微阴影效果 */
            width: 100%;          /* 宽度100% */
            max-width: 600px;     /* 最大宽度限制 */
            aspect-ratio: 2/1;    /* 固定宽高比为2:1 */
        }

        /* Canvas元素样式 */
        #signatureCanvas {
            display: block;       /* 块级元素 */
            width: 100%;         /* 宽度填满容器 */
            height: 100%;        /* 高度填满容器 */
            touch-action: none;  /* 禁用默认触摸行为 */
        }

        /* 控制按钮区域样式 */
        .controls {
            margin-bottom: 20px;
            display: flex;       /* 弹性布局 */
            gap: 10px;           /* 按钮间距 */
            flex-wrap: wrap;     /* 允许换行 */
            justify-content: center; /* 水平居中 */
            width: 100%;        /* 宽度100% */
            max-width: 600px;   /* 最大宽度限制 */
        }

        /* 按钮基础样式 */
        button {
            padding: 12px 20px;  /* 内边距 */
            background-color: #4CAF50; /* 绿色背景 */
            color: white;        /* 白色文字 */
            border: none;        /* 无边框 */
            border-radius: 6px;  /* 圆角 */
            cursor: pointer;     /* 手型光标 */
            font-size: 1rem;     /* 字体大小 */
            min-width: 120px;    /* 最小宽度 */
            transition: background-color 0.2s; /* 背景色过渡动画 */
        }

        /* 按钮悬停效果 */
        button:hover {
            background-color: #45a049; /* 深绿色 */
        }

        /* 清除按钮特殊样式 */
        button#clearBtn {
            background-color: #f44336; /* 红色背景 */
        }

        button#clearBtn:hover {
            background-color: #d32f2f; /* 深红色 */
        }

        /* 预览区域样式 */
        .preview {
            margin-top: 20px;
            text-align: center;  /* 文字居中 */
            width: 100%;        /* 宽度100% */
            max-width: 600px;   /* 最大宽度限制 */
        }

        /* 预览图片样式 */
        #signaturePreview {
            max-width: 100%;    /* 最大宽度100% */
            border: 1px solid #ddd; /* 浅灰色边框 */
            margin-top: 10px;   /* 顶部间距 */
            background-color: white; /* 白色背景 */
            box-shadow: 0 1px 3px rgba(0,0,0,0.1); /* 轻微阴影 */
        }

        /* 使用说明文本样式 */
        .instructions {
            margin-top: 15px;
            color: #666;        /* 灰色文字 */
            font-size: 0.9rem;  /* 较小字体 */
            text-align: center; /* 文字居中 */
            padding: 0 10px;    /* 左右内边距 */
        }

        /* 移动端响应式样式 */
        @media (max-width: 480px) {
            body {
                padding: 15px;  /* 较小的内边距 */
            }
            h1 {
                font-size: 1.3rem; /* 较小的字体 */
                margin-bottom: 15px;
            }
            button {
                padding: 10px 15px; /* 较小的内边距 */
                font-size: 0.9rem;  /* 较小的字体 */
                min-width: 100px;   /* 较小的最小宽度 */
            }
        }
    </style>
</head>
<body>
<h1>高清手写签名板</h1>

<!-- Canvas签名区域容器 -->
<div class="canvas-container">
    <!-- Canvas绘图区域 -->
    <canvas id="signatureCanvas"></canvas>
</div>

<!-- 控制按钮区域 -->
<div class="controls">
    <!-- 保存签名按钮 -->
    <button id="saveBtn">保存签名</button>
    <!-- 清除签名按钮 -->
    <button id="clearBtn">清除签名</button>
</div>

<!-- 签名预览区域 -->
<div class="preview">
    <h3>签名预览:</h3>
    <!-- 签名预览图片,初始隐藏 -->
    <img id="signaturePreview" alt="签名预览" style="display: none;">
</div>

<!-- 使用说明 -->
<p class="instructions">PC端: 使用鼠标按住并拖动来签名 | 移动端: 使用手指触摸并滑动来签名</p>

<script>
    /**
     * 自定义Canvas2Image实现
     * 解决原生Canvas2Image库在部分浏览器中无法直接保存data URL的问题
     * 使用toBlob API实现更可靠的图片保存功能
     */
    const CustomCanvas2Image = {
        /**
         * 保存Canvas为PNG图片
         * @param {HTMLCanvasElement} canvas - 要保存的Canvas元素
         * @param {number} [width] - 可选,输出图片宽度
         * @param {number} [height] - 可选,输出图片高度
         * @param {string} [fileName] - 可选,保存的文件名
         */
        saveAsPNG: function(canvas, width, height, fileName) {
            return this.convertToImage(canvas, width, height, fileName, 'png');
        },

        /**
         * 将Canvas转换为图片并保存
         * @param {HTMLCanvasElement} canvas - 要转换的Canvas元素
         * @param {number} [width] - 可选,输出图片宽度
         * @param {number} [height] - 可选,输出图片高度
         * @param {string} [fileName] - 可选,保存的文件名
         * @param {string} [type] - 图片类型,默认'png'
         */
        convertToImage: function(canvas, width, height, fileName, type) {
            // 设置默认文件名和图片类型
            fileName = fileName || 'untitled';
            type = type || 'png';

            // 创建隐藏的下载链接
            const link = document.createElement('a');
            link.download = fileName + '.' + type;

            /**
             * 使用Canvas的toBlob方法获取图片二进制数据
             * 这是比toDataURL更高效的方案,且不会遇到data URL长度限制问题
             */
            canvas.toBlob(function(blob) {
                // 创建对象URL
                link.href = URL.createObjectURL(blob);

                // 模拟点击下载
                const event = new MouseEvent('click');
                link.dispatchEvent(event);

                // 释放对象URL内存
                setTimeout(function() {
                    URL.revokeObjectURL(link.href);
                }, 100);
            }, 'image/' + type, 1.0); // 1.0表示最高质量
        }
    };

    /**
     * 主应用程序逻辑
     * 使用立即执行函数封装,避免污染全局命名空间
     */
    (function() {
        'use strict'; // 启用严格模式

        // ==================== DOM元素获取 ====================
        // Canvas容器元素
        const container = document.querySelector('.canvas-container');
        // Canvas绘图元素
        const canvas = document.getElementById('signatureCanvas');
        // Canvas 2D绘图上下文
        const ctx = canvas.getContext('2d');
        // 保存按钮
        const saveBtn = document.getElementById('saveBtn');
        // 清除按钮
        const clearBtn = document.getElementById('clearBtn');
        // 预览图片元素
        const previewImg = document.getElementById('signaturePreview');

        // ==================== 全局变量 ====================
        // 设备像素比(用于高清显示)
        const devicePixelRatio = window.devicePixelRatio || 1;
        // 是否正在绘制的标志
        let isDrawing = false;
        // 上一个点的X坐标
        let lastX = 0;
        // 上一个点的Y坐标
        let lastY = 0;
        // 存储触摸点的数组(用于平滑处理)
        let points = [];

        // ==================== Canvas初始化 ====================
        /**
         * 设置Canvas尺寸和样式
         * 考虑设备像素比以确保高清显示
         */
        function setupCanvas() {
            // 获取容器的实际显示尺寸
            const rect = container.getBoundingClientRect();
            const width = rect.width;
            const height = rect.height;

            /**
             * 设置Canvas的实际像素尺寸
             * 乘以devicePixelRatio确保在高DPI设备上清晰显示
             */
            canvas.width = Math.floor(width * devicePixelRatio);
            canvas.height = Math.floor(height * devicePixelRatio);

            // 设置Canvas的显示尺寸(CSS像素)
            canvas.style.width = `${width}px`;
            canvas.style.height = `${height}px`;

            /**
             * 缩放绘图上下文以匹配设备像素比
             * 这样我们在逻辑坐标中绘图时,会自动映射到物理像素
             */
            ctx.scale(devicePixelRatio, devicePixelRatio);

            // 设置默认绘制样式
            ctx.strokeStyle = '#000000';  // 黑色线条
            ctx.lineWidth = 2;            // 2像素线宽
            ctx.lineCap = 'round';        // 圆形线帽
            ctx.lineJoin = 'round';       // 圆形线连接

            // 初始化清空画布
            clearCanvas();
        }

        // ==================== 画布操作函数 ====================
        /**
         * 清除画布内容
         * 重置为白色背景
         */
        function clearCanvas() {
            // 保存当前绘图状态
            ctx.save();
            // 重置变换矩阵
            ctx.setTransform(1, 0, 0, 1, 0, 0);
            // 填充白色背景
            ctx.fillStyle = '#ffffff';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            // 恢复之前保存的绘图状态
            ctx.restore();
            // 隐藏预览图片
            previewImg.style.display = 'none';
            // 清空存储的点数组
            points = [];
        }

        // ==================== 签名保存函数 ====================
        /**
         * 保存签名图片
         * 使用自定义的Canvas2Image实现
         */
        function saveSignature() {
            try {
                // 显示预览(使用data URL)
                previewImg.src = canvas.toDataURL('image/png');
                previewImg.style.display = 'block';

                /**
                 * 使用自定义的Canvas2Image保存高清PNG图片
                 * 参数说明:
                 * 1. canvas元素
                 * 2. 宽度(null表示使用原始尺寸)
                 * 3. 高度(null表示使用原始尺寸)
                 * 4. 文件名(包含时间戳确保唯一)
                 */
                CustomCanvas2Image.saveAsPNG(
                    canvas,
                    null,
                    null,
                    `signature_${new Date().getTime()}`
                );

                console.log('签名保存成功');
            } catch (error) {
                console.error('保存签名失败:', error);

                /**
                 * 备用保存方案:当直接保存失败时
                 * 1. 显示预览图片
                 * 2. 提示用户手动长按保存
                 */
                const previewData = canvas.toDataURL('image/png');
                previewImg.src = previewData;
                previewImg.style.display = 'block';
                alert('请长按预览图片并选择"保存图片"');
            }
        }

        // ==================== 绘图相关函数 ====================
        /**
         * 获取精确的坐标位置(兼容鼠标和触摸事件)
         * @param {Event} e - 鼠标或触摸事件对象
         * @returns {Array} 包含x,y坐标的数组
         */
        function getPosition(e) {
            // 获取Canvas相对于视口的位置和尺寸
            const rect = canvas.getBoundingClientRect();
            let clientX, clientY;

            // 判断事件类型(触摸事件或鼠标事件)
            if (e.type.includes('touch')) {
                // 触摸事件:获取第一个触摸点
                const touch = e.touches[0] || e.changedTouches[0];
                clientX = touch.clientX;
                clientY = touch.clientY;
            } else {
                // 鼠标事件:直接获取坐标
                clientX = e.clientX;
                clientY = e.clientY;
            }

            // 计算缩放比例(考虑设备像素比)
            const scaleX = canvas.width / rect.width;
            const scaleY = canvas.height / rect.height;

            // 返回转换后的坐标(考虑设备像素比和Canvas缩放)
            return [
                (clientX - rect.left) * scaleX / devicePixelRatio,
                (clientY - rect.top) * scaleY / devicePixelRatio
            ];
        }

        /**
         * 开始绘制
         * @param {Event} e - 鼠标或触摸事件对象
         */
        function startDrawing(e) {
            e.preventDefault(); // 阻止默认行为(如滚动)
            isDrawing = true;   // 设置绘制标志
            const pos = getPosition(e); // 获取起始位置
            lastX = pos[0];     // 记录起始X坐标
            lastY = pos[1];     // 记录起始Y坐标
            points = [{x: lastX, y: lastY}]; // 初始化点数组

            // 开始新路径
            ctx.beginPath();
            ctx.moveTo(lastX, lastY);
        }

        /**
         * 绘制过程(包含平滑处理)
         * @param {Event} e - 鼠标或触摸事件对象
         */
        function draw(e) {
            if (!isDrawing) return; // 如果不是绘制状态则返回
            e.preventDefault();    // 阻止默认行为

            // 获取当前点坐标
            const pos = getPosition(e);
            const x = pos[0];
            const y = pos[1];

            // 存储当前点用于平滑处理
            points.push({x, y});

            // 限制处理点数以提高性能
            if (points.length > 10) {
                points.shift(); // 移除最旧的点
            }

            // 计算平滑后的点坐标
            const smoothed = smoothPoints(points);
            const smoothedX = smoothed.x;
            const smoothedY = smoothed.y;

            // 绘制线条到平滑后的点
            ctx.lineTo(smoothedX, smoothedY);
            ctx.stroke();

            // 更新最后位置
            lastX = smoothedX;
            lastY = smoothedY;
        }

        /**
         * 平滑处理函数(减少绘制锯齿)
         * @param {Array} points - 包含点对象的数组
         * @returns {Object} 平滑后的坐标 {x, y}
         */
        function smoothPoints(points) {
            // 如果点数不足2个,返回第一个点或默认值
            if (points.length < 2) return points[0] || {x: 0, y: 0};

            // 使用加权平均算法平滑坐标
            let sumX = 0;
            let sumY = 0;
            let weightSum = 0;

            // 遍历所有点,越近的点权重越大
            for (let i = 0; i < points.length; i++) {
                const weight = (i + 1) / points.length; // 线性权重
                sumX += points[i].x * weight;
                sumY += points[i].y * weight;
                weightSum += weight;
            }

            // 返回加权平均值
            return {
                x: sumX / weightSum,
                y: sumY / weightSum
            };
        }

        /**
         * 结束绘制
         */
        function stopDrawing() {
            if (isDrawing) {
                isDrawing = false; // 重置绘制标志
                points = [];      // 清空点数组
            }
        }

        // ==================== 事件监听 ====================
        /**
         * 添加所有事件监听器
         */
        function addEventListeners() {
            // ===== PC端鼠标事件 =====
            // 鼠标按下:开始绘制
            canvas.addEventListener('mousedown', startDrawing);
            // 鼠标移动:绘制过程
            canvas.addEventListener('mousemove', draw);
            // 鼠标释放:结束绘制
            canvas.addEventListener('mouseup', stopDrawing);
            // 鼠标离开Canvas:结束绘制
            canvas.addEventListener('mouseleave', stopDrawing);

            // ===== 移动端触摸事件 =====
            // 使用 passive: false 以便能够阻止默认滚动行为

            // 触摸开始:开始绘制
            canvas.addEventListener('touchstart', function(e) {
                e.preventDefault(); // 必须阻止默认行为
                startDrawing(e);
            }, {passive: false});

            // 触摸移动:绘制过程
            canvas.addEventListener('touchmove', function(e) {
                e.preventDefault(); // 必须阻止默认行为
                draw(e);
            }, {passive: false});

            // 触摸结束:结束绘制
            canvas.addEventListener('touchend', function(e) {
                e.preventDefault(); // 必须阻止默认行为
                stopDrawing();
            }, {passive: false});

            // ===== 按钮事件 =====
            // 保存按钮点击事件
            saveBtn.addEventListener('click', saveSignature);
            // 清除按钮点击事件
            clearBtn.addEventListener('click', clearCanvas);

            // ===== 窗口大小变化事件 =====
            // 使用防抖避免频繁重绘
            window.addEventListener('resize', function() {
                clearTimeout(this.resizeTimer);
                this.resizeTimer = setTimeout(() => {
                    setupCanvas();
                }, 200); // 200毫秒后执行
            });

            // ===== 阻止文档触摸移动事件 =====
            // 当正在绘制时阻止页面滚动
            document.addEventListener('touchmove', function(e) {
                if (isDrawing) {
                    e.preventDefault();
                }
            }, {passive: false});
        }

        // ==================== 兼容性检测 ====================
        /**
         * 检测浏览器兼容性
         * @returns {boolean} 是否兼容
         */
        function checkCompatibility() {
            // 检测Canvas支持
            if (!window.HTMLCanvasElement) {
                alert('您的浏览器不支持Canvas,请使用现代浏览器如Chrome、Firefox、Safari或Edge');
                return false;
            }

            try {
                // 测试获取2D上下文
                const testCanvas = document.createElement('canvas');
                if (!testCanvas.getContext || !testCanvas.getContext('2d')) {
                    alert('您的浏览器不支持Canvas 2D绘图');
                    return false;
                }

                // 测试toBlob方法支持
                if (!testCanvas.toBlob && !testCanvas.msToBlob) {
                    alert('您的浏览器不支持Canvas toBlob方法,将使用备用保存方案');
                }

                return true;
            } catch (e) {
                console.error('兼容性检测失败:', e);
                return false;
            }
        }

        // ==================== 初始化 ====================
        // 检查兼容性后初始化应用
        if (checkCompatibility()) {
            setupCanvas();      // 初始化Canvas设置
            addEventListeners(); // 添加事件监听
        }
    })();
</script>
</body>
</html>

相关文章:

  • 蓝桥杯省模拟赛 阶乘求值
  • QEMU源码全解析 —— 块设备虚拟化(12)
  • 线性回归 + 基础优化算法
  • docker - compose up - d`命令解释,重复运行会覆盖原有容器吗
  • 滚珠花键的预压调整怎么做?
  • 附录C SLAC匹配过程命令定义与实际抓包
  • Go 语言标准库中math模块详细功能介绍与示例
  • GRS认证的主要步骤是什么?
  • Go 语言标准库中strings和strconv详细功能介绍与示例
  • Java基础 3.28
  • K8s Calico网络介绍
  • Z字形变换
  • UI产品经理基础(六):如何解决用户的质疑?
  • Zabbix技术系列文章,第一篇——基础入门
  • 全链路压测:性能测试的流量录制和回放
  • 人工智能之数学基础:矩阵的相似变换
  • C++调用Python
  • JUC 03
  • 智慧医院整体规划设计方案
  • javaWeb vue的简单语法
  • 做网站网页的工作怎么样/网站建设的意义和作用
  • 公司网站制作平台/友情链接模板
  • 域名有了怎么建网站/营销活动策划
  • wap网站是什么意思/个人推广网站
  • 珠海网站建设哪个好薇/西安网站推广助理
  • seo怎么给网站做外链/微信朋友圈广告如何投放