一、Canvas简介
1.1 什么是canvas
Canvas(画布)是在HTML5中新增的标签用于在网页实时生成图像,可以操作图像内容,是一个可以用JavaScript操作的位图(bitmap)。
1.2 canvas的坐标系统
canvas的坐标系统如下图所示,其具有如下特点:
- x轴正方向向右、y轴正方向向下
- 画布的原点在左上角
- 横纵坐标单位为像素
- 每个轴的最小单元为一个像素(栅格)
1.3 canvas的绘制流程
- 创建一个标签
- 获取canvas元素对应的DOM对象,这是一个Canvas对象
- 调用Canvas对象的getContext()方法,该方法返回一个CanvasRenderingContext2D对象,该对象即可绘制图形
- 调用CanvasRenderingContext2D对象的方法绘图
1.4 canvas的应用领域
canvas这个神奇的东西有很多领域可以得到应用,下面我们一起唠一唠。
- 游戏:canvas 在基于 Web 的图像显示方面比 Flash 更加立体、更加精巧,canvas 游戏在流畅度和跨平台方面更优秀,例如这25款canvas游戏
- 可视化的库:Echart
- banner广告:Canvas 实现动态的广告效果非常合适
- 图形编辑器:后续Photoshop能够100%基于Web实现
- 微信读书、腾讯文档均是通过canvas实现
二、基础功能
通过第一章对canvas有了初步的认识,本章就按照一个人绘制一幅画的思路进行演化,逐步了解canvas的基本功能,从而更好的使用它实现一些酷炫的效果。
2.1 坐标系选择
当要绘制一幅画时首先要确定坐标系,只有选择好了坐标系之后才好动笔,在canvas中默认坐标系在左上角(X轴正方向向右、Y轴正方向向下),可是有的时候需要变换坐标系才能更方便的实现所需的效果,此时需要变换坐标系,canvas提供了以下几种变换坐标系的方式:
- translate(dx,dy):平移坐标系。相当于把原来位于(0,0)位置的坐标原点平移到(dx、dy)点。
- rotate(angle):旋转坐标系。该方法控制坐标系统顺时针旋转angle弧度。
- scale(sx,sy):缩放坐标系。该方法控制坐标系统水平方向上缩放sx,垂直方向上缩放sy。
- transform(a,b,c,d,e,f):允许缩放、旋转、移动并倾斜当前的环境坐标系,其中a表示水平缩放、b表示水平切斜、c表示垂直切斜、d表示垂直缩放、e表示水平移动、f表示垂直移动。
- function main() {
- const canvas = document.getElementById('canvasId');
- const ctx = canvas.getContext('2d');
- ctx.lineWidth = 4;
- // 默认
- ctx.save();
- ctx.strokeStyle = '#F00';
- drawCoordiante(ctx);
- ctx.restore();
- // 平移
- ctx.save();
- ctx.translate(150, 150);
- ctx.strokeStyle = '#0F0';
- drawCoordiante(ctx);
- ctx.restore();
- // 旋转
- ctx.save();
- ctx.translate(300, 300);
- ctx.rotate(-Math.PI / 2);
- ctx.strokeStyle = '#00F';
- drawCoordiante(ctx);
- ctx.restore();
- // 缩放
- ctx.save();
- ctx.translate(400, 400);
- ctx.rotate(-Math.PI / 2);
- ctx.scale(0.5, 0.5);
- ctx.strokeStyle = '#000';
- drawCoordiante(ctx);
- ctx.restore();
- }
- function drawCoordiante(ctx) {
- ctx.beginPath();
- ctx.moveTo(0, 0);
- ctx.lineTo(120, 0);
- ctx.moveTo(0, 0);
- ctx.lineTo(0, 80);
- ctx.closePath();
- ctx.stroke();
- }
- main();
2.2 图形绘制
坐标系选择好了之后,就要开始动笔创作大作了,那么canvas到底允许绘制哪些内容呢?
直线
- function drawLine(ctx, startX, startY, endX, endY) {
- ctx.moveTo(startX, startY);
- ctx.lineTo(endX, endY);
- ctx.stroke();
- }
圆弧
- function drawCircle(ctx, x, y, R, startAngle, endAngle) {
- ctx.arc(x, y, R, startAngle, endAngle);
- ctx.stroke();
- }
曲线
- // 贝济埃曲线
- function drawBezierCurve(ctx, cpX1, cpY1, cpX, cpY2, endX, endY) {
- ctx.bezierCurveTo(cpX1, cpY1, cpX, cpY2, endX, endY);
- ctx.stroke();
- }
- // 二次曲线
- function drawQuadraticCurve(ctx, cpX, cpY, endX, endY) {
- ctx.quadraticCurveTo(cpX, cpY, endX, endY);
- ctx.stroke();
- }
矩形
- // 填充矩形
- function drawFillRect(ctx, x, y, width, height) {
- ctx.fillRect(x, y, width, height);
- }
- // 边框矩形
- function drawStrokeRect(ctx, x, y, width, height) {
- ctx.strokeRect( x, y, width, height);
- }
字符串
- / 填充字符串
- function drawFillText(ctx, text, x, y) {
- ctx.fillText(text, x, y);
- }
- // 边框字符串
- function drawStrokeText(ctx, text, x, y) {
- ctx.strokeText(text, x, y);
- }
复杂图形绘制——路径
- // 利用路径绘制
- function drawFigureByPath(ctx) {
- ctx.beginPath();
- ctx.moveTo(100, 400);
- ctx.lineTo(200, 450);
- ctx.lineTo(150, 480);
- ctx.closePath();
- ctx.fill();
- }
- function main() {
- const canvas = document.getElementById('canvasId');
- const ctx = canvas.getContext('2d');
- ctx.lineWidth = 2;
- ctx.strokeStyle = '#F00';
- ctx.fillStyle = '#F00';
- ctx.font = 'normal 50px 宋体';
- drawLine(ctx, 50, 10, 150, 10);
- ctx.moveTo(150, 100);
- drawCircle(ctx, 100, 100, 50, 0, Math.PI);
- ctx.moveTo(300, 100);
- drawCircle(ctx, 250, 100, 50, 0, Math.PI * 2);
- ctx.moveTo(350, 150);
- drawBezierCurve(ctx, 200, 200, 450, 250, 300, 300);
- ctx.moveTo(50, 250);
- drawQuadraticCurve(ctx, 50, 400, 80, 400);
- drawFillRect(ctx, 100, 300, 100, 50);
- drawStrokeRect(ctx, 300, 300, 100, 50);
- drawFillText(ctx, 'I', 100, 400);
- drawStrokeText(ctx, 'I', 300, 400);
- drawFigureByPath(ctx);
- }
2.3 填充风格
利用canvas绘制图形时势必要上点颜料,通过设置fillStyle属性即可设置对应的颜料,对于颜料值主要有以下四种:纯颜色、线性渐变颜色、径向渐变颜色、位图。
纯颜色
- function useColorFill(ctx) {
- ctx.save();
- ctx.fillStyle = '#F00';
- ctx.fillRect(10, 10, 100, 100);
- ctx.restore();
- }
线性渐变颜色
- function useLinearGradientFill(ctx) {
- ctx.save();
- const lg = ctx.createLinearGradient(110, 10, 210, 10);
- lg.addColorStop(0.2, '#F00');
- lg.addColorStop(0.5, '#0F0');
- lg.addColorStop(0.9, '#00F');
- ctx.fillStyle = lg;
- ctx.fillRect(120, 10, 100, 100);
- ctx.restore();
- }
径向渐变颜色
- function useRadialGradientFill(ctx) {
- ctx.save();
- const lg = ctx.createRadialGradient(260, 60, 10, 260, 60, 60);
- lg.addColorStop(0.2, '#F00');
- lg.addColorStop(0.5, '#0F0');
- lg.addColorStop(0.9, '#00F');
- ctx.fillStyle = lg;
- ctx.fillRect(230, 10, 100, 100);
- ctx.restore();
- }
位图填充
- function useImageFill(ctx) {
- ctx.save();
- const image = new Image();
- image.src = 'https://dss2.baidu.com/6ONYsjip0QIZ8tyhnq/it/u=442547030,98631113&fm=58';
- image.onload = function () {
- // 创建位图填充
- const imgPattern = ctx.createPattern(image, 'repeat');
- ctx.fillStyle = imgPattern;
- ctx.fillRect(340, 10, 100, 100);
- ctx.restore();
- }
- }
2.4 临时保存
用一只画笔在画某个美女时,忽然来了灵感需要画另一个帅哥,这个时候又不想放弃这个美女,则就需要将当前画美女的颜料、坐标等状态进行暂存,等到画完帅哥后恢复状态进行美女的绘制。在canvas中可以通过save()和restore()方法实现,调用save()方法后将这一时刻的设置放到一个暂存栈中,然后可以放心的修改上下文,在需要绘制之前的上下文时,可以调用restore()方法。
- // 从左往右是依次绘制(中间为用新的样式填充,最后一个是恢复到原来样式填充)
- function main() {
- const canvas = document.getElementById('canvasId');
- const ctx = canvas.getContext('2d');
- ctx.fillStyle = '#F00';
- ctx.fillRect(10, 10, 100, 100);
- ctx.save();
- ctx.fillStyle = '#0F0';
- ctx.fillRect(150, 10, 100, 100);
- ctx.restore();
- ctx.fillRect(290, 10, 100, 100);
- }
2.5 引入外部图像
有的时候需要引入外部图片,然后对外部图片进行像素级别的处理,最后进行保存。
- 绘制图像:drawImage
- 取得图像数据:getIamgeData
- 将修改后的数据重新填充到Canvas中:putImageData
- 输出位图:toDataURL
- function main() {
- const canvas = document.getElementById('canvasId');
- const ctx = canvas.getContext('2d');
- const image = document.getElementById('image');
- // 绘制图像
- ctx.drawImage(image, 0, 0);
- // 获取图像数据
- const imageData = ctx.getImageData(0, 0, image.width, image.height);
- const data = imageData.data;
- for (let i = 0, len = data.length; i < len; i += 4) {
- const red = data[i];
- const green = data[i + 1];
- const blue = data[i + 2];
- const average = Math.floor((red + green + blue) / 3);
- data[i] = average;
- data[i + 1] = average;
- data[i + 2] = average;
- }
- imageData.data = data;
- ctx.putImageData(imageData, 0, 0);
- document.getElementById('result').src = canvas.toDataURL('image/png');
- }
三、猪头实战
学习了这么多,就利用canvas绘制一个猪头吧,毕竟每个程序员身边肯定有一个陪伴自己的小胖猪,嘿嘿。
- function main() {
- const canvas = document.getElementById('canvasId');
- const ctx = canvas.getContext('2d');
- ctx.lineWidth = 4;
- ctx.strokeStyle = '#000';
- ctx.fillStyle = '#ffd8e1';
- ctx.translate(260, 20);
- drawPigEar(ctx);
- ctx.save();
- ctx.rotate(Math.PI / 2);
- drawPigEar(ctx);
- ctx.restore();
- drawPigFace(ctx);
- ctx.save();
- ctx.translate(-100, -100);
- drawPigEye(ctx);
- ctx.restore();
- ctx.save();
- ctx.translate(100, -100);
- drawPigEye(ctx);
- ctx.restore();
- ctx.save();
- ctx.translate(0, 60);
- drawPigNose(ctx);
- ctx.restore();
- }
- function drawPigEar(ctx) {
- ctx.save();
- ctx.beginPath();
- ctx.arc(-250, 0, 250, 0, -Math.PI / 2, true);
- ctx.arc(0, -250, 250, -Math.PI, Math.PI / 2, true);
- ctx.closePath();
- ctx.fill();
- ctx.stroke();
- ctx.restore();
- }
- function drawPigFace(ctx) {
- ctx.save();
- ctx.beginPath();
- ctx.arc(0, 0, 250, 0, Math.PI * 2);
- ctx.fill();
- ctx.stroke();
- ctx.closePath();
- ctx.restore();
- }
- function drawPigEye(ctx) {
- ctx.save();
- ctx.fillStyle = '#000';
- ctx.beginPath();
- ctx.arc(0, 0, 20, 0, Math.PI * 2);
- ctx.closePath();
- ctx.fill();
- ctx.restore();
- }
- function drawPigNose(ctx) {
- ctx.save();
- ctx.fillStyle = '#fca7aa';
- ctx.beginPath();
- ctx.ellipse(0, 0, 150, 100, 0, 0, Math.PI * 2);
- ctx.closePath();
- ctx.fill();
- ctx.stroke();
- ctx.save();
- ctx.translate(-60, 0);
- drawPigNostrils(ctx);
- ctx.restore();
- ctx.save();
- ctx.translate(60, 0);
- drawPigNostrils(ctx);
- ctx.restore();
- ctx.restore();
- }
- function drawPigNostrils(ctx) {
- ctx.save();
- ctx.fillStyle = '#b55151';
- ctx.beginPath();
- ctx.ellipse(0, 0, 40, 60, 0, 0, Math.PI * 2);
- ctx.closePath();
- ctx.fill();
- ctx.stroke();
- ctx.restore();
- }
- main();
本文转载自微信公众号「执鸢者」,可以通过以下二维码关注。转载本文请联系执鸢者公众号。