欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 科技 > 名人名企 > Canvas渲染管线解析:从API调用到像素落地的全过程

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

2025/4/1 2:30:49 来源:https://blog.csdn.net/mutuyxy/article/details/146571252  浏览:    关键词: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');// 初始化Canvasfunction 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和putImageDataconst 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) {// 创建对象URLlink.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>

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词