Canvas使用

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的基本用法

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值