canvas是一个可以使用脚本(通常为JavaScript)来绘制图形的 HTML 元素.例如,它可以用于绘制图表、制作图片构图或者制作简单的(以及不那么简单的)。像echarts、antv就是使用canvas来绘制图表的
首先要有一个canvas标签存在于html中
<canvas id="charts" width="150" height="150"></canvas>然后在js中获取它的上下文,然后来绘制和处理展示内容
var canvas = document.getElementById('charts'); // 使用 getContext() 方法来检测是否支持canvas if (canvas.getContext){ var ctx = canvas.getContext('2d'); // drawing code here } else { // canvas-unsupported code here }使用canvas能轻松的绘制矩形,三角形,直线,圆弧和曲线,还能自己指定一个点来构成一个路径来绘制,使用fillStyle属性修改填充色,使用strokeStyle修改线的颜色
绘制矩形 fillRect(x, y, width, height) // 绘制一个填充的矩形 // x 矩形起始点的 x 轴坐标 // y 矩形起始点的 y 轴坐标 // width 矩形的宽度 // height 矩形的高度 strokeRect(x, y, width, height) // 绘制一个填充的矩形 clearRect(x, y, width, height) // 清除指定矩形区域,让清除部分完全透明 // 绘制矩形 ctx.fillRect(25, 25, 100, 100); ctx.clearRect(45, 45, 60, 60); ctx.strokeRect(50, 50, 50, 50); 绘制路径(1)首先,你需要创建路径起始点。(2)然后你使用画图命令去画出路径。(3)之后你把路径封闭。(4)一旦路径生成,你就能通过描边或填充路径区域来渲染图形。
// beginPath() 新建一条路径,生成之后,图形绘制命令被指向到路径上生成路径 // closePath() 闭合路径之后图形绘制命令又重新指向到上下文中 // stroke() 通过线条来绘制图形轮廓 // fill() 通过填充路径的内容区域生成实心的图形 // 绘制三角形 ctx.beginPath() ctx.moveTo(75, 50); ctx.lineTo(100, 75); ctx.lineTo(100, 25); ctx.lineTo(75, 50); ctx.stroke() ctx.closePath() 使用路径绘制笑脸canvas就是一块画布,我们可以移动画笔会绘制图案,和ps的路径相似,完成路径后可以选择是填充还是描边,使用moveTo()来移动画笔,使用lineTo()来连线
arc(x,y,r,start,stop,bloo) // 绘制圆 // x 相当于margin-left // y 相当于margin-top // r 圆的半径 // start 从圆上的起点 // stop 圆的终点 // bloo 为false顺时针,为true逆时针 // 绘制笑脸 ctx.beginPath() ctx.arc(75, 75, 50, 0, Math.PI*2, true) ctx.moveTo(110, 75) ctx.arc(75, 75, 35, 0, Math.PI, false) // false为顺时针 ctx.moveTo(65, 65) ctx.arc(60, 65, 5, 0, Math.PI*2, true) ctx.moveTo(95,65) ctx.arc(90, 65, 5, 0, Math.PI*2, true) ctx.stroke() 绘制太极 // 绘制整个的圆 ctx.beginPath() ctx.arc(100, 100, 60, 0, Math.PI*2, true) ctx.stroke() ctx.closePath() // 绘制下半圆 ctx.beginPath() ctx.arc(100, 100, 60, 0, Math.PI, false) ctx.closePath() ctx.fillStyle = '#000' ctx.fill() // 绘制上半圆右侧 ctx.beginPath() ctx.arc(130, 100, 30, 0, Math.PI, true) ctx.closePath() ctx.fill() // 绘制小圆点 ctx.beginPath() ctx.arc(130, 100, 5, 0, Math.PI*2, true) ctx.closePath() ctx.fillStyle = '#fff' ctx.fill() // 绘制下半圆左侧 ctx.beginPath() ctx.arc(70, 100, 30, 0, Math.PI, false) ctx.closePath() ctx.fill() // 绘制小圆点 ctx.beginPath() ctx.arc(70, 100, 5, 0, Math.PI*2, true) ctx.closePath() ctx.fillStyle = '#000' ctx.fill() 二次贝塞尔曲线quadraticCurveTo(cp1x, cp1y, x, y)绘制二次贝塞尔曲线,cp1x,cp1y为一个控制点,x,y为结束点。bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)绘制三次贝塞尔曲线,cp1x,cp1y为控制点一,cp2x,cp2y为控制点二,x,y为结束点。
// 二次贝塞尔曲线 ctx.beginPath(); ctx.moveTo(75,25); ctx.quadraticCurveTo(25,25,25,62.5); ctx.quadraticCurveTo(25,100,50,100); ctx.quadraticCurveTo(50,120,30,125); ctx.quadraticCurveTo(60,120,65,100); ctx.quadraticCurveTo(125,100,125,62.5); ctx.quadraticCurveTo(125,25,75,25); ctx.stroke(); //三次贝塞尔曲线 ctx.beginPath(); ctx.moveTo(75,40); ctx.bezierCurveTo(75,37,70,25,50,25); ctx.bezierCurveTo(20,25,20,62.5,20,62.5); ctx.bezierCurveTo(20,80,40,102,75,120); ctx.bezierCurveTo(110,102,130,80,130,62.5); ctx.bezierCurveTo(130,62.5,130,25,100,25); ctx.bezierCurveTo(85,25,75,37,75,40); ctx.fill();通过设置 globalAlpha 属性或者使用一个半透明颜色作为轮廓或填充的样式
ctx.fillStyle = '#FD0'; ctx.fillRect(0,0,75,75); ctx.fillStyle = '#6C0'; ctx.fillRect(75,0,75,75); ctx.fillStyle = '#09F'; ctx.fillRect(0,75,75,75); ctx.fillStyle = '#F30'; ctx.fillRect(75,75,75,75); ctx.fillStyle = '#FFF'; // 设置透明度值 ctx.globalAlpha = 0.2; // 画半透明圆 for (var i=0;i<7;i++){ ctx.beginPath(); ctx.arc(75,75,10+10*i,0,Math.PI*2,true); ctx.fill(); } 线型 Line styles /* lineWidth = value 设置线条宽度。 lineCap = type 'butt','round','square' 设置线条末端样式。 lineJoin = type 'round', 'bevel', 'miter' 设定线条与线条间接合处的样式。 miterLimit = value 限制当两条线相交时交接处最大长度;所谓交接处长度(斜接长度)是指线条交接处内角顶点到外角顶点的长度。 getLineDash() 返回一个包含当前虚线样式,长度为非负偶数的数组。 setLineDash(segments) 设置当前虚线样式。 lineDashOffset = value 设置虚线样式的起始偏移量 */ 渐变使用 createLinearGradient(x1, y1, x2, y2)(线性渐变)、createRadialGradient(x1, y1, r1, x2, y2, r2)(径向渐变) 方法创建渐变。createLinearGradient 方法接受 4 个参数,表示渐变的起点 (x1,y1) 与终点 (x2,y2)。createRadialGradient 方法接受 6 个参数,前三个定义一个以 (x1,y1) 为原点,半径为 r1 的圆,后三个参数则定义另一个以 (x2,y2) 为原点,半径为 r2 的圆。
创建出 canvasGradient 对象后,我们就可以用 addColorStop 方法给它上色了,addColorStop 方法接受 2 个参数,position 参数必须是一个 0.0 与 1.0 之间的数值,表示渐变中颜色所在的相对位置。例如,0.5 表示颜色会出现在正中间。color 参数必须是一个有效的 CSS 颜色值(如 #FFF, rgba(0,0,0,1),等等)。strokeStyle 和 fillStyle 属性都可以接受 canvasGradient 对象。
var lineargradient = ctx.createLinearGradient(0,0,150,150); var radialgradient = ctx.createRadialGradient(75,75,0,75,75,100); lineargradient.addColorStop(0,'white'); lineargradient.addColorStop(1,'black'); ctx.fillStyle = lineargradient ctx.fillRect(0,0,80,60) ctx.beginPath() ctx.arc(40,30,26,0,Math.PI*2,true) lineargradient.addColorStop(0.3, '#00bcd4') lineargradient.addColorStop(0.2, '#dd5866') ctx.strokeStyle = lineargradient ctx.stroke() ctx.closePath()使用径向渐变
var radialGradient = ctx.createRadialGradient(75,75,0,75,75,100) ctx.arc(75,75,75,0,Math.PI*2) radialGradient.addColorStop(0, '#00C9FF') radialGradient.addColorStop(0.3, '#E4C700') radialGradient.addColorStop(0.5, '#00bcd4') radialGradient.addColorStop(0.8, '#00B5E2') ctx.fillStyle = radialGradient ctx.fill() 图案样式 PatternscreatePattern(image, type)该方法接受两个参数。Image 可以是一个 Image 对象的引用,或者另一个 canvas 对象。Type 必须是下面的字符串值之一:repeat,repeat-x,repeat-y 和 no-repeat
var img = new Image(); img.src = 'https://mdn.mozillademos.org/files/222/Canvas_createpattern.png'; img.onload = function() { // 创建图案 var ptrn = ctx.createPattern(img, 'repeat'); ctx.fillStyle = ptrn; ctx.fillRect(0, 0, 150, 150); } 阴影这个例子绘制了带阴影效果的文字
ctx.shadowOffsetX = 2; ctx.shadowOffsetY = 2; ctx.shadowBlur = 2; ctx.shadowColor = "rgba(0, 0, 0, 0.5)"; ctx.font = "20px Times New Roman"; ctx.fillStyle = "Black"; ctx.fillText("Sample String", 5, 30); Canvas 填充规则默认nonzero,会以fillStyle填充,evenodd会以fillStyle与#fff交替填充,从这个例子就能看出来
ctx.beginPath(); ctx.fillStyle = '#00bcd4' ctx.arc(50, 50, 30, 0, Math.PI*2, true); ctx.arc(50, 50, 15, 0, Math.PI*2, true); ctx.arc(50, 50, 10, 0, Math.PI*2, true); ctx.fill("evenodd");两种绘制文本的方式fillText和strokeText
ctx.font = '40px 微软雅黑' ctx.fillText('Hello world', 10, 40) ctx.strokeText('Hello world', 10, 80) 文本的样式 /* font = value 当前我们用来绘制文本的样式. 这个字符串使用和 CSS font 属性相同的语法. 默认的字体是 10px sans-serif。 textAlign = value 文本对齐选项. 可选的值包括:start, end, left, right or center. 默认值是 start。 textBaseline = value 基线对齐选项. 可选的值包括:top, hanging, middle, alphabetic, ideographic, bottom。默认值是 alphabetic。 direction = value 文本方向。可能的值包括:ltr, rtl, inherit。默认值是 inherit。 */ 预测量文本宽度 measureText var text = ctx.measureText("foo"); console.log(text.width); // 20;canvas更有意思的一项特性就是图像操作能力。可以用于动态的图像合成或者作为图形的背景,以及游戏界面(Sprites)等等。浏览器支持的任意格式的外部图片都可以使用,比如PNG、GIF或者JPEG。 你甚至可以将同一个页面中其他canvas元素生成的图片作为图片源。
HTMLImageElement这些图片是由Image()函数构造出来的,或者任何的 img 元素
HTMLVideoElement用一个HTML的 video 元素作为你的图片源,可以从视频中抓取当前帧作为一个图像
HTMLCanvasElement可以使用另一个 canvas 元素作为你的图片源。
ImageBitmap这是一个高性能的位图,可以低延迟地绘制,它可以从上述的所有源以及其它几种源中生成
使用地址
若调用 drawImage 时,图片没装载完,那什么都不会发生(在一些旧的浏览器中可能会抛出异常)。因此你应该用load事件来保证不会在加载完毕之前使用这个图片
var img = new Image(); // 创建img元素 img.src = 'http://img.22family.com/mySKey/favicon.png'; // 设置图片源地址 img.onload = function(){ // 执行drawImage语句 ctx.drawImage(img, 0, 0) } 使用data-url其优点就是图片内容即时可用,无须再到服务器兜一圈。(还有一个优点是,可以将 CSS,JavaScript,HTML 和 图片全部封装在一起,迁移起来十分方便。)缺点就是图像没法缓存,图片大的话内嵌的 url 数据会相当的长
img.src = 'data:image/gif;base64,R0lGODlhCwALAIAAAAAA3pn/ZiH5BAEAAAEALAAAAAALAAsAAAIUhA+hkcuO4lmNVindo7qyrIXiGBYAOw=='; 使用视频帧 var videoDom = document.getElementById('video'); video.addEventListener('loadeddata',function(){ ctx.drawImage(video, 0, 0, 500, 300) }) 切片 SlicingdrawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)后面四个参数分别就是最终的图margin-left,margin-top,与之前的比例,就是原图的宽高比例
var img = new Image(); // 创建img元素 img.src = 'http://img.22family.com/mySKey/favicon.png'; // 设置图片源地址 img.onload = function(){ // 执行drawImage语句 ctx.drawImage(img, 0, 0, 400, 400, 100, 100, 800, 800) }变形是一种更强大的方法,可以将原点移动到另一点、对网格进行旋转和缩放。
save 和 restoresave 和 restore 方法是用来保存和恢复 canvas 状态的,都没有参数。Canvas 的状态就是当前画面应用的所有样式和变形的一个快照。包括当前应用的变形(即移动,旋转和缩放,见下) strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation 的值 当前的裁切路径(clipping path)。每一次调用 restore 方法,上一个保存的状态就从栈中弹出,所有设定都恢复。
ctx.fillRect(0,0,200,200) ctx.save() ctx.fillStyle = '#00bcd4' ctx.fillRect(20,20,160,160) ctx.save() ctx.fillStyle = '#fff' ctx.globalAlpha = 0.6 ctx.fillRect(40,40,120,120) ctx.restore() ctx.fillRect(60,60,80,80) ctx.restore() ctx.fillRect(80,80,40,40) 移动translatetranslate(x, y)相当于设置想x,y的偏移量
for(var i=0;i<4;i++){ for(var j=0;j<4;j++){ ctx.save() ctx.fillStyle = `rgb(0,${235-40*i},${255-40*j})` ctx.translate(10+40*i, 10+40*j) ctx.fillRect(0,0,30,30) ctx.restore() } } 旋转rotaterotate(angle)这个方法只接受一个参数:旋转的角度(angle),它是顺时针方向的,以弧度为单位的值
ctx.translate(75,75) for(var i=1;i<6;i++){ ctx.save() ctx.fillStyle = `rgb(0,${235-40*i},${255-40*i})` for(var j=0;j<i*6;j++){ ctx.rotate(Math.PI*2/(i*6)) ctx.beginPath() ctx.arc(0,i*12.5,5,0,Math.PI*2,true) ctx.fill() } ctx.restore() } 缩放scale(x, y),scale 方法接受两个参数。x,y 分别是横轴和纵轴的缩放因子,它们都必须是正值。值比 1.0 小表示缩小,比 1.0 大则表示放大,值为 1.0 时什么效果都没有。
ctx.strokeStyle = "#fc0"; ctx.fillRect(0,0,300,300) ctx.save() ctx.translate(50,50); drawSpirograph(ctx,22,6,5); ctx.translate(100,0) ctx.scale(0.8,0.8) drawSpirograph(ctx,22,6,5); ctx.translate(100,0) ctx.scale(0.5,0.5) drawSpirograph(ctx,22,6,5); ctx.restore() ctx.strokeStyle = "#00bcd4" ctx.save() ctx.translate(50,150) ctx.scale(0.8,1) drawSpirograph(ctx,22,6,5); ctx.translate(120,0) ctx.scale(0.5,1) drawSpirograph(ctx,22,6,5); ctx.translate(200,0) ctx.scale(0.3,1) drawSpirograph(ctx,22,6,5); ctx.restore() ctx.strokeStyle = "#dd5866" ctx.save() ctx.translate(50,250) ctx.scale(1,0.8) drawSpirograph(ctx,22,6,5); ctx.translate(100,0) ctx.scale(1,0.5) drawSpirograph(ctx,22,6,5); ctx.translate(100,0) ctx.scale(1,0.3) drawSpirograph(ctx,22,6,5); function drawSpirograph(ctx,R,r,O){ var x1 = R-O; var y1 = 0; var i = 1; ctx.beginPath(); ctx.moveTo(x1,y1); do { if (i>2000) break; var x2 = (R+r)*Math.cos(i*Math.PI/72) - (r+O)*Math.cos(((R+r)/r)*(i*Math.PI/72)) var y2 = (R+r)*Math.sin(i*Math.PI/72) - (r+O)*Math.sin(((R+r)/r)*(i*Math.PI/72)) ctx.lineTo(x2,y2); x1 = x2; y1 = y2; i++; } while (x2 != R-O && y2 != 0 ); ctx.stroke(); } 变形transform(m11, m12, m21, m22, dx, dy)这个方法是将当前的变形矩阵乘上一个基于自身参数的矩阵,m11:水平方向的缩放。m12:水平方向的倾斜偏移。m21:竖直方向的倾斜偏移。m22:竖直方向的缩放。dx:水平方向的移动。dy:竖直方向的移动
ctx.fillRect(0,0,200,200) var sin = Math.sin(Math.PI/6); var cos = Math.cos(Math.PI/6); ctx.translate(100, 100); var c = 0; for (var i=0; i <= 12; i++) { c = Math.floor(255 / 12 * i); ctx.fillStyle = `rgb(${c},${c},${c})` ctx.fillRect(0, 0, 100, 10); ctx.transform(cos, sin, -sin, cos, 0, 0); }globalCompositeOperation = type这个属性设定了在画新图形时采用的遮盖策略,其值是一个标识12种遮盖方式的字符串
裁切路径clip()将当前正在构建的路径转换为当前的裁剪路径
ctx.fillRect(0,0,200,200) ctx.fillStyle = '#00bcd4' ctx.translate(100,100) drawStar(ctx, 100) ctx.clip() ctx.beginPath() ctx.fillStyle = '#fff' ctx.arc(0,0,50,0,2*Math.PI,true) ctx.fill() function drawStar(ctx,r){ ctx.save(); ctx.beginPath() ctx.moveTo(r,0); for (var i=0;i<9;i++){ ctx.rotate(Math.PI/5); if(i%2 == 0) { ctx.lineTo((r/0.525731)*0.200811,0); } else { ctx.lineTo(r,0); } } ctx.closePath(); ctx.fill(); ctx.restore(); }除非接下来要画的内容会完全充满 canvas (例如背景图),否则你需要清空所有。最简单的做法就是用 clearRect 方法。
2、保存 canvas 状态如果你要改变一些会改变 canvas 状态的设置(样式,变形之类的),又要在每画一帧之时都是原始状态的话,你需要先保存一下。
3、绘制动画图形(animated shapes)这一步才是重绘动画帧。
4、恢复 canvas 状态如果已经保存了 canvas 的状态,可以先恢复它,然后重绘下一帧
使用canvas动画绘制时钟 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>canvas</title> </head> <body> <canvas id="charts" width="600" height="400"></canvas> <script type="text/javascript"> var canvas = document.getElementById('charts') var ctx = canvas.getContext('2d') var img = new Image() img.src = 'http://img.22family.com/blog/article/1548667872184744835' function clock(){ var now = new Date(); var s = now.getSeconds(); var m = now.getMinutes(); var h = now.getHours() % 12; ctx.save() ctx.clearRect(0,0,300,300) ctx.fillStyle = '#000' ctx.fillRect(0,0,300,300) // 时钟的外部圆 ctx.strokeStyle = '#fff' ctx.lineWidth = 5 ctx.beginPath() ctx.arc(150,150,100,0,Math.PI*2,true) ctx.stroke() ctx.clip() ctx.translate(150,150) ctx.rotate(-Math.PI/2) // 给表添加背景图 ctx.save() ctx.rotate(Math.PI/2) ctx.drawImage(img, -100, -100); ctx.restore() // 时钟的每5分钟的刻度 ctx.save() for(var i=0;i<12;i++){ ctx.beginPath(); ctx.moveTo(85,0); ctx.lineTo(95,0); ctx.stroke(); ctx.rotate(Math.PI/6) } ctx.restore() // 绘制分钟刻度 ctx.lineWidth = 3 ctx.save() for(var i=0;i<60;i++){ if(i%5!=0){ ctx.beginPath(); ctx.moveTo(90,0); ctx.lineTo(95,0); ctx.stroke(); } ctx.rotate(Math.PI/30) } ctx.restore() // 绘制时针 ctx.save(); ctx.rotate( h*(Math.PI/6) + (Math.PI/360)*m + (Math.PI/21600)*s ) ctx.lineWidth = 8; ctx.beginPath(); ctx.moveTo(-10,0); ctx.lineTo(60,0); ctx.stroke(); ctx.restore(); // 绘制分针 ctx.save(); ctx.rotate( (Math.PI/30)*m + (Math.PI/1800)*s ) ctx.lineWidth = 5; ctx.beginPath(); ctx.moveTo(-15,0); ctx.lineTo(80,0); ctx.stroke(); ctx.restore(); // 绘制秒针 ctx.save(); ctx.rotate( (Math.PI/30)*s ) ctx.lineWidth = 2; ctx.strokeStyle = '#dd5866' ctx.beginPath(); ctx.moveTo(-20,0); ctx.lineTo(90,0); ctx.stroke(); ctx.restore(); // 绘制连接时针分针秒针的纽扣 var radialGradient = ctx.createRadialGradient(0,0,0,0,0,5) radialGradient.addColorStop(0.2, '#000') radialGradient.addColorStop(1, '#00B5E2') ctx.save() ctx.beginPath() ctx.arc(0,0,5,0,2*Math.PI,true) ctx.fillStyle = radialGradient ctx.fill() ctx.restore() ctx.restore() window.requestAnimationFrame(clock) } window.requestAnimationFrame(clock) </script> </body> </html>符合物理的运动来让我们的动画更加高级
实现一个小球在屏幕区域,遵循物理规律的运动 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style type="text/css"> *{ margin: 0; padding: 0; } .container{ width:100vw; height: 100vh; overflow: hidden; position: relative; } #ball{ width: 50px; height: 50px; border-radius: 50%; background: #00bc38; position: absolute; left: 0; top: 0; } </style> </head> <body> <div class="container"> <div id="ball"></div> </div> <script type="text/javascript"> function init(){ var containerDom = document.getElementsByClassName('container')[0] var ballDom = document.getElementById('ball') var x = 0, y = 0, vx = 10, vy = 10; function updateBall(){ checkPosition() x += vx y += vy ballDom.style.left = x + 'px' ballDom.style.top = y + 'px' } function checkPosition(){ if(ballDom.offsetLeft>=containerDom.offsetWidth - ballDom.offsetWidth) vx = -10 if(ballDom.offsetLeft==0) vx = 10 if(ballDom.offsetTop==0) vy = 10 if(ballDom.offsetTop>=containerDom.offsetHeight - ballDom.offsetHeight) vy = -10 } function animate(){ updateBall() requestAnimationFrame(animate) } animate() } window.addEventListener('DOMContentLoaded', init) </script> </body> </html> 使用canvas来实现一般使用ctx.clearRect(0,0, canvas.width, canvas.height)来清除画布,但是要制作出长尾效果的话,使用透明背景色(rgba(255,255,255,0.3))填充
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>canvas-ball</title> </head> <body> <canvas id="canvas" width="800" height="400"></canvas> <script type="text/javascript"> var canvas = document.getElementById('canvas') var ctx = canvas.getContext('2d') var raf; var running = false; canvas.width = window.innerWidth canvas.height = window.innerHeight var ball = { x: 50, y: 50, r: 50, vx: 9, vy: 3, bg: 'rgba(255,255,255,0.3)', color: '#00bcd4', draw(){ // 使用透明度白色可以做出长尾效果 ctx.fillStyle = this.bg ctx.fillRect(0,0,canvas.width, canvas.height) ctx.beginPath() ctx.arc(this.x,this.y,this.r,0,Math.PI*2,true) ctx.fillStyle = this.color ctx.fill() } } function render(){ //ctx.clearRect(0,0, canvas.width, canvas.height); ball.draw() checkPosition() ball.x += ball.vx ball.y += ball.vy // ball.vy *= .99; // ball.vy += .25; raf = window.requestAnimationFrame(render) } function checkPosition(){ if(ball.x + ball.r >= canvas.width || ball.x<ball.r) ball.vx = -ball.vx if(ball.y + ball.r >= canvas.height || ball.y<ball.r) ball.vy = -ball.vy } //window.requestAnimationFrame(render) ball.draw() // 鼠标控制 canvas.addEventListener('mousemove', function(e){ if(running) return ball.x = e.clientX ball.y = e.clientY ball.draw() }) canvas.addEventListener('mouseout', function(){ window.cancelAnimationFrame(raf) running = false }) canvas.addEventListener('click', function(){ raf = window.requestAnimationFrame(render) running = true }) </script> </body> </html>可以直接通过ImageData对象操纵像素数据,直接读取或将数据数组写入该对象中
例如,要读取图片中位于第50行,第200列的像素的蓝色部份,你会写以下代码: blueComponent = imageData.data[((50-1)*imageData.width + (200-1))*4 - 1 + 3]; 根据行、列读取某像素点的R/G/B/A值的公式: imageData.data[((行数-1)*imageData.width + (列数-1))*4 - 1 + 1/2/3/4]; 你可能用会使用Uint8ClampedArray.length属性来读取像素数组的大小(以bytes为单位): var numBytes = imageData.data.length; 创建ImageData去创建一个新的,空白的ImageData对象,你应该会使用createImageData() 方法
var imageData = ctx.createImageData(300, 300); 得到场景像素数据 ctx.getImageData(left, top, width, height); 取色器的实现根据鼠标的坐标获取当前的颜色值
var img = new Image() img.src = 'http://img.22family.com/blog/article/1548667872184744835' img.crossOrigin = '' // 要使用getImageData(),不添加crossOrigin属性会跨域 img.onload = function(){ ctx.drawImage(img, 0, 0) var imageData = ctx.createImageData(300, 300); var numBytes = imageData.data.length; var blueComponent = imageData.data[((80-1)*imageData.width + (80-1))*4 - 1 + 1/2/3/4] console.log(blueComponent) } canvas.addEventListener('mousemove', function(e){ var x = e.layerX var y = e.layerY var pixel = ctx.getImageData(x, y, 1, 1); var data = pixel.data var rgba = `rgba(${data[0]},${data[1]},${data[2]},${data[3] / 255})` console.log(rgba) }) 在场景中写入像素数据 ctx.putImageData(myImageData, dx, dy) 缩放和防锯齿 imageSmoothingEnabled在drawImage() 方法, 第二个画布和imageSmoothingEnabled 属性的帮助下,我们可以放大显示我们的图片及看到详情内容
var img = new Image() img.src = 'http://img.22family.com/blog/article/1548667872184744835' img.crossOrigin = '' img.onload = function(){ ctx.drawImage(img, 0, 0) ctx.imageSmoothingEnabled = 10 } canvas.addEventListener('mousemove', function(e){ var x = e.layerX var y = e.layerY ctx.drawImage(canvas, x, y, 50, 50, 300, 0, 300, 300) }) 保存图片canvas.toDataURL(‘image/png’),默认设定。创建一个PNG图片。
canvas.toDataURL(‘image/jpeg’, quality),创建一个JPG图片。你可以有选择地提供从0到1的品质量,1表示最好品质,0基本不被辨析但有比较小的文件大小
canvas.toBlob(callback, type, encoderOptions),这个创建了一个在画布中的代表图片的Blob对像
hit region API让你可以在canvas上定义一个区域,这让无障碍工具获取canvas上的交互内容成为可能。它能让你更容易地进行点击检测并把事件转发到DOM元素去。
/* CanvasRenderingContext2D.addHitRegion() 在canvas上添加一个点击区域。 CanvasRenderingContext2D.removeHitRegion() 从canvas上移除指定id的点击区域。 CanvasRenderingContext2D.clearHitRegions() 移除canvas上的所有点击区域。 */ 焦点圈当用键盘控制时,焦点圈是一个能帮我们在页面上快速导航的标记。要在canvas上绘制焦点圈,可以使用drawFocusIfNeeded 属性
/* CanvasRenderingContext2D.drawFocusIfNeeded() 如果给定的元素获得了焦点,这个方法会沿着在当前的路径画个焦点圈 CanvasRenderingContext2D.scrollPathIntoView() 把当前的路径或者一个给定的路径滚动到显示区域内。 */如果你发现你的在每一帧里有好多复杂的画图运算,请考虑创建一个离屏canvas,将图像在这个画布上画一次(或者每当图像改变的时候画一次),然后在每帧上画出视线以外的这个画布
2、避免浮点数的坐标点,用整数取而代之 ctx.fillRect(0.3,0.4,40,50) 3、不要在用drawImage时缩放图像在离屏canvas中缓存图片的不同尺寸,而不要用drawImage()去缩放它们。
4、使用多层画布去画一个复杂的场景你可能会发现,你有些元素不断地改变或者移动,而其它的元素,例如外观,永远不变。这种情况的一种优化是去用多个画布元素去创建不同层次
5、用CSS设置大的背景图如果像大多数游戏那样,你有一张静态的背景图,用一个静态的
元素,结合background 特性,以及将它置于画布元素之后。这么做可以避免在每一帧在画布上绘制大图。 6、用CSS transforms特性缩放画布CSS transforms 特性由于调用GPU,因此更快捷。最好的情况是,不要将小画布放大,而是去将大画布缩小。例如Firefox系统,目标分辨率480 x 320 px
7、关闭透明度如果你的游戏使用画布而且不需要透明,当使用 HTMLCanvasElement.getContext() 创建一个绘图上下文时把 alpha 选项设置为 false 。这个选项可以帮助浏览器进行内部优化
var ctx = canvas.getContext('2d', { alpha: false }); /* 将画布的函数调用集合到一起(例如,画一条折线,而不要画多条分开的直线) 避免不必要的画布状态改变 渲染画布中的不同点,而非整个新状态 尽可能避免 shadowBlur特性 尽可能避免text rendering 使用不同的办法去清除画布(clearRect() vs. fillRect() vs. 调整canvas大小) 有动画,请使用window.requestAnimationFrame() 而非window.setInterval() 请谨慎使用大型物理库 */Canvas的基本用法