关于前端:Canvas-从进阶到退学

47次阅读

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

本文简介

点赞 + 关注 + 珍藏 = 学会了

接着《Canvas 从入门到劝敌人放弃(图解版)》,本文持续补充 canvas 根底知识点。

这次我不手绘了!

本文会波及到 canvas 的常识包含:变形、像素管制、突变、暗影、门路

变形

这里说的变形是基于画布,全局进行变形。

变形次要包含:平移 translate缩放 scale旋转操作 rotate

除了对应的办法外,还能够应用 transformsetTransform 对下面三种操作进行配置,这称为“变换矩阵”。

在学习“变形”之前,须要理解 W3C 坐标系

箭头所指是各轴本人的正方向,x 轴越往右(正方向)值越大,y 轴越往下(正方向)值越大。

平移

应用 translate() 办法能够实现平移成果(位移)。

translate(x, y) 接管 2 个参数,第一个参数代表 x 轴方向位移间隔,第二个参数代表 y 轴方向位移间隔。

负数代表向正方向位移,正数代表向反方向位移。

演示平移成果之前,我先创立一个矩形,长宽都是 100,地位在画布的原点 (0, 0),也就是紧贴画布的左上角。

<canvas id="c" style="border: 1px solid #ccc;"></canvas>

<script>
  const context = document.getElementById('c')
  const ctx = context.getContext('2d')

  // 紧贴原点的矩形,默认是彩色[]
  ctx.fillRect(0, 0, 100, 100)
</script>

如果此时在 fillRect 之前设置 translate 就能够实现整个画布位移的成果。

// 省略局部代码

// 平移,往右平移 10,往下平移 20
ctx.translate(10, 20)

// 渲染矩形
ctx.fillRect(0, 0, 100, 100)

从上图能够看出,矩形间隔画布顶部的间隔是 20,间隔画布左侧的间隔是 10。

留神:平移 translate() 要写在“绘制操作(本例是 fillRect)”之前才无效。

如果在应用 translate 的前后都有渲染操作,画布会屡次渲染,并不会主动清屏。

比方这样

<canvas id="c" style="border: 1px solid #ccc;"></canvas>

<script>
  const context = document.getElementById('c')
  const ctx = context.getContext('2d')

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

  ctx.translate(10, 20)

  ctx.fillRect(0, 0, 100, 100)
</script>

再做个显著点的成果,每秒平移一次

<canvas id="c" style="border: 1px solid #ccc;"></canvas>

<script>
  const context = document.getElementById('c')
  const ctx = context.getContext('2d')

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

  setInterval(() => {ctx.translate(10, 20)
    ctx.fillRect(0, 0, 100, 100)
  }, 1000)

</script>

能够看出,每次应用 translate() 平移画布,都会基于上一次画布所在的地位进行平移。

上图成果是 canvas 的默认成果,所以在执行 translate 之前能够执行“清屏操作”。

清屏

<canvas id="c" style="border: 1px solid #ccc;"></canvas>

<script>
  const context = document.getElementById('c')
  const ctx = context.getContext('2d')

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

  setInterval(() => {ctx.clearRect(0, 0, context.width, context.height)
    ctx.translate(10, 20)
    ctx.fillRect(0, 0, 100, 100)
  }, 1000)

</script>

缩放

缩放画布用到的办法是 scale(x, y),接管 2 个参数,第一个参数是 x 轴方向的缩放,第二个参数是 y 轴方向的缩放。

x 或者 y 的值是 0 ~ 1 时代表放大,比方取值为 0.5 时,示意比本来放大一半;值为 2 时,比本来放大一倍。

<canvas id="c" style="border: 1px solid #ccc;"></canvas>

<script>
  const context = document.getElementById('c')
  const ctx = context.getContext('2d')

  ctx.font = '60px Arial'
  ctx.strokeStyle = 'hotpink'
  ctx.strokeText('雷猴', 40, 100)

  // 放大
  ctx.scale(0.5, 0.5)
  
  // 从新渲染
  ctx.strokeText('雷猴', 40, 100)
</script>

scale() 办法同样会保留本来曾经渲染的内容。

如果不须要保留本来内容,能够应用“清屏操作”。

留神:scale() 会以上一次缩放为基准进行下一次缩放。

副作用:

其实从下面的例子就能够看出 scale() 存在一点副作用的,从图中能够看出,缩放后文本的左上角坐标产生了“位移”,文本描边粗细也产生了变动。

尽管说是副作用,但也很容易了解,整块画布缩放了,对应的坐标比例其实也跟着缩放嘛。

旋转

应用 rotate(angle) 办法能够旋转画布,但默认的旋转原点是画布的左上角,也就是 (0, 0) 坐标。

我计算旋转角度通常是用 角度 * Math.PI / 180 的形式示意。

尽管这样书写代码看上去很长,但习惯后就比拟直观的看出要旋转多少度。

rotate(angle) 中的参数 angle 代表角度,angle 的取值范畴是 -Math.PI * 2 ~ Math.pi * 2

当旋转角度小于 0 时,画布逆时针旋转;反之顺时针旋转。

<canvas id="c" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const context = document.getElementById('c')
  const ctx = context.getContext('2d')

  ctx.font = '60px Arial'
  ctx.strokeStyle = 'pink'
  ctx.strokeText('雷猴', 40, 100)

  // 旋转 45°
  ctx.rotate(45 * Math.PI / 180)
  
  // 从新渲染
  ctx.strokeText('雷猴', 40, 100)
</script>

批改原点

如果须要批改旋转核心,能够应用 translate() 办法平移画布,通过计算挪动到指定地位。

<canvas id="c" style="border: 1px solid #ccc;"></canvas>

<script>
  const context = document.getElementById('c')
  const ctx = context.getContext('2d')

  ctx.font = '60px Arial'
  ctx.strokeStyle = 'pink'
  ctx.strokeText('雷猴', 40, 100)

  // 设置旋转核心
  ctx.translate(90, -50)

  // 旋转
  ctx.rotate(45 * Math.PI / 180)
  
  // 从新渲染
  ctx.strokeText('雷猴', 40, 100)
</script>

变换矩阵

变换矩阵罕用办法有 transform()setTransform() 两个办法。

变换矩阵是一个略微进阶一点的常识了,别怕!

后面的 平移 translate缩放 scale旋转操作 rotate 能够说都是 transform() 的“语法糖”。

变换矩阵曾经波及到一点数学知识了,但本文不会讲到这些常识,只会讲讲 transform() 是怎么用的。

transform

transform() 一个办法就能够实现 平移、缩放、旋转 三种性能,它接管 6 个参数。

transform(a, b, c, d, e, f)

  • a: 程度缩放(x 轴方向),默认值是 1;
  • b: 程度歪斜(x 轴方向),默认值是 0;
  • c: 垂直歪斜(y 轴方向),默认值是 0;
  • d: 垂直缩放(y 轴方向),默认值是 1;
  • e: 程度挪动(x 轴方向),默认值是 0;
  • f: 垂直挪动(y 轴方向),默认值是 0;

这默认值看上去很乱,但如果这样排列一下是不是就比拟容易了解了:

$$
\begin{pmatrix}a & c & e \\\\ b & d & f \\\\ 0 & 0 & 1 \end{pmatrix}
$$

轻易批改几个值试试成果:

<canvas id="c" width="400" height="400" style="border: 1px solid #ccc;"></canvas>

<script>
  const context = document.getElementById('c')
  const ctx = context.getContext('2d')

  // 变换矩阵
  ctx.transform(1, 1, 1, 2, 30, 40)

  // 绘制矩形
  ctx.fillRect(10, 10, 100, 100)
</script>

setTransform

setTransform(a, b, c, d, e, f) 同样接管 6 个参数,和 transform() 一样

<canvas id="c" width="400" height="400" style="border: 1px solid #ccc;"></canvas>

<script>
  const context = document.getElementById('c')
  const ctx = context.getContext('2d')

  // 变换矩阵
  ctx.setTransform(2, 1, 1, 2, 20, 10)

  // 绘制矩形
  ctx.fillRect(10, 10, 100, 100)
</script>

transform 和 setTransform 的区别

transform() 每次执行都会参考上一次变换后的后果

比方上面这个屡次执行的状况:

<canvas id="c" width="400" height="400" style="border: 1px solid #ccc;"></canvas>

<script>
  const context = document.getElementById('c')
  const ctx = context.getContext('2d')
  
  ctx.fillStyle = 'rgba(10, 10, 10, 0.2)'
    
  ctx.fillRect(10, 10, 100, 100)

  ctx.transform(1, 0, 0, 1, 10, 10)
  ctx.fillRect(10, 10, 100, 100)

  ctx.transform(1, 0, 0, 1, 10, 10)
  ctx.fillRect(10, 10, 100, 100)

  ctx.transform(1, 0, 0, 1, 10, 10)
  ctx.fillRect(10, 10, 100, 100)

  ctx.transform(1, 0, 0, 1, 10, 10)
  ctx.fillRect(10, 10, 100, 100)

</script>

setTransform() 每次调用都会基于最原始是状态进行变换。

<canvas id="c" width="400" height="400" style="border: 1px solid #ccc;"></canvas>

<script>
  const context = document.getElementById('c')
  const ctx = context.getContext('2d')

  ctx.fillStyle = 'rgba(10, 10, 10, 0.2)'

  ctx.fillRect(10, 10, 100, 100)

  ctx.setTransform(1, 0, 0, 1, 10, 10)
  ctx.fillRect(10, 10, 100, 100)

  ctx.setTransform(1, 0, 0, 1, 10, 10)
  ctx.fillRect(10, 10, 100, 100)

  ctx.setTransform(1, 0, 0, 1, 10, 10)
  ctx.fillRect(10, 10, 100, 100)

  ctx.setTransform(1, 0, 0, 1, 10, 10)
  ctx.fillRect(10, 10, 100, 100)

</script>

不论扭转多少次,setTransform() 都会参考原始状态进行变换。

管制像素

位图是由像素点组成的,canvas 提供了几个 api 能够操作图片中的像素。

很多工具网站也罕用接下来说到的几个 api 做图片滤镜。

须要留神的是,canvas 提供的操作像素的办法,必须应用服务器能力运行起来,不然没有成果的。

能够搭建本地服务器运行本文案例,办法有很多种。

比方你应用 Vue 或者 React 的脚手架搭建的我的项目,运行后就能跑起本文所有案例。

又或者应用 http-server 启动本地服务。

getImageData()

首先要介绍的是 getImageData() 办法,这个办法能够获取指定区域内的所有像素。

getImageData(x, y, width, height) 接管 4 个参数,这 4 个参数示意选区范畴。

xy 代表选区的左上角坐标,width 示意选区宽度,height 示意选区高度。

还是举例说明比较清楚。下图渲染到画布上的是我的猫Bubble

<canvas id="c" width="400" height="400" style="border: 1px solid #ccc;"></canvas>

<script>
  const context = document.getElementById('c')
  const ctx = context.getContext('2d')

  const img = new Image() // 创立图片对象
  img.src = './bubble.jpg' // 加载本地图片

  // 图片加载实现后在执行其余操作
  img.onload = () => {
    // 渲染图片
    ctx.drawImage(img, 0, 0)
    // 获取图片信息
    const imageData = ctx.getImageData(0, 0, img.width, img.height)
    console.log(imageData)
  }

</script>

打印进去的信息能够点开大图看看

  • data: 图片像素数据集,以数组的模式寄存,这是本文要讲的重点,须要关注!
  • colorSpace: 图片应用的色调规范,这个属性在 Chrome 里有打印进去,Firefox 里没打印。不重要~
  • height: 图片高度
  • width: 图片宽度

通过 getImageData() 获取到的信息中,须要重点关注的是 data,它是一个一维数组,仔细观察发现外面的值没一个是大于 255 的,也不会小于 0。

其实 data 属性里记录了图片每个像素的 rgba 值别离是多少。

  • r 代表红色
  • g 代表绿色
  • b 代表蓝色
  • a 透明度

这个和 CSS 里的 rgba 是同一个意思。

data 里,4 个元素记录 1 个像素的信息。也就是说,1 个像素是由 rgba 4 个元素组成。而且每个元素的取值范畴是 0 – 255 的整数。

 data: **[r1, g1, b1, a1, r2, g2, b2, a2, ......]** 
像素点 色彩通道
imgData.data[0] 49 红色 r
imgData.data[1] 47 绿色 g
imgData.data[2] 51 蓝色 b
imgData.data[3] 255 透明度 a
…… …… ……
imgData.data[n-4] 206 红色 r
imgData.data[n-2] 200 绿色 g
imgData.data[n-3] 200 蓝色 b
imgData.data[n-1] 255 透明度 a

如果一张图只有 10 个像素,通过 getImageData() 获取到的 data 信息中就有 40 个元素。

putImageData()

putImageData(imageData, x, y) 能够将 ImageData 对象的数据(图片像素数据)绘制到画布上。

putImageData(imgData, x, y, dirtyX, dirtyY, dirtyWidth, dirtyHeight) 也能够接管更多参数。

  • imageData: 规定要放回画布的 ImageData 对象
  • x: ImageData 对象左上角的 x 坐标,以像素计
  • y: ImageData 对象左上角的 y 坐标,以像素计
  • dirtyX: 可选。程度值(x),以像素计,在画布上搁置图像的地位
  • dirtyY: 可选。程度值(y),以像素计,在画布上搁置图像的地位
  • dirtyWidth: 可选。在画布上绘制图像所应用的宽度
  • dirtyHeight: 可选。在画布上绘制图像所应用的高度

比方,我要将图片复制到另一个地位

<canvas id="c" width="500" height="500" style="border: 1px solid #ccc;"></canvas>

<script>
  const context = document.getElementById('c')
  const ctx = context.getContext('2d')

  const img = new Image() // 创立图片对象
  img.src = './bubble.jpg' // 加载本地图片

  // 图片加载实现后在执行其余操作
  img.onload = () => {
    // 渲染图片
    ctx.drawImage(img, 0, 0)
    // 获取图片信息
    const imageData = ctx.getImageData(0, 0, img.width, img.height)

    // 将图片对象输入到 (100, 100) 的地位上
    ctx.putImageData(imageData, 100, 100)
  }

</script>

能够实现复制的成果。

通明

晓得后面两个 api 就能够实现通明成果了。

后面讲到,通过 getImageData() 获取的是一个数组类型的数据,每 4 个元素代表 1 个像素,就是rgba,而 a 示意通明通道,所以只需批改每组像素的最初 1 个元素的值,就能批改图片的不透明度。

<canvas id="c" width="500" height="500" style="border: 1px solid #ccc;"></canvas>

<script>
  const context = document.getElementById('c')
  const ctx = context.getContext('2d')

  const img = new Image() // 创立图片对象
  img.src = './bubble.jpg' // 加载本地图片

  // 图片加载实现后在执行其余操作
  img.onload = () => {
    // 渲染图片
    ctx.drawImage(img, 0, 0)
    // 获取图片信息
    const imageData = ctx.getImageData(0, 0, img.width, img.height)

    for (let i = 0; i < imageData.data.length; i += 4) {imageData.data[i + 3] = imageData.data[i + 3] * 0.5
    }

    // 将图片对象输入到 (100, 100) 的地位上
    ctx.putImageData(imageData, 100, 100)
  }

</script>

滤镜

要做不同的滤镜成果,其实就是通过不同的算法去操作每个像素的值,我在《Canvas 10 款根底滤镜(原理篇)》讲到相干常识,有趣味的工友能够点进去看看

突变

csssvg 里都有突变,canvas 必定也不会缺失这个能力啦。

canvas 提供了 线性突变 createLinearGradient径向突变 createRadialGradient

线性突变 createLinearGradient

canvas 中应用线性突变步骤如下:

  1. 创立线性突变对象: createLinearGradient(x1, y1, x2, y2)
  2. 增加突变色彩: addColorStop(stop, color)
  3. 设置填充色或描边色彩: fillStylestrokeStyle

createLinearGradient(x1, y1, x2, y2)

createLinearGradient(x1, y1, x2, y2) 中,x1, y1 示意突变的起始地位,x2, y2 示意突变的完结地位。

比方程度方向的从左往右的线性突变,此时的 y1y2 的值是一样的。

两个点就能够确定一个突变方向。

addColorStop(stop, color)

addColorStop(stop, color) 办法能够增加渐变色。

第一个参数 stop 示意渐变色地位的偏移量,取值范畴是 0 ~ 1。

第二个参数 color 示意色彩。

填充突变

理论编码演示一下

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const context = document.getElementById('c')
  const ctx = context.getContext('2d')

  // 1. 创立线性突变对象
  const lgrd = ctx.createLinearGradient(10, 10, 200, 10)

  // 2. 增加突变色彩
  lgrd.addColorStop(0, 'pink')
  lgrd.addColorStop(1, 'yellow')

  // 设置填充色
  ctx.fillStyle = lgrd

  // 创立矩形,填充
  ctx.fillRect(10, 10, 200, 200)
</script>

如果想批改突变的方向,只需在应用 createLinearGradient() 时设置好终点和起点坐标即可。

除了填充色,描边突变和文本突变同样能够做到。

描边突变

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const context = document.getElementById('c')
  const ctx = context.getContext('2d')

  const lgrd = ctx.createLinearGradient(10, 10, 200, 10)

  lgrd.addColorStop(0, 'pink')
  lgrd.addColorStop(1, 'yellow')

  ctx.strokeStyle  = lgrd
  ctx.lineWidth = 10
  ctx.strokeRect(10, 10, 200, 200)

</script>

文本突变

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const context = document.getElementById('c')
  const ctx = context.getContext('2d')

  const lgrd = ctx.createLinearGradient(10, 10, 200, 10)

  lgrd.addColorStop(0, 'pink')
  lgrd.addColorStop(1, 'yellow')

  const text = '雷猴'
  ctx.font = 'bold 100px 黑体'
  ctx.fillStyle = lgrd
  ctx.fillText(text, 10, 100)
</script>

多色线性突变

在 0 ~ 1 的范畴内,addColorStop 能够设置多个色彩在不同的地位上。

// 省略局部代码
lgrd.addColorStop(0, '#2a9d8f') // 绿色
lgrd.addColorStop(0.5, '#e9c46a') // 黄色
lgrd.addColorStop(1, '#f4a261') // 橙色

径向突变 createRadialGradient

径向突变是从一个点到另一个点扩散进来的突变,是圆形(椭圆也能够)突变。

间接看成果

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const context = document.getElementById('c')
  const ctx = context.getContext('2d')

  const rgrd = ctx.createRadialGradient(70, 70, 0, 70, 70, 60)
  rgrd.addColorStop(0, 'yellow')
  rgrd.addColorStop(1, 'pink')
  ctx.fillStyle = rgrd

  ctx.fillRect(10, 10, 120, 120)
</script>

createRadialGradient 能够创立一个径向突变的对象。应用步骤和 createLinearGradient 一样,但参数不同。

createRadialGradient(x1, y1, r1, x2, y2, r2)

  • x1, y1: 突变开始的圆心坐标
  • r1: 突变开始的圆心半径
  • x2, y2: 突变完结的圆心坐标
  • r2: 突变完结的圆心半径

同样应用 addColorStop 设置突变色彩,同样反对多色突变。

突变的注意事项

突变的定位坐标是参照画布的,超出定位的局部会应用最邻近的那个色彩。

我用线性突变举例。

<canvas id="c" width="600" height="600" style="border: 1px solid #ccc;"></canvas>

<script>
  const context = document.getElementById('c')
  const ctx = context.getContext('2d')

  const lgrd = ctx.createLinearGradient(200, 0, 400, 400)

  lgrd.addColorStop(0, 'pink')
  lgrd.addColorStop(1, 'yellow')

  ctx.fillStyle = lgrd

  ctx.fillRect(10, 10, 160, 160)

  ctx.fillRect(220, 10, 160, 160)

  ctx.fillRect(430, 10, 160, 160)

  ctx.fillRect(10, 210, 160, 160)

  ctx.fillRect(220, 210, 160, 160)

  ctx.fillRect(430, 210, 160, 160)

  ctx.fillRect(10, 430, 160, 160)

  ctx.fillRect(220, 430, 160, 160)

  ctx.fillRect(430, 430, 160, 160)

</script>

下面的例子中,我只创立了一个突变,而后创立了 9 个正方形。

此时正方形的填充色取决于呈现在画布中的地位。

能够批改一下 createLinearGradient() 的定位数据对照了解。

// 省略局部代码
const lgrd = ctx.createLinearGradient(200, 0, 400, 400)

如果想每个图形都有本人的渐变色,这须要定制化配置,每个创立每个图形之前都独自创立一个渐变色。

<canvas id="c" width="600" height="600" style="border: 1px solid #ccc;"></canvas>

<script>
  const context = document.getElementById('c')
  const ctx = context.getContext('2d')

  // 粉 - 黄 突变
  const lgrd1 = ctx.createLinearGradient(10, 10, 160, 160)
  lgrd1.addColorStop(0, 'pink')
  lgrd1.addColorStop(1, 'yellow')
  ctx.fillStyle = lgrd1
  ctx.fillRect(10, 10, 160, 160)

  // 橘黄 - 蓝紫 突变
  const lgrd2 = ctx.createLinearGradient(210, 10, 380, 160)
  lgrd2.addColorStop(0, 'bisque')
  lgrd2.addColorStop(1, 'blueviolet')
  ctx.fillStyle = lgrd2
  ctx.fillRect(220, 10, 160, 160)
</script>

所以不论是填充色还是秒变色彩,每个元素最好都本人从新设定一下。不然可能会呈现意想不到的成果~

暗影

暗影在前端也是很罕用的特效。依稀记得当年还用 png 做暗影成果

canvas 中,和暗影相干的属性次要有以下 4 个:

  • shadowOffsetX: 设置或返回暗影与形态的程度间隔。默认值是 0。大于 0 时向正方向偏移。
  • shadowOffsetY: 设置或返回暗影与形态的垂直距离。默认值是 0。大于 0 时向正方向偏移。
  • shadowColor: 设置或返回用于暗影的色彩。默认彩色。
  • shadowBlur: 设置或返回用于暗影的含糊级别。默认值是 0,数值越大含糊度越强。

置信应用过 css 暗影属性的工友,了解起 canvas 暗影也会十分轻松。

<canvas id="c" width="300" height="200" style="border: 1px solid #ccc;"></canvas>

<script>
  const context = document.getElementById('c')
  const ctx = context.getContext('2d')

  ctx.shadowOffsetX = 10 // x 轴偏移量
  ctx.shadowOffsetY = 10 // y 轴偏移量
  ctx.shadowColor = '#f38181' // 暗影色彩
  ctx.shadowBlur = 10 // 暗影含糊度,默认 0

  ctx.fillStyle = '#fce38a' // 填充色
  ctx.fillRect(30, 30, 200, 100)

  console.log(ctx.shadowOffsetX) // 输入暗影 x 轴方向的偏移量:10
</script>

除了图形外,文本和图片都能够设置暗影成果。

<canvas id="c" width="300" height="200" style="border: 1px solid #ccc;"></canvas>

<script>
  const context = document.getElementById('c')
  const ctx = context.getContext('2d')

  ctx.shadowOffsetX = 10 // x 轴偏移量
  ctx.shadowOffsetY = 10 // y 轴偏移量
  ctx.shadowColor = '#b83b5e' // 暗影色彩
  ctx.shadowBlur = 10 // 暗影含糊度,默认 0

  const text = '雷猴'
  ctx.font = 'bold 100px 黑体'
  ctx.fillStyle = '#fce38a'
  ctx.fillText(text, 10, 100)
</script>

门路

在 Canvas 从入门到劝敌人放弃(图解版)—— 新开门路 中我讲到 新开门路 敞开门路 的用法,本节会在上篇的根底上丰盛更多应用细节。

本节要讲的是

  • beginPath(): 新开门路
  • closePath(): 敞开门路
  • isPointInPath(): 判断某个点是否在以后门路内

beginPath()

beginPath() 办法是用来开拓一条新的门路,这个办法会将以后门路之中的所有子门路都革除掉,以此来重置以后门路。

如果你的画布上有几个根底图形(直线、多边形、圆形、弧、贝塞尔曲线),如果款式相互之间受到影响,那你能够立即想想在绘制新图形之前是不是忘了应用 beginPath()

先举几个例子阐明一下。

净化:色彩、线条粗细受到净化

前面的款式笼罩了后面的款式。

<canvas id="c" width="300" height="200" style="border: 1px solid #ccc;"></canvas>

<script>
  const context = document.getElementById('c')
  const ctx = context.getContext('2d')

  // 第一条线,粉色
  ctx.moveTo(50, 40)
  ctx.lineTo(150, 40)
  ctx.strokeStyle = 'pink' // 粉色描边
  ctx.stroke()

  // 第二条线,红色
  ctx.moveTo(50, 80)
  ctx.lineTo(150, 80)
  ctx.strokeStyle = 'red' // 红色描边
  ctx.lineWidth = 10 // 外表粗细
  ctx.stroke()
</script>

净化:图形门路净化

比方画布上有一条直线和一个圆形,不应用 beginPath() 开拓新门路的话,它们可能会“打架”。

<canvas id="c" width="300" height="200" style="border: 1px solid #ccc;"></canvas>

<script>
  const context = document.getElementById('c')
  const ctx = context.getContext('2d')

  // 第一条线,粉色
  ctx.moveTo(50, 40)
  ctx.lineTo(150, 40)
  ctx.strokeStyle = 'pink' // 粉色描边
  ctx.stroke()

  // 圆形
  ctx.arc(150, 120, 40, 0, 360 * Math.PI / 180)
  ctx.lineWidth = 4
  ctx.stroke()
</script>

明明直线和圆形是没有交加的,为什么会有一条歪斜的线把两个元素连接起来?

解决办法

除了下面两种状况外,可能还有其余更加奇怪的状况(像极喝醉了假酒),都能够先思考是不是要应用 beginPath()

比方这样做。

<canvas id="c" width="300" height="200" style="border: 1px solid #ccc;"></canvas>

<script>
  const context = document.getElementById('c')
  const ctx = context.getContext('2d')

  // 第一条线,粉色
  ctx.moveTo(50, 40)
  ctx.lineTo(150, 40)
  ctx.strokeStyle = 'pink' // 粉色描边
  ctx.lineWidth = 10
  ctx.stroke()

  // 圆形
  ctx.beginPath() // 开拓新的门路
  ctx.arc(150, 120, 40, 0, 360 * Math.PI / 180)
  ctx.strokeStyle = 'skyblue' // 蓝色描边
  ctx.lineWidth = 4
  ctx.stroke()
</script>

在应用 arc 或者 moveTo 办法之前加上一句 ctx.beginPath() 就能够无效解决以上问题。

这个例子中,如果没用 ctx.beginPath()canvas 就会认为 线 和 圆形 都属于同一个门路,所以在画圆形时,下笔的时候就会把线的“完结点”和圆的“终点”相连起来。

stroke()fill() 都是以最近的 beginPath() 前面所定义的状态款式为根底进行绘制的。

注意事项

后面的款式会笼罩前面元素的默认款式,即便应用了 beginPath()

<canvas id="c" width="300" height="200" style="border: 1px solid #ccc;"></canvas>

<script>
  const context = document.getElementById('c')
  const ctx = context.getContext('2d')

  // 第一条线,粉色
  ctx.moveTo(50, 40)
  ctx.lineTo(150, 40)
  ctx.strokeStyle = 'pink' // 粉色描边
  ctx.lineWidth = 10 // 外表粗细
  ctx.stroke()

  // 第二条线,红色
  ctx.beginPath()
  ctx.moveTo(50, 80)
  ctx.lineTo(150, 80)
  ctx.stroke()
</script>

第一条先设置了 strokeStylelineWidth,第二条线并没有设置这两个属性,即便在绘制第二条线的开始时应用了 ctx.beginPath(),第二条线也会应用第一条线的 strokeStylelineWidth。除非第二条线本人也有设置这两个属性,不然就会沿用之前的配置项。

“ 非凡状况 ”

还要补充一个“非凡状况”。

<canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

<script>
  const cnv = document.getElementById('c')
  const ctx = cnv.getContext('2d')

  // 第一条线,粉色
  ctx.moveTo(50, 30)
  ctx.lineTo(150, 30)
  ctx.strokeStyle = 'pink' // 粉色描边
  ctx.lineWidth = 10 // 描边粗细
  ctx.stroke()

  // 矩形
  ctx.strokeStyle = 'red' // 红色描边
  ctx.strokeRect(50, 50, 200, 100)
</script>

这个例子中,绘制矩形 rect 前并没有用 beginPath(),但矩形的红色描边并没有影响直线的粉色描边。

其实还不止 strokeRect(),还有 fillRect()strokeText()fillText() 都不会影响其余图形,这些办法都只会绘制图形,不会影响本来门路。

closePath()

closePath() 办法能够敞开以后门路,它能够显示关闭某段凋谢的门路。这个办法罕用于敞开圆弧门路或者由圆弧、线段创立进去的凋谢门路。

closePath() 是敞开门路,并不是完结门路。

敞开门路,指的是连贯终点与起点,也就是可能主动关闭图形。

完结门路,指的是开始新的门路。

根底用法

举个例子会更直观

<canvas id="c" width="300" height="200" style="border: 1px solid #ccc;"></canvas>

<script>
  const context = document.getElementById('c')
  const ctx = context.getContext('2d')

  ctx.moveTo(50, 40)
  ctx.lineTo(150, 40)
  ctx.lineTo(150, 140)
  ctx.stroke()
</script>

下面的代码通过 moveTolineTo 画了 3 个点,应用 stroke() 办法把这 3 个点连起来,就造成了上图成果。

但如果此时在 stroke() 前应用 closePath() 办法,最终进去的门路将主动闭合(将终点和起点连接起来)。

<canvas id="c" width="300" height="200" style="border: 1px solid #ccc;"></canvas>

<script>
  const context = document.getElementById('c')
  const ctx = context.getContext('2d')

  ctx.moveTo(50, 40)
  ctx.lineTo(150, 40)
  ctx.lineTo(150, 140)
  ctx.closePath() // 敞开门路
  ctx.stroke()
</script>

注意事项

看到下面的例子后,可能有些工友会感觉应用 ctx.lineTo(50, 40) 连贯回终点也有同样成果。

// 省略局部代码
ctx.moveTo(50, 40)
ctx.lineTo(150, 40)
ctx.lineTo(150, 140)
ctx.lineTo(50, 40)
ctx.stroke()

的确在描边为 1 像素时看上去成果是差不多的,但如果此时将 lineWidth 的值设置得大一点,就能看到显著区别。

// 省略局部代码
ctx.lineWidth = 10
ctx.moveTo(50, 40)
ctx.lineTo(150, 40)
ctx.lineTo(150, 140)
ctx.lineTo(50, 40) // 连贯回终点
ctx.stroke()

如果用 closePath() 主动闭合门路的话,会呈现以下成果

// 省略局部代码
ctx.lineWidth = 10
ctx.moveTo(50, 40)
ctx.lineTo(150, 40)
ctx.lineTo(150, 140)
ctx.closePath() // 敞开门路
ctx.stroke()

本文到此就完结了,但 canvas 的知识点还没完,还有很多很多,基本学不完的那种。

接下来 本专栏 的文章会偏差于 知识点 + 案例 的形式解说 canvas

代码仓库

⭐雷猴 Canvas

举荐浏览

👍《Canvas 从入门到劝敌人放弃(图解版)》

👍《Canvas 10 款根底滤镜(原理篇)》

👍《Fabric.js 从入门到收缩》

👍《『Three.js』腾飞!》

👍《p5.js 光速入门》

👍《SVG 从入门到悔恨,怎么不早点学起来(图解版)》

点赞 + 关注 + 珍藏 = 学会了
代码仓库

正文完
 0