关于canvas:乘风破浪的canvas

34次阅读

共计 19211 个字符,预计需要花费 49 分钟才能阅读完成。

本篇文章次要波及的内容有

canvas 根本理解

什么是 canvas?

canvas 是 HTML5 新增的元素,可用于通过 javascript 中的脚本来绘制图形;例如它能够用于绘制图形,创立动画。canvas 最早由 Apple 引入 webkit;

咱们能够应用 canvas 标签来定义一个 canvas 元素,应用 canvas 标签时,倡议成对呈现,不要应用闭合的模式。canvas 默认具备宽高,width: 300px;height: 150px;

canvas 中的替换内容

canvas 标签很容易定义一些替换内容,因为某些较老的浏览器(尤其是 IE9 之前的 IE 浏览器),不反对 HTML 元素 canvas,然而在这些浏览器上你应该给用户展现一些替换内容。这非常简单,咱们只须要在 canvas 标签中提供替换文本就能够了,反对 canvas 的浏览器将会疏忽容器中蕴含的内容,并且只是失常渲染 canvas,不反对 canvas 的浏览器会显示替换内容。

canvas 标签中的两个属性

canvas 看起来和 img 标签看起来很像,惟一不同的是它并没有 src 和 alt 属性。实际上 canvas 标签只有两个属性,width 和 height;这些都是可选的,当没有设置高宽时,canvas 会初始化宽度为 300px 和高度 150px。

画布高宽

html 属性设置 width height 时只影响画布自身不影响画布内容 css 属性设置 width height 时岂但会影响画布自身的高宽,还会使画布中的内容等比例缩放(缩放参照于画布默认的尺寸)

!!!所以不要通过 css 设置画布的尺寸

渲染上下文

canvas 元素只是创立了一个固定大小的画布,要想在它下面绘制内容,咱们须要找到它的渲染上下文;

canvas 元素中有一个 getContext()办法,这个办法是用来取得渲染上下文和它的绘画性能。

getContext 只有一个参数,上下文格局


上下文格局类型:"2d", 建设一个 CanvasRenderingContext2D 二维渲染上下文。"webgl" (或 "experimental-webgl") 这将创立一个 WebGLRenderingContext 三维渲染上下文对象。只在实现 WebGL 版本 1(OpenGL ES 2.0)的浏览器上可用。"webgl2" (或 "experimental-webgl2") 这将创立一个 WebGL2RenderingContext 三维渲染上下文对象。只在实现 WebGL 版本 2 (OpenGL ES 3.0)的浏览器上可用。"bitmaprenderer" 这将创立一个只提供将 canvas 内容替换为指定 ImageBitmap 性能的 ImageBitmapRenderingContext。
const canvas = document.querySelector('canvas');
if (canvas.getContext) {const ctx = canvas.getContext('2d');
}

绘制矩形

HTML 中的元素 canvas 只反对一种原生 (调用一个 api) 的图形绘制:矩形;所有其余的图形的绘制都至多须要生成一条门路。

绘制矩形

canvas 提供三种办法绘制矩形:1. 绘制一个填充矩形
        ctx.fillRect(x, y, width, height);
    2. 绘制一个矩形的边框
        ctx.strokeRect(x, y, width, height);
    3. 革除指定矩形的区域,让革除局部齐全通明
        ctx.clearRect(x, y, width, height);
        
x,y 指定了在 canvas 画布上所绘制的左上角(绝对于原点)的坐标。width 和 height 设置绘制矩形的尺寸(存在边框的话,边框会在 width 上占据一个边框的宽度,高度同理)

strokeRect 时,边框像素渲染问题

按理来说渲染进去的边框应该是 1px,然而 canvas 在渲染矩形边框时,边框的宽度是平均分在偏移地位的两侧。



ctx.strokeRect(10, 10, 50, 50);
阐明:canvas 渲染边框在 10.5 到 9.5 之间
    然而浏览器是不会让一个像素只用本人的一半,所有这个边框渲染时在 9 至 11 之间。

解决办法:

1. ctx.strokeRect(10.5, 10.5, 50, 50);

2. 绘制一个通明的矩形进行笼罩
    ctx.strokeRect(10, 10, 50, 50);
    ctx.clearRect(10, 10, 50, 50);

增加款式和色彩

fillStyle 设置图形的填充色彩,默认 #000000
strokeStyle 设置图形轮廓的色彩,默认 #000000
lineWidth 设置以后绘线的粗细,属性值必须为正值。默认 1.0

lineJoin

设定线条与线条间接合处的款式(默认是 miter),与填充没有关系

都有哪些值,含意是什么

1. round: 圆角
2. bevel: 斜角
3. miter: 直角

绘制门路

图形的根本元素是门路。门路是通过不同色彩和宽高的线段或曲线相连造成的不同形态的点的汇合。

大体绘制的步骤


1. 首先,你须要创立门路起始点
2. 而后应用画图命令去画门路
3. 而后你把门路进行关闭
4. 一旦门路生成,你就能通过描边或者填充门路区域来渲染图形

绘制门路之绘制三角形

画笔上的 api
1. beginPath()
    新建一条门路,生成之后,图形绘制命令指向到门路上筹备生成门路。生成门路的第一步叫做 beginPath()。实质上,门路是由多个子门路形成,这些子门路都在一个列表中,所有的子门路形成图形,而每次这个办法调用之后,列表清空重置。2. moveTo(x, y)
    将笔触挪动到指定坐标 x y 上
3. lineTo(x, y)
    将笔触挪动到指定坐标 x y 上
4. closePath()
    闭合门路之后图形绘制命令又从新指向到上下文
5. stroke()
    须要手动合并门路,要么 closePath(),要么 lineTo 到起始点
6. fill()
    会主动合并门路

画一个描边三角形

    ctx.beginPath();
    ctx.moveTo(100, 100);
    ctx.lineTo(100, 200);
    ctx.lineTo(200, 200);
    ctx.lineTo(100, 100);// 或者 closePath()
    ctx.stroke(); // 描边
    ctx.fill(); // 填充三角形

如何了解 beginPath 呢?看一个实例, 以及效果图
我只是像绘制一个描边三角形和另外一个填充的三角形,然而见效果图,把后面的本只是描边的三角形也从新填充了一遍;

    ctx.strokeStyle = 'pink';
    ctx.lineWidth = 10;
    // ctx.beginPath();
    ctx.moveTo(100, 100);
    ctx.lineTo(100, 200);
    ctx.lineTo(200, 200);
    ctx.closePath();
    ctx.stroke();

    ctx.moveTo(200, 200);
    ctx.lineTo(200, 300);
    ctx.lineTo(300, 300);

    ctx.fill();

绘制门路之绘制矩形

rect(x, y, width, height)

    ctx.rect(100, 100, 100, 100);
    ctx.fill();// 绘制一个填充矩形
    // ctx.stroke(); // 绘制一个描边矩形

lineCap

lineCap 是 canvas 2D API 指定如何绘制没一条线段末端的属性。

存在的值
1. butt(默认值) 线段末端以方形完结
2. round 线段末端以圆形完结
3. square 线段末端以方形完结,然而减少了一个宽度和线段雷同,高度是线段厚度一半的矩形区域

save

save() 是 Canvas 2D API 通过将以后状态入栈中,保留 canvas 全副状态的办法


保留到栈中的绘制状态有上面局部组成:以后的变换矩阵。以后的剪切区域
    以后的虚线列表
    以下属性以后的值:strokeStyle
        fillStyle
        lineWidth
        lineCap
        lineJoin

restore

save & restore & beginPath 套路步骤(多个图形之间齐全独立时)

    ctx.save();
        
        款式相干的
    
    ctx.beginPath(); // 每次绘制门路之前都先 beginPath 一下
        门路
    ctx.restore();

    而后开始绘制图形

看一个简略的小 demo 联合图解了解 save&restore

    ctx.save();
    ctx.fillStyle = 'pink';
    ctx.save();
    ctx.fillStyle = 'deeppink';
    ctx.fillStyle = 'blue';
    ctx.save();
    ctx.fillStyle = 'red';
    ctx.save();
    ctx.fillStyle = 'green';
    ctx.save();
    ctx.restore(); // 绿色
    ctx.restore(); // 红色
    ctx.restore(); // blue
    ctx.restore(); // deeppink
    ctx.restore(); // pink
    
    ctx.fillRect(50, 50, 100, 100); // pink

理解了以上根本的用法,这时候能够插入一个小 demo——签名

  const canvas = document.querySelector('canvas');
  if (canvas.getContext) {const ctx = canvas.getContext('2d');
    ctx.beginPath();
    canvas.addEventListener('mousedown', function(e) {
      e = e || window.event;
      ctx.moveTo(e.clientX - canvas.offsetLeft, e.clientY - canvas.offsetTop);
      document.onmousemove = function(e) {
        e = e || window.event;
        ctx.lineTo(e.clientX - canvas.offsetLeft, e.clientY - canvas.offsetTop);
        ctx.stroke();}
      document.onmouseup = function(e) {
        e = e || window.event;
        document.onmousedown = document.onmousemove = null;
        if (document.releaseCapture) {document.releaseCapture();
        }
      }
    });

绘制曲线

绘制圆形

arc(x, y, radius, startAngle, endAngle, anticlockwise)

该 api 形容的是以 (x, y) 为圆心 radius 为半径的圆弧,从 star 开始到 endAngle 完结,依照 anticlockwise 给定的方向 (默认是顺时针) 生成一个圆弧。anticlockwise: false(默认顺时针)
x,y  绘制圆弧所在圆上的圆心坐标
radius 半径
startAngle, endAngle: 值是弧度,这些值都是以 x 轴为基准参数
arcTo 绘制圆弧
arcTo(x1, y1, x2, y2, radius)
依据给定的控制点和半径画一段圆弧(至多须要三个控制点)
    ctx.arcTo(50, 50, 100, 50, 50);
    ctx.arcTo(100, 50, 100, 100, 50);

    ctx.stroke();

二次贝塞尔

quadraticCurveTo(cp1x, cp1y, x, y)
绘制二次贝塞尔尔曲线,cp1x,cp1y 为一个控制点,x,y 为完结点。
// 连贯三个控制点,不便看出曲线是如何绘制的
    ctx.beginPath();
    ctx.moveTo(50, 50);
    ctx.lineTo(100, 50);
    ctx.lineTo(100, 100);
    ctx.stroke();

    ctx.beginPath();
    ctx.moveTo(50, 50);
    ctx.quadraticCurveTo(100, 50, 100, 100); // 没有指定半径,绘制的圆弧必须过这个控制点

    ctx.stroke();

三次贝塞尔

bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
绘制三次贝塞尔曲线,cp1x,cp1y 为控制点一,cp2x, cp2y 为控制点二,x,y 为完结点
    ctx.beginPath();
    ctx.moveTo(50, 50);
    ctx.lineTo(100, 50);
    ctx.lineTo(0, 300);
    ctx.lineTo(200, 200);
    ctx.stroke();

    ctx.beginPath();
    ctx.moveTo(50, 50);
    ctx.bezierCurveTo(100, 50, 0, 300, 200, 200); // 没有指定半径,绘制的圆弧必须过第一和第三个控制点

    ctx.stroke();

canvas 中变换

translate(x, y)

这个办法是用来挪动 canvas 的原点到一个指定的地位。translate 办法承受两个参数。x 是左右偏移量,y 是高低偏移量,并且在 canvas 中 translate 是累加的

    ctx.translate(50, 50);
    
    ctx.strokeRect(0, 0, 50, 50);

    ctx.translate(50, 50);
    ctx.strokeRect(0, 0, 50, 50);


从效果图不难看出这个 translate 办法是扭转了画布的原点并且是有累加成果的,如果不想要这个累加这个成果,这时候就须要应用后面提及的 save 办法和 restore 办法了,试试看

    ctx.save();
    ctx.translate(50, 50);
    ctx.strokeRect(0, 0, 50, 50);
    ctx.restore();

    ctx.save();
    ctx.strokeStyle = 'red';

    ctx.translate(70, 70);
    ctx.strokeRect(0, 0, 50, 50);

rotate(angle)

这个办法只承受一个参数,旋转的角度(angle),它是顺时针方向的,以弧度为单位的值。旋转的中心点始终是 canvas 的原地,如果要扭转原点,咱们能够应用 translate 办法,并且在 canvas 中 roate 也是累加的。

ctx.rotate(Math.PI * 45 / 180);

ctx.fillRect(50, 50, 50, 50);

    ctx.rotate(Math.PI * 45 / 180);

    ctx.fillRect(50, 50, 50, 50);
    ctx.rotate(Math.PI * (360 - 45) / 180); // 如果是叠加的让它转回默认的地位
    ctx.fillRect(50, 50, 50, 50); // 这个绘制进去的是效果图中残缺矩形

scale(x, y)

scale 办法承受两个参数,x,y,别离是横轴和纵轴的缩放因子,它们必须是正值。值比 1.0 小示意放大,比 1.0 大则示意放大,值为 1.0 什么成果都没有。缩放个别咱们用它增减图形在 canvas 中的像素数目,对形态,位图进行放大或者放大。在 canvas 中 scale 是累加的

css 像素是一个形象单位,它所占据理论尺寸会随着缩放发生变化
放大时,区域物理尺寸没有变,区域内(画布)css 像素个数变少,每一个 css 像素的面积变大
放大时,区域物理尺寸没有变,区域内(画布)css 像素个数变多,每一个 css 像素的面积变小

变换和旋转以及缩放相结合

    ctx.scale(2, 2);  
    ctx.translate(10, 10);
    ctx.rotate(Math.PI * 10 / 180);

    ctx.fillRect(50, 50, 50, 50); // 也就是说参数指定的像素点的个数

来一个变换相干的实例找找感觉

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    canvas {
      position: absolute;
      top: 0;
      right: 0;
      bottom: 0;
      left: 0;
      margin: auto;
      background: grey;
    }
  </style>
</head>
<body>
  <canvas width="300" height="300"></canvas>
</body>
<script>
  const canvas = document.querySelector('canvas');
  if (canvas.getContext) {const ctx = canvas.getContext('2d');
    let flag = 0;
    let scaleValue = 0;
    let scaleFlag = 1;
    setInterval(function() {
      flag += 2;
      if (scaleValue >= 100) {scaleFlag = -2;} else if (scaleValue === 0) {scaleFlag = 2;}
      scaleValue += scaleFlag; // scaleValue 值的区间[0, 2]
      ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight);

      ctx.save();
      ctx.translate(150, 150);

      ctx.rotate(Math.PI*flag/180);
      ctx.scale(scaleValue/50, scaleValue/50); 
      ctx.fillRect(-50, -50, 100, 100); 
      ctx.restore();}, 1000/60);
  }
</script>
</html>

理解了 canvas 中的变动,这时候能够来写一个简易版的时钟啦

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    
    canvas {
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate3d(-50%, -50%, 0);
      background: grey;

    }
  </style>
</head>
<body>
  <canvas width="400" height="400">
    您的浏览器不反对 canvas,请应用萌萌哒谷歌:https://www.google.cn/chrome/
  </canvas>
</body>
<script>
  window.onload = function() {const canvas = document.querySelector('canvas');
  if (canvas.getContext) {const ctx = canvas.getContext('2d');

    setInterval(function() {ctx.clearRect(0, 0, canvas.width, canvas.height);
      move();}, 1000);
    move();
    function move() {ctx.save();
      
      ctx.lineWidth = 8;
      ctx.lineCap = 'round';
      ctx.translate(200, 200);
      ctx.rotate(-90*Math.PI/180);
      ctx.scale(.5, .5);
      ctx.beginPath();
      
      // 外层空心圆盘
      ctx.save();
      ctx.strokeStyle = '#325FA2';
      ctx.lineWidth = 14;
      ctx.beginPath();
      ctx.arc(0,0, 140, 0, Math.PI*2);
      ctx.stroke();
      ctx.restore();

      // 时针刻度
      ctx.save();
      for (let i = 0; i < 12; i++) {ctx.rotate(30*Math.PI/180); // canvas 中变换是累加的
        ctx.beginPath();
        ctx.moveTo(100, 0);
        ctx.lineTo(120, 0);
        ctx.stroke();}
      ctx.restore();

      // 分针刻度
      ctx.save();
      ctx.lineWidth = 4;
      for (let i = 0; i < 60; i++) {
        // 解决时钟的地位不反复画分针刻度
        if (i % 5 !== 0) {ctx.beginPath();
          ctx.moveTo(117, 0);
          ctx.lineTo(120, 0);
        }
        ctx.rotate(6*Math.PI/180); // canvas 中变换是累加的
        ctx.stroke();}
      ctx.restore();

      // 时针 分针 秒针 表座
      const date = new Date();
      const s =  date.getSeconds();
      const m = date.getMinutes() + s / 60;
      let h = date.getHours() + m / 60;
      h > 12 ? h - 12 : h;
      console.log(s, m, h);
      // 时针
      ctx.save();
      ctx.lineWidth = '14';
      ctx.rotate(h*30*Math.PI/180);
      ctx.beginPath();
      ctx.moveTo(-20, 0);
      ctx.lineTo(80, 0);
      ctx.stroke();
      ctx.restore();
      // 分针
      ctx.save();
      ctx.lineWidth = '10';
      ctx.rotate(m*6*Math.PI/180);
      ctx.beginPath();
      ctx.moveTo(-28, 0);
      ctx.lineTo(112, 0);
      ctx.stroke();
      ctx.restore();

      // 秒针 表座
      ctx.save();
      ctx.strokeStyle = '#D40000';
      ctx.fillStyle = '#D40000';
      ctx.lineWidth = '6';
      ctx.rotate(s*6*Math.PI/180);
      ctx.beginPath();
      ctx.moveTo(-30, 0);
      ctx.lineTo(83, 0);
      ctx.stroke();
      // 表座
      ctx.beginPath();
      ctx.arc(0,0,10,0,Math.PI*2);
      ctx.fill();

      ctx.beginPath();
      ctx.arc(96,0,10,0,Math.PI*2);
      ctx.stroke();

      ctx.restore();

      ctx.restore();}
    
  }

  }
  
</script>
</html>

canvas 中应用图片(背景、突变)

在 canvas 中插入图片

1.canvas 操作图片时,必须要等图片加载完能力操作
2.drawImage(image, x, y, width, height)
    image: 图像源;是 imgae 对象 或者 canvas 对象
    x, y: 图片在 canvas 中开始绘制的起始坐标
    width & height: 这两个参数用来管制 当向 canvas 画入时应该缩放的大小
    const image = new Image();
    image.src = './image.jpg'; 
    image.onload = function() {ctx.drawImage(image, 0, 0, image.width, image.height);
    }

在 canvas 中设置背景

1. createPattern(image, repetition)
    image: 图像源
    repetition: repeat | repeat-x | repeat-y | no-repeat
个别状况下会将 createPattern()返回的对象作为 fillStyle 的值
    const image = new Image();
    image.src = './image.jpg'; 
    image.onload = function() {ctx.fillStyle = ctx.createPattern(image, 'repeat');
      ctx.fillRect(0, 0, canvas.width, canvas.height);
    }

在 canvas 中应用突变

线性突变
1. createLinearGradient(x1, y1, x2, y2)
    x1,y1: 突变的终点
    x2,y2: 突变的起点
2. gradient.addColorStop(position, color)
    gradient: createLinearGradient 办法的返回值
    position: 必须是 0 - 1 之间的数值,示意突变中色彩所在的绝对地位。color: 必须是一个无效的 css 色彩值 (如: #fff rgba(255, 255, 255, 1) 等)
径向突变
1. createRadialGradient(x1, y1, r1, x2, y2, r2)
    x1, y1, r1: 定义一个以 (x1, y1) 为原点,半径为 r1 的圆
    x2, y2, r2:定义一个以 (x2, y2) 为原点,半径为 r2 的圆

线性突变

    const gradient = ctx.createLinearGradient(0, 0, 300, 200);
    gradient.addColorStop(0, 'red');
    gradient.addColorStop(.5, 'yellow');
    gradient.addColorStop(1, 'green');
    ctx.fillStyle = gradient;
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    

径向突变

    // 两个圆之间是突变的
    const gradient = ctx.createRadialGradient(150, 150, 50, 150, 150, 150); 
    gradient.addColorStop(0, 'red');
    gradient.addColorStop(.5, 'yellow');
    gradient.addColorStop(1, 'green');
    ctx.fillStyle = gradient;
    ctx.fillRect(0, 0, canvas.width, canvas.height);

绘制文本

canvas 提供了两种办法来渲染文本

1. fillText(text, x, y): 在指定的 (x, y) 地位填充指定的文本
2. strokeText(text, x, y): 在指定的 (x, y) 地位绘制描边文字

还提供 api 设置文本款式

font = value 
    默认字体是 '10px sans-serif'
    这个字符串应用和 css font 属性的语法雷同
    留神:font 属性在指定时,必须要有字体大小 和 字体 缺一不可,并且只有 sans-serif 这种字体可用,写错了也默认应用 sans-serif
textAlign = value
    设置文本程度对齐形式
    文本对齐选项:left: 文本左对齐
        right: 文本右对齐
        center: 文本居中对齐。值得注意的是,这里的文本居中是基于你在 fillText 的时候所给 x 值,文本的一半在 x 地位的右边一半内容在左边
textBaseline = value
    设置文本基线的属性
    top|middle|bottom
        文本基线在文本块的顶部 | 两头 | 底部
        
measureText: 返回 TextMetrics 对象,蕴含文本尺寸信息

            
    const ctx = canvas.getContext('2d');
    ctx.font = '26px sans-serif';
    ctx.textAlign = 'left';
    ctx.fillText('hello world', 150, 150); 

计算文本的宽度, 这个比拟有用

    ctx.font = '26px sans-serif';
    ctx.fillText('hello world', 150, 150); 
    const textMetrics = ctx.measureText('hello world');
    console.log(textMetrics);

文本暗影 & 盒模型暗影

1. shadowOffsetX = float 默认为 0
    shadowOffsetX 用来设定暗影在 X 轴的延长间隔
2. shadowOffsetY = float 默认为 0
    shadowOffsetY 用来设定暗影在 Y 轴的延长间隔
3. shadowOffsetBlur = float 默认为 0
    用于设定暗影的含糊水平,与像素数量没有关系,不受变换矩阵的影响
4. shadowOffsetColor = color(必须) 默认全透明的彩色
    ctx.font = '26px sans-serif';
    ctx.shadowOffsetX = 3;
    ctx.shadowOffsetY = 3;
    ctx.shadowBlur = 0.5;
    ctx.shadowColor = 'rgba(0, 0, 0, .6)';
    ctx.fillText('hello world', 150, 150); 

像素操作

到目前为止,咱们还尚未理解 canvas 画布实在像素的原理,事实上,咱们能够通过 ImageData 对象操纵像素数据,间接读取或者将数据数组写入该对象中

获取场景像素数据

getImageData() 取得一个含画布场景像素数据的 ImageData 对象

ctx.getImageData(sx, sy, sw, sh)
    sx 将要被提取的图形像素数据矩形区域左上角 x 坐标
    sy 将要被提取的图形像素数据矩形区域左上角 y 坐标
    sw 将要被提取的图形像素数据矩形区域的宽度
    sh 将要被提取的图形像素数据矩形区域的高度
返回的 ImageData 对象
    width:横向上像素点的个数
    height:纵向上像素点的个数
    data:Unit8ClampeArray 类型的一维数组,蕴含着 RGBA 格局的整型数据
        R: 0 - 255 (彩色到红色)
        G: 0 - 255 (彩色到红色)
        B: 0 - 255 (彩色到红色)
        A: 0 - 255 (通明到不通明)
        
// 为了不便看出画布,图中画布灰色是通过 css 设置的
    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    console.log(imageData);
    console.log(imageData.data);
    

由此能够看出画布默认是彩色通明的

在场景中写入像素数据

1. putImageData(myImageData, dx, dy)
    dx 和 dy 参数示意在场景内左上角绘制的像素所失去的设施坐标

    ctx.fillRect(0, 0, 50, 50); // 默认绘制的矩形是彩色
    const imageData = ctx.getImageData(0, 0, 50, 50); // 获取场景矩形的像素数据
    
    for (let i = 0; i < imageData.data.length; i++) {
      const r = i*4;
      const g = i*4 + 1;
      const b = i*4 + 2;
      const a = i*4 + 3;
      imageData.data[r] = 100; 
    }
    ctx.putImageData(imageData, 0, 0); // 批改了场景所有像素点 G 为 100 变成了酒红色

创立一个 ImageData 对象

ctx.createImageData(width, height)
    width: ImageData 新对象的宽度
    height: ImageData 新对象的高度
默认状况下创立的是彩色通明的

像素操作之马赛克

铺垫

单像素操作,定义一个工具函数,给定一个 imageData 对象和一个坐标获取该坐标点的像素信息

 // 给定偏移量拿像素点的信息
  function getPXInfo (imageData, x, y) {const color = [];
    const data = imageData.data;
    const width = imageData.width;
    const height = imageData.height;
    const theFirst = (y * width) + x; // 以后像素后面有多少个像素点
    // r
    color[0] = data[theFirst * 4];
    // g
    color[1] = data[theFirst * 4 + 1];
    // b
    color[2] = data[theFirst * 4 + 2];
    //a
    color[3] = data[theFirst * 4 + 3];

    return color;
  }

单像素操作,定义一个工具函数,给定一个坐标和像素信息设置该坐标


    ctx.save();
    ctx.fillStyle = 'pink';

    ctx.beginPath();
    ctx.fillRect(0, 0, 100,100);

    ctx.restore();

    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    
    console.log(getPXInfo(imageData, 100, 0));
    // 将矩形区域的第一行改成了彩色
    for (let i = 0; i < 100; i++) {var newImageData = changePXInfo(imageData, i, 0, [0, 0, 0, 255]);
    }
    ctx.putImageData(newImageData, 0, 0);
    // 扭转指定地位上的像素点信息
    function changePXInfo (imageData, x, y, color) {
        const data = imageData.data;
        const width = imageData.width;
        const height = imageData.height;
        const theFirst = (y * width) + x; // 以后像素后面有多少个像素点
        // r
        data[theFirst * 4] = color[0];
        // g
        data[theFirst * 4 + 1] = color[1];
        // b
        data[theFirst * 4 + 2] = color[2];
        //a
        data[theFirst * 4 + 3] = color[3];
        return imageData;
      }

马赛克实现

思路
1. 将图片区域划分成若干个马赛克矩形区域
2. 从马赛克矩形区域中随机抉择一个像素点,并且将该像素点信息赋予马赛克矩形区域内其余像素点
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    body { }
    canvas {
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate3d(-50%, -50%, 0);
      /* border: 1px solid; */
      background: grey;

    }
  </style>
</head>
<body>
  <canvas></canvas>
</body>
<script>
  const canvas = document.querySelector('canvas');
  if (canvas.getContext) {const ctx = canvas.getContext('2d');

    ctx.save();
    ctx.fillStyle = 'pink';

    ctx.beginPath();
    ctx.fillRect(0, 0, 100,100);

    ctx.restore();
    const image = new Image();
    image.src = './image.jpeg'; 
    image.onload = function() {
      canvas.width = image.width * 2;
      canvas.height = image.height; 
      draw();}

    function draw() {ctx.drawImage(image, 0, 0, image.width, image.height);
      const oldImageData = ctx.getImageData(0, 0, image.width, image.height);
      // 依据 oldImageData 数据的批改成
      let newImageData = ctx.createImageData(image.width, image.height);
      // 1. 选取马赛克矩形
      // 2. 从马赛克矩形中随机抽出一个像素点的信息(rgba)
      // 3. 将整个马赛克矩形中的像素点信息对立调成随机抽取的那个(2 中的)
      const size = 5;
      for (let i = 0; i < oldImageData.width/size;i++) {
        // 列的马赛克矩形
        for (let j = 0; j < oldImageData.height/size;j++) {// (i, j) 每一个马赛克矩形的坐标
          /*
            (0, 0)  (0, 0) - (4, 4)    (1, 0)  (5, 0) - (9, 4)
            (0, 1)  (0, 5) - (4, 9)    (1, 1)  (5, 5) - (9, 9)
          */
         // Math.random ---> [0, 1)
         const color = getPXInfo(oldImageData, Math.floor(Math.random()*size + size*i), Math.floor(Math.random()*size + size*j));
         // 循环每一个马赛克矩形  将整个马赛克矩形中的像素点信息对立调成随机抽取的那个(2 中的)
         for (let a = 0; a < size; a++) {for (let b = 0; b < size; b++) {newImageData = setPXInfo(newImageData, size * i + a, size * j + b, color);
            }
         }
        }
      }
      ctx.putImageData(newImageData, image.width, 0);
    }
  }

  // 给定偏移量拿像素点的信息
  function getPXInfo (imageData, x, y) {const color = [];
    const data = imageData.data;
    const width = imageData.width;
    const height = imageData.height;
    const theFirst = (y * width) + x; // 以后像素后面有多少个像素点
    // r
    color[0] = data[theFirst * 4];
    // g
    color[1] = data[theFirst * 4 + 1];
    // b
    color[2] = data[theFirst * 4 + 2];
    //a
    color[3] = data[theFirst * 4 + 3];

    return color;
  }

  function setPXInfo (imageData, x, y, color) {
    const data = imageData.data;
    const width = imageData.width;
    const height = imageData.height;
    const theFirst = (y * width) + x; // 以后像素后面有多少个像素点
    // r
    data[theFirst * 4] = color[0];
    // g
    data[theFirst * 4 + 1] = color[1];
    // b
    data[theFirst * 4 + 2] = color[2];
    //a
    data[theFirst * 4 + 3] = color[3];
    return imageData;
  }
</script>
</html>

局部图解

实现的效果图:

合成

全局通明的的设置

globalAlpha = value(默认值是 1)
    这个属性影响到 canvas 里所有图形的通明的
    有效值范畴 0(齐全通明)到 1(齐全不通明)
    ctx.save();
    ctx.fillStyle = 'red';
    ctx.globalAlpha = .5; // 把画布上的所有内容都透明度都批改了
    ctx.beginPath();
    ctx.fillRect(0, 0, 100, 100);
    ctx.restore();

笼罩合成

多个图像叠加在一起时,如何展现的操作

source: 新的图像源(前面绘制的图像)
destination: 曾经绘制过的图形(指标)

ctx.globalCompositeOperation: (属性值不止下方八种,这八种比拟罕用)
    source-over(默认值): 源在下面,新的图像层级比拟高
    source-in: 只留下源与指标的重叠局部(源的那局部)
    source-out: 只留下源超出指标的局部
    source-atop: 砍掉源溢出的局部
    
    destination-over(默认值): 指标在下面,旧的图像层级比拟高
    destination-in: 只留下源与指标的重叠局部(指标的那局部)
    destination-out: 只留下指标超过源的局部
    destination-atop: 砍掉指标溢出的局部

    ctx.fillStyle = 'darkred';
    ctx.fillRect(0, 0, 100, 100);

    ctx.globalCompositeOperation = 'source-over';

    ctx.fillStyle = 'black';
    ctx.fillRect(50, 50, 100, 100);

下图是 globalCompositeOperation 八种值所对应的效果图

刮刮卡的制作

剖析构造:

底层是背景图片,下面是 canvas
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    * {
      margin: 0;
      padding: 0;
    }
    html, body {
      height: 100%;
      overflow: hidden;
    }
    #wrap, #wrap ul, ul li {height: 100%;}
    ul  li {background-image: url(down.jpg);
      background-size: 100% 100%;
      background-repeat: no-repeat;
    }
    canvas {
      position: absolute;
      top: 0;
      left: 0;
      transition: 1s;
    }
  </style>
</head>
<body>
  <div id='wrap'>
    <canvas></canvas>
    <ul>
      <li></li>
    </ul>
  </div>
  
</body>

<script type="text/javascript">
  window.onload = function() {const canvas = document.querySelector('canvas');
    canvas.width = document.documentElement.clientWidth;// 获取视口的宽度
    canvas.height = document.documentElement.clientHeight;// 获取视口的宽度

    if (canvas.getContext) {const ctx = canvas.getContext('2d');

      const image = new Image();
      image.src = './up.jpg';

      image.onload = function() {draw();
      }

      function draw() {
        let count = 0;
        ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
        ctx.lineCap = 'round';
        ctx.lineJoin = 'round';
        ctx.lineWidth = 40;
        canvas.addEventListener('touchstart', function(e) {
          e = e || window.e;
          const touchC = e.changedTouches[0]; // 挪动端获取手指列表的第一根手指
          const x = touchC.clientX - canvas.offsetLeft;
          const y = touchC.clientY - canvas.offsetTop;
          ctx.save();
          
          ctx.globalCompositeOperation = 'destination-out'; // 只留下指标操过源的局部
          ctx.beginPath();
          // ctx.arc(x, y, 20, 0, 2*Math.PI);
          ctx.moveTo(x, y);
          ctx.lineTo(x + 1, y + 1);
          ctx.stroke();
          ctx.restore();});
        // 手指挪动的时候 必须 touchstart 触发后才会触发
        canvas.addEventListener('touchmove', function(e) {
          e = e || window.event;
          const touchC = e.changedTouches[0]; // 挪动端获取手指列表的第一根手指
          const x = touchC.clientX - canvas.offsetLeft;
          const y = touchC.clientY - canvas.offsetTop;

          ctx.save();
          ctx.globalCompositeOperation = 'destination-out'; // 只留下指标操过源的局部
          
          // ctx.arc(x, y, 20, 0, 2*Math.PI);
          ctx.moveTo(x, y);
          ctx.lineTo(x + 1, y + 1);
          ctx.stroke();

          ctx.restore();});
        canvas.addEventListener('touchend', function(e) {
          e = e || window.event;
          // 当划掉的内容超过一半的时候,把 canvas 那一层有过渡的干掉
          const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);

          const allPx = imageData.width * imageData.height; // 所有的像素点个数
          // 统计像素点是通明的
          for (let i = 0; i < allPx;i++) {if (imageData.data[4*i+3] === 0) {count++;}
          }
          if (count >= allPx/2) {canvas.style.opacity = 0;}

        });
        // 当 canvas 实现过渡时触发
        canvas.addEventListener('transitionend',  function() {this.remove();
        });
      }
    }
  }
</script>
</html>

其余

将画布导出为图像(挪动端)

toDataURL() canvas 元素上的办法
    返回:base64 格局的门路
    

事件操作

ctx.isPointInPath(x, y) 判读在以后门路中是否蕴含检测点
    x 检测点 x 坐标
    y 检测点 y 坐标
此办法只作用于最新画出的 canvas 图像
    ctx.arc(100, 100, 50, 0, 2*Math.PI);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(200, 200, 50, 0, 2*Math.PI);

    ctx.fill();
    canvas.onclick = function(e) {
      // 点击画布时都触发
      e = e || window.event;
      const x = e.clientX - canvas.offsetLeft;
      const y = e.clientY - canvas.offsetTop;

      if (ctx.isPointInPath(x, y)) {alert('点在了最初绘制指标图形上了');
      }

正文完
 0