canvas入门学习草稿纸-合集

canvas入门学习草稿纸1——canvas画线和矩形

HTML中的元素canvas只支持一种原生的图形绘制:矩形。 跟着MDN教程一步一脚印。

先看一个例子,更多例子(持续更新):

canvas画线

使用canvas画线,会用到ctx.stroke()。步骤一般会经历这些:

  1. ctx.beginPath()新开始一个路径
  2. ctx.moveTo(x, y)将笔头移到某个起点
  3. 想象绘制过程,如绘制直线ctx.lineTo(x, y)、绘制圆弧ctx.arc(x, y, r, start, end, anticlockwise)、绘制贝塞尔曲线ctx.quadraticCurveToctx.bezierCurveTo
  4. ctx.stroke()

其他还有些方法,如设置线条颜色ctx.strokeStyle = 'rgba(x,x,x,a)'ctx.closePath首尾闭合,ctx.fill()默认会先闭合,再填充闭合区域等等方法。

这里踩过的注意点坑是:

  1. 笔触不想连起来的话,记得下一次画之前使用ctx.moveTo()
  2. 如果想两次绘制的线样式不同,需要先ctx.beginPath()重制一下

设置线的样式

线有什么样式好设置的?仔细想想也有不少,比如线的:粗细、颜色、类型(点、虚线、实线等)等。需要注意的是,这些属性都是【赋值】所以都是【属性=值】这种写法,而不是调用方法(除了类型)。这些canvas都有涉及提供:

  1. 粗细,ctx.lineWidth
  2. 颜色,ctx.strokeStyle
  3. 类型,ctx.setLineDash()

绘制矩形

和svg不一样,HTML中的元素canvas只支持一种原生的图形绘制:矩形。如果需要绘制其他形状,可以自己用线绘制,连接。

方法:

  1. ctx.strokeRect(x, y, width, height)绘制矩形的框子
  2. ctx.fillRect(x, y, width, height)绘制实心的矩形

canvas入门学习草稿纸2——canvas画复杂的图形

圆形

使用canvas画圆形,ctx.arc()方法: 1. ctx.beginPath()新开始一个路径 2. ctx.moveTo(x, y)将笔头移到圆上 3. ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise): 画一个以(x,y)为圆心的以radius为半径的圆弧(圆),从startAngle开始到endAngle结束,按照anticlockwise给定的方向(默认为顺时针, bool)来生成。 4. ctx.arcTo(x1, y1, x2, y2, radius): 根据给定的控制点和半径画一段圆弧,再以直线连接两个控制点 5. ctx.stroke()画线

arc()函数中表示角的单位是弧度,不是角度。角度与弧度的js表达式: 弧度=(Math.PI/180)*角度。

椭圆

椭圆的画法,有几种:

  1. canvas自带的方法ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)绘制

  2. 利用arc()方法画圆,然后用scale,以下贴一段demo代码

var canvas = document.getElementById("canvas");
canvas.width = 600;
canvas.height = 600;
var ctx = canvas.getContext("2d");
ctx.lineWidth = 10;
drawEllipseWithArcAndScale(ctx, 110, 250, 100, 30, 'orange'); //椭圆

function drawEllipseWithArcAndScale(ctx, cx, cy, rx, ry, style) {
  ctx.save(); // save state
  ctx.beginPath();
  ctx.translate(cx-rx, cy-ry);
  ctx.scale(rx, ry);
  ctx.arc(1, 1, 1, 0, 2 * Math.PI, false);
  ctx.restore(); // restore to original state
  ctx.save();
  if(style) ctx.strokeStyle=style;
  ctx.stroke();
  ctx.restore();
}

这个方法其实是有问题的,1是无法准确的绘制指定的椭圆,2是scale压缩会把压缩地方的线绘制的更粗

  1. 使用Bezier曲线绘制
drawEllipseWithBezier(ctx, 10, 10, 200, 60, 'blue');
function drawEllipseWithBezier(ctx, x, y, w, h, style) {
  var kappa = .5522848,
      ox = (w / 2) * kappa,
      oy = (h / 2) * kappa,
      xe = x + w,
      ye = y + h,
      xm = x + w / 2,
      ym = y + h / 2;

  ctx.save();
  ctx.beginPath();
  ctx.moveTo(x, ym);
  ctx.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
  ctx.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
  ctx.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
  ctx.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
  if(style)
    ctx.strokeStyle=style;
  ctx.stroke();
  ctx.restore();
}

或者另一种,根据中心点去绘制贝塞尔曲线。

drawEllipseWithBezierByCenter(ctx, 110, 110, 200, 60, '#0099ff');
function drawEllipseWithBezierByCenter(ctx, cx, cy, w, h, style) {
  drawEllipseWithBezier(ctx, cx - w/2.0, cy - h/2.0, w, h, style);
}
  1. 使用ctx.quadraticCurveTo()

补充quadraticCurveTo写法:开始点:moveTo(20,20) 控制点:quadraticCurveTo(20,100,200,20) 结束点:quadraticCurveTo(20,100,200,20)


参考文档: 绘制椭圆代码jsbin

多边形

多边形,其实都只是直线的连接。只要计算好每个点的坐标,就能够绘制出来。

其他

如❤️形等。使用贝塞尔曲线绘制吧。

ctx.beginPath()
ctx.moveTo(280, 140)
ctx.bezierCurveTo(260, 120, 260, 150, 280, 170)
ctx.bezierCurveTo(300, 150, 300, 120, 280, 140)
ctx.stroke()

canvas入门学习草稿纸3——canvas色彩

填充色彩

ctx.fillStyle赋值颜色: 1. 支持常见颜色名称,如redblackgray等等 2. 支持十六进制色彩字符串,如:#ffffff白色等 3. rgba()rgb()方法 4. hsla()hsl()色相、饱和度、明度三个通道,来配置颜色

渐变色

线性渐变

canvas本身提供了createLinearGradient的方法来,实现线性渐变,创建渐变-渐变色-应用

var grad = ctx.createLinearGradient(xstart,ystart,xend,yend);
grad.addColorStop(stop,color);  //color参考上面填充色彩

ctx.fillStyle = grad
ctx.strokeStyle = grad

径向渐变

canvas本身提供了createRadialGradient的方法,来实现径向渐变,步骤同线性渐变。

var grad = ctx.createRadialGradient(x0,y0,r0,x1,y1,r1);
grad.addColorStop(stop,color);  //color参考上面填充色彩

ctx.fillStyle = grad
ctx.strokeStyle = grad

canvas入门学习草稿纸4——canvas的2d变换

2d图形变换常常结合定时绘图,可以实现一些比较简单的动画效果,毕竟是封装好的变换方法。但是综合使用下,也能够实现一些较为复杂的效果。常用的变换方法主要有三个:

  1. 平移变换 translate(x,y) 2. 旋转角度变换 rotate(deg) 3. 缩放 scale(sx,sy)

平移

canvas提供了一个平移的基础方法translate(dx,dy),用于将当前的绘制原点进行平行偏移。这里的dx、dy是基于当前的原点位置的偏移。PS.如果当前已经使用过一次平移,再次使用时,是基于当前已偏移的原点,进行二次偏移。如:

ctx.translate(100, 100)
ctx.translate(100, 100) //此时的xy位置都是基于200, 200的xy值

因此,在使用完平移后,建议将原点恢复到0,0,否则定位计算将会变的很乱。。 恢复的方法有两种: 1. 使用负偏移,还原到原点,之前平移了多少,这次就返回多少 2. 使用ctx.save(),ctx.restore()方法来还原

var canvas = document.getElementById("my-canvas")
var ctx = canvas.getContext("2d")

ctx.save()
ctx.fillStyle = "#f00"
ctx.translate(100,100)
ctx.fillRect(100,100,200,100)
ctx.restore() //还原之前保存的坐标原点

ctx.save()
ctx.fillStyle = "#ddd"
ctx.translate(200,200)
ctx.fillRect(100,100,200,100)
ctx.restore()

旋转

旋转rotate(deg)参数是弧度,和arc()里的最后两个参数一样。从纵轴即y轴向上顺时针旋转,注意的是这里的旋转中心点是原点,即坐标(0, 0)。如果想要在你希望的地方作为旋转中心的话,要结合上面说的平移translate()平移原点,然后旋转。同时要注意的是,这里也有上面平移同样的⚠️注意点,原点平移后,记得还原。

deg = 2 * Math.PI / 360 * 角度,之前也有说过,2π就是360度。

如下,写一个简单的旋转动画。

var canvas = document.getElementById("my-canvas")
var ctx = canvas.getContext("2d")

ctx.save()
ctx.fillStyle = "#f00"
ctx.translate(100,100)
ctx.fillRect(100,100,100,100)
ctx.restore()

ctx.save()
ctx.translate(150, 150)
ctx.rotate(0.5 * Math.PI)
ctx.restore()

ctx.save()
ctx.fillStyle = "#ddd"
ctx.translate(300,100)
ctx.fillRect(100,200,200,100)
ctx.restore()

var ani = setInterval((function(){
  var deg = 0
  return function() {
    if(deg < Math.PI){
      ctx.save()
      ctx.translate(500, 350)
      ctx.clearRect(-200, -200, 400, 400)
      ctx.rotate(deg)
      ctx.fillStyle = "#ddd"
      ctx.fillRect(-100,-50,200,100)
      deg += 2 * Math.PI / 360 * 5
      ctx.restore()
    } else {
      ani && clearInterval(ani)
    }
  }
})(), 20)

缩放

缩放变换scale(scalewidth,scaleheight),其中scalewidth、scaleheight分别是【缩放当前绘图的宽度】 和 【缩放当前绘图的高度】的倍数值。 第一次使用起来,觉得有点奇怪。对绘图进行缩放,1. 所有之后的绘图也会被缩放 2.定位也会被缩放 3. 感觉线的粗细这些值也被缩放了

var canvas=document.getElementById("my-canvas")
var ctx=canvas.getContext("2d")

ctx.strokeRect(5,5,25,15)
ctx.scale(2,2)
ctx.strokeRect(5,5,25,15)

transform

写css动画效果时,经常会用到这个transform: translate/rotate/scale...,学习canvas时,会发现都是相似的。canvas也提供了一个transform方法。

/**
 * @param a 水平缩放绘图
 * @param b 水平倾斜绘图
 * @param c 垂直倾斜绘图
 * @param d 垂直缩放绘图
 * @param e 水平移动绘图
 * @param f 垂直移动绘图
 * 
 */
ctx.transform(a,b,c,d,e,f)

就api来看的话,translate平移对应的参数应该是e和f,rotate对应的是b和c,scale对应的是a和d。且注意,transform也会基于之前变换后绘图的基础上来进行变换的。

同时,canvas还提供了另一个方法,setTransform()它时不会基于之前绘图2d变换的基础上变换的

canvas入门学习草稿纸5——canvas文字渲染

对于封装好的渲染功能,(这里想指的是那些不是由计算好定位然后绘制图案的操作,而是由canvas本身提供好的绘制功能。比如这里的文字,文字是自己输入的,而不是自己计算好位置,一横一竖的画出来的功能。),很多都可以和其他的地方进行类比,比如html渲染(包括css的样式控制)
可以类比想象到,如果canvas要实现文字渲染功能,肯定需要支持文字的字体、字号大小、文字颜色、粗细、行高等等基本属性配置,初次之外如果像ps那种立体字、阴影等也有可能会在canvas中支持。
通过学习后,发现确实和其他的渲染有很大的相似性。

通过配置文字属性后,通过ctx.fillTextctx.strokeText来编写文字。

文字基本配置

ctx.font用于配置文字的绝大多数支持配置的属性,如默认值ctx.font='10px sans-serif'就是字体大小和文字的字体等,即css中的font-sizefont-family属性。
通过文档来看,是和css中的font支持的是一样的。ctx.font = 'a b c d e f g h i j k'

按照顺序来说,照着文档来写就行,其实一般情况也用不上那么多。前五个比较常用且常见:

字母 含义 备注
a font-style 规定字体样式normal 可以为normal、italic、oblique
b font-variant 规定字体变体
c font-weight 规定字体的粗细,同css
d font-size / line-height 规定字号和行高,像素
e font-family 规定字体系列

可以通过@font-family加载字体来使用。

文字对齐

canvas的文字对齐配置有垂直和水平对齐配置。

  1. 水平对齐,ctx.textAlign="center|end|left|right|start"
  2. 垂直对齐,ctx.textBaseline="alphabetic|top|hanging|middle|ideographic|bottom"

文字阴影

canvas的阴影shadow不仅仅是对文字有效的,对其他也有效。 - ctx.shadowColor,配置阴影颜色 - ctx.shadowOffsetX,阴影x轴位移。正值向右,负值向左 - ctx.shadowOffsetY,阴影y轴位移。正值向下,负值向上 - ctx.shadowBlur,阴影模糊滤镜

canvas入门学习草稿纸6——canvas 图片处理

添加图片

ctx.drawImage用来将图片绘制到 canvas 上。
此处demo

var canvas = document.getElementById("my-canvas");
var ctx = canvas.getContext("2d");

var img = new Image();
img.src = "../assets/meinv.jpeg";
img.onload = function() {
  ctx.drawImage(img, 0, 0);

  ctx.drawImage(img, 220, 230, 200, 200);

  ctx.translate(240, 260);
  ctx.drawImage(img, 20, 30, 160, 160);
};

语法:

ctx.drawImage(image, dx, dy);
ctx.drawImage(image, dx, dy, dWidth, dHeight);
ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
参数 描述
img 规定要使用的图像、画布或视频。
sx 可选。开始剪切的 x 坐标位置。
sy 可选。开始剪切的 y 坐标位置。
swidth 可选。被剪切图像的宽度。
sheight 可选。被剪切图像的高度。
x 在画布上放置图像的 x 坐标位置。
y 在画布上放置图像的 y 坐标位置。
width 可选。要使用的图像的宽度。(伸展或缩小图像)
height 可选。要使用的图像的高度。(伸展或缩小图像)

图片处理

图片处理,需要先获取图片的数据详情,canvas 提供了获取图片像素数据,ctx.getImageData()方法复制画布上指定矩形的像素数据,处理好像素数据后,然后通过 putImageData() 将图像数据放回画布。

getImageData获取图片像素数据,方法返回 ImageData 对象,是拷贝了画布指定矩形的像素数据。

对于 ImageData 对象中的每个像素,都存在着四方面的信息,即 RGBA 值:

  • R - 红色 (0-255)
  • G - 绿色 (0-255)
  • B - 蓝色 (0-255)
  • A - alpha 通道 (0-255; 0 是透明的,255 是完全可见的)

反色(负片)

反转图片中所有颜色,使用b = 255 - a来计算:

var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.drawImage(img, 0, 0);
var imgData = ctx.getImageData(0, 0, c.width, c.height);
// 反转颜色
for (var i = 0; i < imgData.data.length; i += 4) {
  imgData.data[i] = 255 - imgData.data[i];
  imgData.data[i + 1] = 255 - imgData.data[i + 1];
  imgData.data[i + 2] = 255 - imgData.data[i + 2];
  imgData.data[i + 3] = 255;
}
ctx.putImageData(imgData, 0, 0);

黑白照片

根据公式处理所有颜色数据: Gray = (Red * 0.3 + Green * 0.59 + Blue * 0.11)

ctx.drawImage(img, 0, 0);
var imgData = ctx.getImageData(0, 0, c.width, c.height);
// 反转颜色
for (var i = 0; i < imgData.data.length; i += 4) {
  var red = imgData.data[i],
    green = imgData.data[i + 1],
    blue = imgData.data[i + 2];
  var gray = 0.3 * red + 0.59 * green + 0.11 * blue;
  imgData.data[i] = gray;
  imgData.data[i + 1] = gray;
  imgData.data[i + 2] = gray;
}
ctx.putImageData(imgData, 0, 0);

高斯模糊

咱也不懂,咱就直接按照高斯公式来吧:

G(x,y) = \frac{1}{2\pi\sigma^2}e^(-\frac{x^2+y^2}{2\sigma^2})

摘一段网上的高斯模糊处理图片的数据方法:

function gaussBlur(imgData) {
  console.log(imgData);
  var pixes = imgData.data;
  var width = imgData.width;
  var height = imgData.height;
  var gaussMatrix = [],
    gaussSum = 0,
    x,
    y,
    r,
    g,
    b,
    a,
    i,
    j,
    k,
    len;

  var radius = 30;
  var sigma = 5;

  a = 1 / (Math.sqrt(2 * Math.PI) * sigma);
  b = -1 / (2 * sigma * sigma);
  //生成高斯矩阵
  for (i = 0, x = -radius; x <= radius; x++, i++) {
    g = a * Math.exp(b * x * x);
    gaussMatrix[i] = g;
    gaussSum += g;
  }
  //归一化, 保证高斯矩阵的值在[0,1]之间
  for (i = 0, len = gaussMatrix.length; i < len; i++) {
    gaussMatrix[i] /= gaussSum;
  }
  //x 方向一维高斯运算
  for (y = 0; y < height; y++) {
    for (x = 0; x < width; x++) {
      r = g = b = a = 0;
      gaussSum = 0;
      for (j = -radius; j <= radius; j++) {
        k = x + j;
        if (k >= 0 && k < width) {
          //确保 k 没超出 x 的范围
          //r,g,b,a 四个一组
          i = (y * width + k) * 4;
          r += pixes[i] * gaussMatrix[j + radius];
          g += pixes[i + 1] * gaussMatrix[j + radius];
          b += pixes[i + 2] * gaussMatrix[j + radius];
          // a += pixes[i + 3] * gaussMatrix[j];
          gaussSum += gaussMatrix[j + radius];
        }
      }
      i = (y * width + x) * 4;
      // 除以 gaussSum 是为了消除处于边缘的像素, 高斯运算不足的问题
      // console.log(gaussSum)
      pixes[i] = r / gaussSum;
      pixes[i + 1] = g / gaussSum;
      pixes[i + 2] = b / gaussSum;
      // pixes[i + 3] = a ;
    }
  }
  //y 方向一维高斯运算
  for (x = 0; x < width; x++) {
    for (y = 0; y < height; y++) {
      r = g = b = a = 0;
      gaussSum = 0;
      for (j = -radius; j <= radius; j++) {
        k = y + j;
        if (k >= 0 && k < height) {
          //确保 k 没超出 y 的范围
          i = (k * width + x) * 4;
          r += pixes[i] * gaussMatrix[j + radius];
          g += pixes[i + 1] * gaussMatrix[j + radius];
          b += pixes[i + 2] * gaussMatrix[j + radius];
          // a += pixes[i + 3] * gaussMatrix[j];
          gaussSum += gaussMatrix[j + radius];
        }
      }
      i = (y * width + x) * 4;
      pixes[i] = r / gaussSum;
      pixes[i + 1] = g / gaussSum;
      pixes[i + 2] = b / gaussSum;
    }
  }
  console.log(imgData);
  return imgData;
}

canvas入门学习草稿纸7——canvas的实际应用

To be continued...