文章目录
- 一、认识Canvas:定义与核心特性
- 1.1 什么是Canvas?
- 1.2 核心特性与应用场景
- 二、基础环境搭建:从标签到上下文
- 2.1 Canvas标签基础
- 2.2 获取绘图上下文
- 三、核心绘图API:图形与路径
- 3.1 基础图形绘制
- 3.2 路径绘制(核心)
- 四、样式与颜色:让图形更生动
- 4.1 基础样式设置
- 4.2 渐变效果
- 4.3 图案填充
- 五、文本与图像操作:丰富画布内容
- 5.1 文本绘制
- 5.2 图像绘制与处理
- 六、状态管理与变换:复杂绘图必备
- 6.1 状态保存与恢复
- 6.2 图形变换
- 七、实战案例:时钟与简易画板
- 7.1 案例1:简易时钟
- 7.2 案例2:简易画板
- 八、常见问题与避坑技巧
- 8.1 图形模糊问题
- 8.2 路径绘制异常
- 8.3 图像跨域问题
- 九、核心知识点总结
一、认识Canvas:定义与核心特性
1.1 什么是Canvas?
Canvas是HTML5引入的图形绘制标签,其本质是一个“像素容器”——它本身不具备绘图能力,所有绘制操作都需通过JavaScript脚本完成。浏览器会为Canvas创建一个位图(Bitmap),开发者通过操作像素来实现各种图形效果,这与SVG的矢量图形有本质区别。
1.2 核心特性与应用场景
| 核心特性 | 应用场景 |
|---|---|
| 像素级控制 | 图像处理、滤镜效果 |
| 即时模式渲染 | 游戏开发、动画效果 |
| 轻量高效 | 数据可视化(图表) |
| 支持图像/文本混合 | 分享海报生成、水印添加 |
| 典型案例:网易云音乐歌词海报、ECharts图表、在线绘图工具等,均以Canvas为核心技术支撑。 |
二、基础环境搭建:从标签到上下文
2.1 Canvas标签基础
Canvas标签的使用需注意两个核心要点:虚拟画布尺寸(attribute属性)和可视区域尺寸(style样式),二者混淆是初学者最常见的错误。
<!-- 基础Canvas标签结构 --><canvasid="myCanvas"width="600"height="300"style="border:1px solid #ccc;width:300px;height:150px;">您的浏览器不支持HTML5 Canvas,请升级浏览器。</canvas>关键区别:
width/height属性:定义虚拟画布的实际像素尺寸(绘图区域大小),右键下载Canvas图像时,尺寸与此一致;
style样式:仅控制Canvas在页面中的显示大小,若与属性尺寸不一致,会导致图像拉伸变形。
2.2 获取绘图上下文
绘图的核心是获取Canvas的2D上下文对象(CanvasRenderingContext2D),所有绘图API都通过该对象调用。
// 1. 获取Canvas元素constcanvas=document.getElementById('myCanvas');// 2. 检查浏览器支持性if(!canvas.getContext){alert('浏览器不支持Canvas 2D上下文');return;}// 3. 获取2D绘图上下文(核心对象)constctx=canvas.getContext('2d');// 此时可通过ctx调用各类绘图方法注意:getContext(‘2d’)是目前唯一强制支持的上下文类型,调用时需注意大小写敏感。
三、核心绘图API:图形与路径
3.1 基础图形绘制
Canvas提供了矩形的原生绘制方法,其他图形需通过路径实现。
// 1. 填充矩形(实心):fillRect(x, y, width, height)ctx.fillStyle='skyblue';// 设置填充色ctx.fillRect(20,20,100,80);// 左上角(20,20),宽100,高80// 2. 描边矩形(空心):strokeRect(x, y, width, height)ctx.strokeStyle='red';// 设置描边色ctx.lineWidth=2;// 设置线宽ctx.strokeRect(150,20,100,80);// 3. 清除矩形区域:clearRect(x, y, width, height)ctx.clearRect(40,40,60,40);// 清除中间部分,形成“挖空”效果运行效果:两个矩形,第一个中间被清除出空白区域。
3.2 路径绘制(核心)
路径是绘制复杂图形(圆形、多边形等)的基础,核心流程为:beginPath() → 定义路径 → stroke()/fill()。
// 案例1:绘制实心圆形ctx.beginPath();// 开始新路径(避免与之前路径关联)// arc(x, y, 半径, 起始角度, 结束角度, 逆时针方向)ctx.arc(300,100,50,0,Math.PI*2,false);// 0到2π为完整圆形ctx.fillStyle='orange';ctx.fill();// 填充路径// 案例2:绘制三角形ctx.beginPath();ctx.moveTo(400,50);// 移动起点到(400,50)ctx.lineTo(480,150);// 绘制到(480,150)ctx.lineTo(320,150);// 绘制到(320,150)ctx.closePath();// 闭合路径(连接起点与终点)ctx.strokeStyle='green';ctx.stroke();// 描边路径关键API说明:
beginPath():重置路径状态,必须在绘制新路径前调用;moveTo(x,y):移动画笔位置,不绘制线条;closePath():自动连接起点与终点,可选但推荐使用。
四、样式与颜色:让图形更生动
4.1 基础样式设置
// 1. 线条样式ctx.lineWidth=5;// 线宽ctx.lineCap='round';// 线帽:butt(默认)/round/squarectx.lineJoin='bevel';// 线交点:miter(默认)/round/bevel// 2. 绘制带样式的线条ctx.beginPath();ctx.moveTo(50,200);ctx.lineTo(250,200);ctx.strokeStyle='purple';ctx.stroke();4.2 渐变效果
Canvas支持线性渐变和径向渐变,需先创建渐变对象再赋值给fillStyle/strokeStyle。
// 1. 线性渐变:createLinearGradient(x0, y0, x1, y1)constlinearGrad=ctx.createLinearGradient(300,180,450,220);// 渐变方向linearGrad.addColorStop(0,'pink');// 起点颜色linearGrad.addColorStop(1,'red');// 终点颜色ctx.fillStyle=linearGrad;ctx.fillRect(300,180,150,40);// 2. 径向渐变:createRadialGradient(x0, y0, r0, x1, y1, r1)constradialGrad=ctx.createRadialGradient(550,200,10,550,200,50);radialGrad.addColorStop(0,'yellow');radialGrad.addColorStop(1,'orange');ctx.beginPath();ctx.arc(550,200,50,0,Math.PI*2);ctx.fill();4.3 图案填充
可将图片或其他Canvas作为图案填充到图形中。
// 1. 创建图片对象constimg=newImage();img.src='https://www.w3school.com.cn/i/eg_flower.png';// 示例图片img.onload=function(){// 2. 创建图案:createPattern(图像, 重复方式)constpattern=ctx.createPattern(img,'repeat');// repeat/repeat-x/repeat-y/no-repeatctx.fillStyle=pattern;ctx.fillRect(50,250,200,100);};五、文本与图像操作:丰富画布内容
5.1 文本绘制
支持填充文本和描边文本,可设置字体、对齐方式等样式。
// 1. 填充文本ctx.font='bold 24px 微软雅黑';// 字体样式:粗细 大小 字体ctx.fillStyle='black';ctx.textAlign='left';// 文本对齐方式ctx.fillText('Canvas文本示例',280,280);// 文本内容,x坐标,y坐标// 2. 描边文本ctx.font='18px 宋体';ctx.strokeStyle='blue';ctx.strokeText('描边文本效果',280,320);// 3. 测量文本宽度(实用)consttextWidth=ctx.measureText('Canvas文本示例').width;ctx.fillText(`上文宽度:${textWidth}px`,280,350);5.2 图像绘制与处理
通过drawImage()方法可将图片绘制到Canvas中,支持缩放和裁剪。
constimg=newImage();img.src='https://www.w3school.com.cn/i/eg_flower.png';img.onload=function(){// 1. 完整绘制图片:drawImage(img, x, y)ctx.drawImage(img,400,250);// 2. 缩放绘制:drawImage(img, x, y, width, height)ctx.drawImage(img,500,250,60,60);// 缩放到60x60// 3. 裁剪绘制:drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh)// 从图片(sx,sy)位置裁剪sw×sh区域,绘制到画布(dx,dy)位置,缩放到dw×dhctx.drawImage(img,10,10,50,50,400,320,80,80);};注意:图片绘制必须在onload回调中执行,确保图片加载完成。
六、状态管理与变换:复杂绘图必备
6.1 状态保存与恢复
Canvas上下文的状态(样式、变换等)可通过栈结构保存和恢复,适用于复杂绘图场景。
// 1. 保存当前状态(样式:红色、线宽2)ctx.save();ctx.strokeStyle='red';ctx.lineWidth=2;ctx.strokeRect(50,400,80,60);// 2. 恢复到之前保存的状态ctx.restore();// 此时绘制的矩形使用默认样式ctx.strokeRect(150,400,80,60);运行效果:第一个矩形为红色线宽2,第二个为默认黑色线宽1。
6.2 图形变换
通过平移、旋转、缩放等变换,可简化复杂图形的绘制逻辑。
// 案例:旋转矩形ctx.save();// 保存原始状态// 1. 平移原点到矩形中心(旋转中心)ctx.translate(300,430);// 2. 旋转30度(弧度制:Math.PI/180 * 角度)ctx.rotate(Math.PI/180*30);// 3. 绘制矩形(此时原点在中心,需调整坐标)ctx.fillStyle='rgba(0, 128, 0, 0.5)';ctx.fillRect(-40,-30,80,60);// 左上角(-40,-30),确保中心在原点ctx.restore();// 恢复状态,不影响后续绘制// 案例:缩放图形ctx.save();ctx.scale(1.5,0.8);// x轴放大1.5倍,y轴缩小0.8倍ctx.fillStyle='rgba(255, 0, 0, 0.5)';ctx.fillRect(400,400,80,60);ctx.restore();七、实战案例:时钟与简易画板
7.1 案例1:简易时钟
综合运用路径、文本、变换和定时器实现动态时钟。
functiondrawClock(){// 1. 清除画布ctx.clearRect(0,0,canvas.width,canvas.height);// 2. 绘制表盘ctx.beginPath();ctx.arc(300,200,150,0,Math.PI*2);ctx.fillStyle='white';ctx.fill();ctx.strokeStyle='black';ctx.lineWidth=5;ctx.stroke();// 3. 绘制刻度(12个大刻度)for(leti=0;i<12;i++){ctx.save();ctx.translate(300,200);ctx.rotate(Math.PI/6*i);// 每小时30度ctx.beginPath();ctx.moveTo(0,-130);ctx.lineTo(0,-140);ctx.lineWidth=3;ctx.stroke();ctx.restore();}// 4. 获取当前时间constnow=newDate();consthours=now.getHours()%12;constminutes=now.getMinutes();constseconds=now.getSeconds();// 5. 绘制时针(30度/小时 + 0.5度/分钟)ctx.save();ctx.translate(300,200);consthourAngle=Math.PI/6*hours+Math.PI/180*0.5*minutes;ctx.rotate(hourAngle);ctx.lineWidth=6;ctx.lineCap='round';ctx.beginPath();ctx.moveTo(0,20);ctx.lineTo(0,-80);ctx.stroke();ctx.restore();// 6. 绘制分针(6度/分钟 + 0.1度/秒)// (分针和秒针绘制逻辑类似,省略,可自行补充)// 7. 绘制秒针// ...// 8. 绘制中心圆点ctx.beginPath();ctx.arc(300,200,8,0,Math.PI*2);ctx.fill();}// 初始化绘制并设置定时器drawClock();setInterval(drawClock,1000);7.2 案例2:简易画板
结合鼠标事件实现自由绘图功能。
letisDrawing=false;// 绘图状态标记// 鼠标按下:开始绘图canvas.addEventListener('mousedown',(e)=>{isDrawing=true;// 获取鼠标在Canvas中的坐标(需处理偏移)constpos=getCanvasPos(e);ctx.beginPath();ctx.moveTo(pos.x,pos.y);ctx.lineCap='round';// 线条圆润ctx.lineWidth=5;ctx.strokeStyle='black';});// 鼠标移动:绘制线条canvas.addEventListener('mousemove',(e)=>{if(!isDrawing)return;constpos=getCanvasPos(e);ctx.lineTo(pos.x,pos.y);ctx.stroke();});// 鼠标松开/离开:结束绘图canvas.addEventListener('mouseup',()=>isDrawing=false);canvas.addEventListener('mouseleave',()=>isDrawing=false);// 工具函数:获取鼠标在Canvas中的相对坐标functiongetCanvasPos(e){constrect=canvas.getBoundingClientRect();return{x:e.clientX-rect.left,y:e.clientY-rect.top};}八、常见问题与避坑技巧
8.1 图形模糊问题
原因:Canvas虚拟尺寸与显示尺寸不一致,或未适配设备像素比。
// 解决方案:适配设备像素比functioninitCanvas(){constdpr=window.devicePixelRatio||1;// 设备像素比constrect=canvas.getBoundingClientRect();// 设置虚拟尺寸为显示尺寸 * 像素比canvas.width=rect.width*dpr;canvas.height=rect.height*dpr;// 缩放上下文,确保绘制比例正确ctx.scale(dpr,dpr);// 重新设置样式尺寸canvas.style.width=`${rect.width}px`;canvas.style.height=`${rect.height}px`;}8.2 路径绘制异常
问题:线条重复绘制或样式混乱。
解决方案:beginPath()必须在每次绘制新路径前调用,避免与之前的路径状态关联。
8.3 图像跨域问题
问题:绘制跨域图片后,调用toDataURL()会报错。
解决方案:1. 服务器设置CORS响应头;2. 图片添加crossOrigin属性。
constimg=newImage();img.crossOrigin='anonymous';// 启用跨域img.src='https://example.com/image.png';九、核心知识点总结
核心流程:获取Canvas元素 → 获取2D上下文 → 调用绘图API → 渲染图形;
关键概念:区分虚拟画布尺寸(attribute)与可视尺寸(style),路径绘制需用beginPath()重置;
常用API:fillRect/strokeRect(矩形)、arc(圆形)、moveTo/lineTo(路径)、drawImage(图像);
进阶技巧:利用save()/restore()管理状态,通过变换简化复杂绘图,适配设备像素比解决模糊问题。
Canvas的学习核心在于实践,建议从基础图形开始,逐步尝试动画和交互效果,结合实际场景加深理解。