Canvas使用

    xiaoxiao2022-07-03  134

    Canvas使用

    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();

    添加样式和颜色

    颜色 ctx.fillStyle = "orange"; ctx.fillStyle = "#FFA500"; ctx.fillStyle = "rgb(255,165,0)"; ctx.fillStyle = "rgba(255,165,0,1)"; // 利用rgba来绘制 for(var i=0;i<6;i++){ for(var j=0;j<6;j++){ ctx.fillStyle = `rgba(0,${Math.floor(255-42.5*i)},${Math.floor(255-42.5*j)},${0.2+Math.random()*0.8})` ctx.fillRect(i*25,j*25,25,25) } } 透明度 Transparency

    通过设置 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() 图案样式 Patterns

    createPattern(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) }) 切片 Slicing

    drawImage(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 和 restore

    save 和 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) 移动translate

    translate(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() } } 旋转rotate

    rotate(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(); }

    基本动画

    1、清空 canvas

    除非接下来要画的内容会完全充满 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() 把当前的路径或者一个给定的路径滚动到显示区域内。 */

    性能贴士

    1、在离屏canvas上预渲染相似的图形或重复的对象

    如果你发现你的在每一帧里有好多复杂的画图运算,请考虑创建一个离屏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() 请谨慎使用大型物理库 */

    Demo

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>canvas</title> </head> <body> <video style="display:none;" src="http://img.22family.com/test/test-video.mp4" id="video"></video> <canvas id="charts" width="600" height="400"></canvas> <script type="text/javascript"> var canvas = document.getElementById('charts') var ctx = canvas.getContext('2d') // 绘制矩形 // ctx.fillRect(25, 25, 100, 100); // ctx.clearRect(45, 45, 60, 60); // ctx.strokeRect(50, 50, 50, 50); // 绘制三角形 // ctx.beginPath() // ctx.moveTo(75, 50); // ctx.lineTo(100, 75); // ctx.lineTo(100, 25); // ctx.lineTo(75, 50); // ctx.stroke() // ctx.closePath() // 绘制笑脸 // 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() // 二次贝塞尔曲线 // 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(); // 利用rgba来绘制 // for(var i=0;i<6;i++){ // for(var j=0;j<6;j++){ // ctx.fillStyle = `rgba(0,${Math.floor(255-42.5*i)},${Math.floor(255-42.5*j)},${0.2+Math.random()*0.8})` // ctx.fillRect(i*25,j*25,25,25) // } // } // 透明度 Transparency // 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() // } // 线性渐变 // 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() // 图案 // 创建新 image 对象,用作图案 // 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); // 填充规则 // 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"); // 文本 // ctx.font = '40px 微软雅黑' // ctx.fillText('Hello world', 10, 40) // ctx.strokeText('Hello world', 10, 80) // 预测量文本宽度 // var text = ctx.measureText("foo"); // console.log(text.width); // 20; // 图片 // var img = new Image(); // 创建img元素 // img.src = 'http://img.22family.com/mySKey/favicon.png'; // 设置图片源地址 // img.onload = function(){ // // 执行drawImage语句 // ctx.drawImage(img, 0, 0) // } // 获取视频帧图片 // var videoDom = document.getElementById('video'); // video.addEventListener('loadeddata',function(){ // ctx.drawImage(video, 0, 0, 500, 300) // }) // 切片 // 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与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) // 移动translate // 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() // } // } // 旋转 // 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() // } // 缩放 // 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(); // } // 变形 // 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); // } // 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(); // } // 绘制时钟 // 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) // 像素操作 // var img = new Image() // img.src = 'http://img.22family.com/blog/article/1548667872184744835' // img.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) // }) // 缩放和防锯齿 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) var img = canvas.toDataURL('image/png') console.log(img) }) </script> </body> </html>

    参考文献

    Canvas的基本用法

    最新回复(0)