# 04-Canvas与SVG ## 📋 学习目标 - 掌握Canvas 2D绘图API - 理解SVG矢量图形 - 学习Canvas与SVG的应用场景 - 实现图表和交互式图形 ## 🎨 Canvas基础 ### Canvas元素 ```html 您的浏览器不支持Canvas ``` **要点**: - width和height属性设置画布尺寸(默认300x150) - 不要用CSS设置尺寸,会导致缩放失真 - 使用getContext('2d')获取2D渲染上下文 ### 绘制基本形状 #### 矩形 ```javascript const ctx = canvas.getContext('2d'); // 填充矩形 ctx.fillStyle = '#ff6b6b'; ctx.fillRect(10, 10, 100, 100); // 描边矩形 ctx.strokeStyle = '#339af0'; ctx.lineWidth = 2; ctx.strokeRect(120, 10, 100, 100); // 清除矩形 ctx.clearRect(50, 50, 20, 20); ``` #### 路径绘制 ```javascript // 三角形 ctx.beginPath(); ctx.moveTo(250, 10); ctx.lineTo(300, 110); ctx.lineTo(200, 110); ctx.closePath(); ctx.fillStyle = '#51cf66'; ctx.fill(); ctx.strokeStyle = '#2f9e44'; ctx.stroke(); // 圆形 ctx.beginPath(); ctx.arc(400, 60, 50, 0, Math.PI * 2); ctx.fillStyle = '#ffd43b'; ctx.fill(); // 圆弧 ctx.beginPath(); ctx.arc(500, 60, 50, 0, Math.PI, false); ctx.strokeStyle = '#ff6b6b'; ctx.lineWidth = 3; ctx.stroke(); // 贝塞尔曲线 ctx.beginPath(); ctx.moveTo(600, 10); ctx.quadraticCurveTo(650, 10, 650, 60); // 二次贝塞尔 ctx.bezierCurveTo(650, 110, 600, 110, 600, 60); // 三次贝塞尔 ctx.stroke(); ``` ### 样式和颜色 #### 颜色设置 ```javascript // 填充颜色 ctx.fillStyle = '#ff6b6b'; ctx.fillStyle = 'rgb(255, 107, 107)'; ctx.fillStyle = 'rgba(255, 107, 107, 0.5)'; // 描边颜色 ctx.strokeStyle = '#339af0'; // 渐变 const gradient = ctx.createLinearGradient(0, 0, 200, 0); gradient.addColorStop(0, '#ff6b6b'); gradient.addColorStop(1, '#339af0'); ctx.fillStyle = gradient; ctx.fillRect(0, 0, 200, 100); // 径向渐变 const radialGradient = ctx.createRadialGradient(100, 100, 10, 100, 100, 50); radialGradient.addColorStop(0, '#fff'); radialGradient.addColorStop(1, '#000'); ctx.fillStyle = radialGradient; ctx.fillRect(50, 50, 100, 100); // 图案 const img = new Image(); img.src = 'pattern.png'; img.onload = () => { const pattern = ctx.createPattern(img, 'repeat'); ctx.fillStyle = pattern; ctx.fillRect(0, 0, 200, 200); }; ``` #### 线条样式 ```javascript ctx.lineWidth = 5; ctx.lineCap = 'round'; // butt, round, square ctx.lineJoin = 'round'; // miter, round, bevel ctx.setLineDash([5, 10]); // 虚线 ctx.lineDashOffset = 0; ``` ### 文本绘制 ```javascript // 填充文本 ctx.font = '30px Arial'; ctx.fillStyle = '#000'; ctx.fillText('Hello Canvas', 10, 50); // 描边文本 ctx.strokeStyle = '#ff6b6b'; ctx.lineWidth = 2; ctx.strokeText('Hello Canvas', 10, 100); // 文本对齐 ctx.textAlign = 'left'; // left, right, center, start, end ctx.textBaseline = 'top'; // top, middle, bottom, alphabetic, hanging // 测量文本 const metrics = ctx.measureText('Hello'); console.log(metrics.width); // 文本宽度 ``` ### 图像处理 ```javascript const img = new Image(); img.src = 'photo.jpg'; img.onload = () => { // 绘制图像 ctx.drawImage(img, 0, 0); // 缩放 ctx.drawImage(img, 0, 0, 200, 150); // 裁剪和绘制 ctx.drawImage( img, 50, 50, 100, 100, // 源图像裁剪区域 0, 0, 200, 200 // 目标画布区域 ); }; // 像素操作 const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const pixels = imageData.data; // [r, g, b, a, r, g, b, a, ...] // 灰度化 for (let i = 0; i < pixels.length; i += 4) { const avg = (pixels[i] + pixels[i + 1] + pixels[i + 2]) / 3; pixels[i] = avg; // R pixels[i + 1] = avg; // G pixels[i + 2] = avg; // B } ctx.putImageData(imageData, 0, 0); ``` ### 变换 #### 基本变换 ```javascript // 平移 ctx.translate(100, 100); // 旋转(弧度) ctx.rotate(Math.PI / 4); // 缩放 ctx.scale(2, 2); // 重置变换 ctx.setTransform(1, 0, 0, 1, 0, 0); // 矩阵变换 ctx.transform(1, 0, 0, 1, 0, 0); ``` #### 保存和恢复状态 ```javascript ctx.fillStyle = '#ff6b6b'; ctx.save(); // 保存当前状态 ctx.fillStyle = '#339af0'; ctx.fillRect(0, 0, 100, 100); ctx.restore(); // 恢复之前的状态 ctx.fillRect(120, 0, 100, 100); // 使用红色 ``` ### 动画 #### 基本动画循环 ```javascript let x = 0; function animate() { // 清除画布 ctx.clearRect(0, 0, canvas.width, canvas.height); // 绘制 ctx.fillStyle = '#ff6b6b'; ctx.fillRect(x, 100, 50, 50); // 更新位置 x += 2; if (x > canvas.width) x = -50; // 下一帧 requestAnimationFrame(animate); } animate(); ``` #### 小球弹跳动画 ```javascript const ball = { x: 100, y: 100, vx: 5, vy: 2, radius: 20, color: '#ff6b6b' }; function drawBall() { ctx.beginPath(); ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2); ctx.fillStyle = ball.color; ctx.fill(); ctx.closePath(); } function update() { ctx.clearRect(0, 0, canvas.width, canvas.height); drawBall(); // 更新位置 ball.x += ball.vx; ball.y += ball.vy; // 边界检测 if (ball.x + ball.radius > canvas.width || ball.x - ball.radius < 0) { ball.vx = -ball.vx; } if (ball.y + ball.radius > canvas.height || ball.y - ball.radius < 0) { ball.vy = -ball.vy; } requestAnimationFrame(update); } update(); ``` ## 📐 SVG基础 ### SVG元素 ```html SVG图片
``` ### 基本形状 #### 矩形 ```html ``` #### 圆形和椭圆 ```html ``` #### 线条和多边形 ```html ``` #### 路径 ```html ``` **路径命令**: - M/m: 移动到 (moveto) - L/l: 直线到 (lineto) - H/h: 水平线 (horizontal) - V/v: 垂直线 (vertical) - C/c: 三次贝塞尔曲线 - Q/q: 二次贝塞尔曲线 - A/a: 圆弧 - Z/z: 闭合路径 ### SVG样式 #### 填充和描边 ```html ``` #### 渐变 ```html ``` #### 滤镜 ```html ``` ### SVG动画 #### CSS动画 ```html ``` #### SMIL动画 ```html ``` ## 🎯 实战案例 ### Canvas图表示例 ```javascript // 柱状图 function drawBarChart(data) { const canvas = document.getElementById('chart'); const ctx = canvas.getContext('2d'); const width = canvas.width; const height = canvas.height; const maxValue = Math.max(...data.map(d => d.value)); const barWidth = width / data.length - 20; const scale = (height - 40) / maxValue; ctx.clearRect(0, 0, width, height); data.forEach((item, index) => { const barHeight = item.value * scale; const x = index * (barWidth + 20) + 10; const y = height - barHeight - 20; // 绘制柱子 ctx.fillStyle = '#339af0'; ctx.fillRect(x, y, barWidth, barHeight); // 绘制标签 ctx.fillStyle = '#000'; ctx.font = '12px Arial'; ctx.textAlign = 'center'; ctx.fillText(item.label, x + barWidth / 2, height - 5); // 绘制数值 ctx.fillText(item.value, x + barWidth / 2, y - 5); }); } // 使用 const chartData = [ { label: '一月', value: 120 }, { label: '二月', value: 80 }, { label: '三月', value: 150 }, { label: '四月', value: 100 } ]; drawBarChart(chartData); ``` ### SVG交互图标 ```html ``` ## ⚖️ Canvas vs SVG对比 ### Canvas优势 - 像素级操作 - 高性能动画 - 适合大量对象 - 游戏开发 - 图像处理 ### SVG优势 - 矢量图形,无损缩放 - 可通过CSS和JavaScript操作 - 可访问性更好 - 文件体积小 - 适合图表和图标 ### 选择建议 ``` Canvas适用于: - 实时游戏渲染 - 复杂动画效果 - 像素级图像处理 - 大量对象(>1000) SVG适用于: - 图标和Logo - 数据可视化 - 需要交互的图形 - 响应式图形 ``` ## 📚 实践练习 ### 练习1:Canvas时钟 实现一个实时时钟,包含: - 表盘、刻度、指针 - 时分秒实时更新 - 平滑动画效果 ### 练习2:SVG图表 创建一个SVG饼图,要求: - 数据驱动 - 鼠标悬停提示 - 动画过渡效果 ### 练习3:Canvas画板 实现一个简单的画板应用: - 自由绘制 - 颜色选择 - 线条粗细 - 橡皮擦和清空 ## 📚 参考资料 - [MDN Canvas教程](https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API) - [MDN SVG教程](https://developer.mozilla.org/zh-CN/docs/Web/SVG) - [SVG规范](https://www.w3.org/TR/SVG2/)