Skip to content

canvas 学习

两个矩形

首先设置画布的宽高都为 150 像素,如案例所示,红色边框矩形为我们设置的画布。

画矩形,就是先找到矩形的左上角顶点位置,然后固定宽和高。

二维画布的坐标系的原点在画布的左上角,水平向右为 x 轴,垂直向下为 y 轴。

下面代码使用到了 fillRect(x, y, width, height) 方法,类似的方法还有 strokeRect(x, y, width, height)clearRect(x, y, width, height)。它们的作用分别是绘制一个矩形边框、清除矩形区域。

案例代码:

vue
<script setup lang="ts">
import { onMounted } from 'vue';

onMounted(() => {
  // 1. 首先我们要拿到画布
  const canvas = document.getElementById('two-rectangle') as HTMLCanvasElement
  // 以此判断浏览器 js 是否支持 canvas。
  if (canvas.getContext) {
    // 2. 再拿到画笔
    const ctx = canvas.getContext('2d')

    // 3. 设置填充样式为纯红色
    // 还可以设置透明度
    // ctx!.fillStyle = "rgba(255, 0, 0)"
    ctx!.fillStyle = "rgba(255, 0, 0, 0.4)"
    // 4. 绘制一个矩形,并填充上色。位置在画布的左上角,宽高都为 60 像素
    // 将 ctx.rect 和 ctx.fill 合并为一个方法,就变成了 ctx.fillRect
    // fillRect 入参及类型
    // (method) CanvasRect.fillRect(x: number, y: number, w: number, h: number): void
    ctx!.fillRect(0, 0, 60, 60)
    
    // 5. 重复 3 和 4 步骤,在右下脚画一个纯蓝色的矩形
    ctx!.fillStyle = "rgba(0, 0, 255, 1)"
    ctx!.fillRect(90, 90, 60, 60)
  }
})
</script>
<template>
  <div class="border-t-1 border-b-1 border-l-none border-r-none border-solid border-t-gray-700 border-b-gray-700 pa-4 my-4">
    <!-- 如果不设置 width 和 height,默认宽 300 像素,高 150 像素。 -->
    <!-- 也可以可以用 css 设置宽高,原理类似与缩放画布。 -->
    <canvas id="two-rectangle" width="150" height="150" class="border border-solid border-red">
      <!-- 可以定义替换内容。如果有浏览器不认识 canvas 标签(比如 ie6),那么就会忽略它并显示它包含的内容。 -->
      不支持 canvas,请换个浏览器~
    <!-- 结束标签不可以省略,否则不会显示。 -->
    </canvas>
  </div>
</template>

镂空矩形

源代码:

vue
<script setup lang="ts">
import { onMounted } from 'vue';

onMounted(() => {
  const canvas = document.getElementById('hollow-rectangle') as HTMLCanvasElement
  const ctx = canvas.getContext('2d')

  // 默认填充颜色为黑色
  ctx!.fillRect(0, 0, 150, 150)
  // 在绘制并填充之后设置填充颜色,设置无效
  // ctx!.fillStyle = 'rgb(0, 255, 0)'

  // 清空中间绘制区域
  ctx!.clearRect(20, 20, 110, 110)

  // 画一个矩形轮廓
  ctx!.strokeRect(30, 30, 90, 90)
})
</script>
<template>
  <div class="border-t-1 border-b-1 border-l-none border-r-none border-solid border-t-gray-700 border-b-gray-700 pa-4 my-4">
    <canvas id="hollow-rectangle" width="150" height="150" class="border border-solid border-red">
    </canvas>
  </div>
</template>

三角形

笑脸

vue
<template>
  <div class="border-t-1 border-b-1 border-l-none border-r-none border-solid border-t-gray-700 border-b-gray-700 pa-4 my-4">
    <canvas id="smile-face" width="150" height="150" class="border border-solid border-red"></canvas>
  </div>
</template>
<script setup lang="ts">
import {onMounted} from 'vue'

onMounted(() => {
  const sfc = document.getElementById('smile-face') as HTMLCanvasElement
  const ctx = sfc.getContext('2d')
  // CanvasPath.arc(x: number, y: number, radius: number, startAngle: number, endAngle: number, counterclockwise?: boolean): void
  // 参数含义:
  // 圆点 x 坐标
  // 圆点 y 坐标
  // 圆半径
  // 开始角度。水平向右的角度为 0,按顺时针方向,角度为正数,逐渐增大。逆时针方向,角度为负数,逐渐减小。
  // 结束角度。水平向右的角度为 0,按顺时针方向,角度为正数,逐渐增大。逆时针方向,角度为负数,逐渐减小。
  // 是否逆时针。默认值为 false,也就是默认为顺时针。从开始角度到结束角度,画线方向是顺时针还是逆时针。
  ctx.arc(75, 75, 75, - Math.PI, Math.PI, false)
  ctx.stroke()

  ctx.beginPath()
  ctx.arc(75, 75, 55, - Math.PI, 0, true)
  ctx.stroke()

  ctx.beginPath()
  ctx.arc(30, 50, 10, 0, 2 * Math.PI)
  ctx.stroke()

  ctx.beginPath()
  ctx.arc(120, 50, 10, 0, 2 * Math.PI)
  ctx.stroke()
})
</script>

arcTo 案例

CanvasRenderingContext2D.arcTo() 是 Canvas 2D API 根据控制点和半径绘制圆弧路径,使用当前的描点 (前一个 moveTo 或 lineTo 等函数的止点)。根据当前描点与给定的控制点 1 连接的直线,和控制点 1 与控制点 2 连接的直线,作为使用指定半径的圆的切线,画出两条切线之间的弧线路径。

基础点是红色,两个控制点是蓝色。

arcTo:

ts
CanvasPath.arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): void

代码:

vue
<script setup lang="ts">
import { onMounted } from 'vue';

onMounted(() => {
  const adc = document.getElementById('arcTo-demo') as HTMLCanvasElement
  const ctx = adc.getContext('2d')
  // 设置虚线,空数组为实线。
  ctx.setLineDash([])
  ctx.beginPath()
  // 起点
  ctx.moveTo(170, 20)
  // 两个控制点
  ctx.arcTo(170, 150, 50, 20, 30)
  // 
  ctx.stroke()

  ctx.beginPath()
  ctx.fillStyle = 'rgba(200, 0, 0)'
  ctx.fillRect(170, 20, 10, 10)

  ctx.beginPath()
  ctx.fillStyle = 'rgba(0, 200, 0)'
  ctx.fillRect(170, 150, 10, 10)
  ctx.fillRect(50, 20, 10, 10)
  // 设置虚线
  ctx.setLineDash([5, 5])
  ctx.moveTo(170, 20)
  ctx.lineTo(170, 150)
  ctx.lineTo(50, 20)
  
  ctx.stroke()
})
</script>
<template>
  <div class="border-t-1 border-b-1 border-l-none border-r-none border-solid border-t-gray-700 border-b-gray-700 pa-4 my-4">
    <canvas id="arcTo-demo" width="200" height="200" class="border border-solid border-red">
    </canvas>
  </div>
</template>

贝塞尔曲线

绘制贝塞尔曲线的网址:https://www.bezier-curve.com/

画一个星形。

代码:

vue
<script setup lang="ts">
import { onMounted } from 'vue';

onMounted(() => {
  const lhc = document.getElementById('love-heart') as HTMLCanvasElement
  const ctx = lhc.getContext('2d')
  // 起点
  ctx.moveTo(82, 63)
  // 左半边
  // 控制点 1,控制点 2,终点
  // bezierCurveTo(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number)
  ctx.bezierCurveTo(0, 0, 0, 111, 80, 161)
  // 右半边
  ctx.moveTo(82, 63)
  ctx.bezierCurveTo(168, 0, 187, 96, 80, 161)
  // ctx.stroke()
  ctx.fillStyle = 'rgb(200, 0, 0)'
  ctx.fill()
})

</script>
<template>
  <div class="border-t-1 border-b-1 border-l-none border-r-none border-solid border-t-gray-700 border-b-gray-700 pa-4 my-4">
    <canvas id="love-heart" width="200" height="200" class="border border-solid border-red">
    </canvas>
  </div>
</template>

Path2D 示例

代码:

vue
<script setup lang="ts">
import { onMounted } from 'vue';

onMounted(() => {
  const p2c = document.getElementById('path-2d') as HTMLCanvasElement
  const ctx = p2c.getContext('2d')

  const rectangle = new Path2D() 
  rectangle.rect(10, 10, 50, 50)

  const circle = new Path2D() 
  circle.moveTo(200, 60)
  circle.arc(150, 60, 50, 0, 2 * Math.PI)

  ctx.stroke(rectangle)
  ctx.fill(circle)
})
</script>
<template>
  <div class="border-t-1 border-b-1 border-l-none border-r-none border-solid border-t-gray-700 border-b-gray-700 pa-4 my-4">
    <canvas id="path-2d" width="200" height="200" class="border border-solid border-red">
    </canvas>
  </div>
</template>

颜色与样式控制

一旦你设置了 fillStyle 或 strokeStyle,那么后续新绘制的图形都会使用这个样式。如果需要不同的样式,需要重新上色。

js
// 设置为橙色
ctx.fillStyle = 'orange'
ctx.fillStyle = '#bfa'
ctx.fillStyle = 'rgb(255, 0, 255)'
ctx.fillStyle = 'rgb(255, 0, 255, 0.3)'

fillStyle 示例(调色板)

代码:
vue
<script setup lang="ts">
import { onMounted } from 'vue';

onMounted(() => {
  const ctx = (document.getElementById('color-palette') as HTMLCanvasElement).getContext('2d')
  for (let i = 0; i < 10; i ++) {
    for (let j = 0; j < 10; j ++) {
      ctx.fillStyle = `rgb(${Math.floor(255 - i * 22.5)}, ${Math.floor(255 - j * 22.5)}, 255)`
      ctx.fillRect(i * 20, j * 20, 20, 20)
    }
  }
})
</script>
<template>
  <div class="border-t-1 border-b-1 border-l-none border-r-none border-solid border-t-gray-700 border-b-gray-700 pa-4 my-4">
    <canvas id="color-palette" width="200" height="200" class="border border-solid border-red">
    </canvas>
  </div>
</template>

strokeStyle 示例(方格圆)

代码:
vue
<script setup lang="ts">
import { onMounted } from 'vue';

onMounted(() => {
  const ctx = (document.getElementById('stroke-style-demo') as HTMLCanvasElement).getContext('2d')
  for (let i = 0; i < 10; i ++) {
    for (let j = 0; j < 10; j ++) {
      ctx.beginPath()
      ctx.strokeStyle = `rgb(${Math.floor(255 - i * 22.5)}, ${Math.floor(255 - j * 22.5)}, 255)`
      ctx.arc(10 + i * 20, 10 + j * 20, 10, 0, 2 * Math.PI)
      ctx.stroke()
    }
  }
})
</script>
<template>
  <div class="border-t-1 border-b-1 border-l-none border-r-none border-solid border-t-gray-700 border-b-gray-700 pa-4 my-4">
    <canvas id="stroke-style-demo" width="200" height="200" class="border border-solid border-red">
    </canvas>
  </div>
</template>

globalAlpha 示例

代码:
vue
<script setup lang="ts">
import { onMounted } from 'vue';

onMounted(() => {
  const ctx = (document.getElementById('globalAlpha') as HTMLCanvasElement).getContext('2d')

  ctx.fillStyle = 'red'
  ctx.fillRect(0, 0, 100, 100)
  ctx.fillStyle = 'green'
  ctx.fillRect(100, 0, 100, 100)
  ctx.fillStyle = 'blue'
  ctx.fillRect(0, 100, 100, 100)
  ctx.fillStyle = '#000'
  ctx.fillRect(100, 100, 100, 100)

  ctx.globalAlpha = 0.2

  ctx.fillStyle = '#fff'
  for (let i = 0; i < 8; i ++) {
    ctx.arc(100, 100, 30 + i * 10, 0, 2 * Math.PI)
    ctx.fill()
  }
})
</script>
<template>
  <div class="border-t-1 border-b-1 border-l-none border-r-none border-solid border-t-gray-700 border-b-gray-700 pa-4 my-4">
    <canvas id="globalAlpha" width="200" height="200" class="border border-solid border-red">
    </canvas>
  </div>
</template>

rgba 示例

代码:

vue
<script setup lang="ts">
import { onMounted } from 'vue';

onMounted(() => {
  const ctx = (document.getElementById('rgba-demo') as HTMLCanvasElement).getContext('2d')

  ctx.fillStyle = 'rgb(255, 0, 0)'
  ctx.fillRect(0, 0, 200, 50)
  ctx.fillStyle = 'rgb(111, 25, 0)'
  ctx.fillRect(0, 50, 200, 50)
  ctx.fillStyle = 'rgb(0, 0, 255)'
  ctx.fillRect(0, 100, 200, 50)
  ctx.fillStyle = 'rgb(200, 200, 0)'
  ctx.fillRect(0, 150, 200, 50)

  for (let i = 0; i < 4; i ++) {
    for (let j = 0; j < 10; j ++) {
      ctx.fillStyle = `rgba(255, 255, 255, ${(j) / 10})`
      ctx.fillRect(5 + j * 19, 5 + i * 50, 19, 40)
    }
  }
})
</script>
<template>
  <div class="border-t-1 border-b-1 border-l-none border-r-none border-solid border-t-gray-700 border-b-gray-700 pa-4 my-4">
    <canvas id="rgba-demo" width="200" height="200" class="border border-solid border-red">
    </canvas>
  </div>
</template>

lineWidth 示例

vue
<script setup lang="ts">
import { onMounted } from 'vue';

onMounted(() => {
  const ctx = (document.getElementById('lineWidth-demo') as HTMLCanvasElement).getContext('2d')
  for (let i = 0; i < 10; i ++) {
    ctx.beginPath()
    ctx.lineWidth = 1
    ctx.moveTo(10*i + i * 0.5, 0)
    ctx.lineTo(10*i + i * 0.5, 200)
    ctx.stroke()
  }
})
</script>
<template>
  <div class="border-t-1 border-b-1 border-l-none border-r-none border-solid border-t-gray-700 border-b-gray-700 pa-4 my-4">
    <canvas id="lineWidth-demo" width="200" height="200" class="border border-solid border-red border-l-none border-r-none">
    </canvas>
  </div>
</template>

在这里,我画了 10 条线,每条线的宽度都设置为 1 像素。可是,在画布上呈现的效果却不相同。

第一条线,从 (0, 0) 画到 (0, 200),可以看到只显示了线的一半。

第二条线,从 (10.5, 0) 画到 (10.5, 200),可以看到显示完整,颜色也比前面的线深。

第三条线:从 (21, 0) 画到 (21, 200),可以看到显示完整,但是颜色有些不对,只有前面的一半色调。

可以看出来,如果线宽的中心线在画布的网格线上,那么颜色就会比较淡。如果线宽中心线刚好位于相邻两根网格线的中线上,那么颜色就会比较深。换句话说,线条的一半如果紧贴着网格线,那么就显示得更清晰。

lineCap 示例

从坐到右,三条线的 lineCap 分别为 'butt', 'round', 'square'。

round 的末端为半径为一半线宽的半圆。

square 的末端为长为线宽,高为一半线宽的长方形。

vue
<script setup lang="ts">
import { onMounted } from 'vue';

onMounted(() => {
  const ctx = (document.getElementById('lineCap-demo') as HTMLCanvasElement).getContext('2d')
  const helpHeight = 20.5
  ctx.strokeStyle = 'blue'
  ctx.moveTo(0, helpHeight)
  ctx.lineTo(200, helpHeight)
  ctx.moveTo(0, 200 - helpHeight)
  ctx.lineTo(200, 200 - helpHeight)
  ctx.stroke()

  const gap = 200 / 4
  const lineCapArr = ['butt', 'round', 'square'] as CanvasLineCap[]
  for (let i = 1; i <= 3; i ++) {
    ctx.beginPath()
    ctx.moveTo(i * gap, helpHeight)
    ctx.strokeStyle = '#000'
    ctx.lineWidth = 18
    ctx.lineCap = lineCapArr[i - 1]
    ctx.lineTo(i * gap, 200 - helpHeight)
    ctx.stroke()
  }
})
</script>
<template>
  <div class="border-t-1 border-b-1 border-l-none border-r-none border-solid border-t-gray-700 border-b-gray-700 pa-4 my-4">
    <canvas id="lineCap-demo" width="200" height="200" class="border border-solid border-red border-l-none border-r-none">
    </canvas>
  </div>
</template>

lineJoin 示例

miterLimit 代表什么意思呢?如下图所示,如果 miterLimit 为 1,代表两条线段垂直。miterLimit 越大,说明两条相交线段的夹角越小,反之越大。

用 mdn 上的话解释:

应用 miter 的效果,线段的外侧边缘会被延伸交汇于一点上。线段之间夹角比较大时,交点不会太远,但随着夹角变小,交点距离会呈指数级增大。

miterLimit 属性就是用来设定外延交点与连接点的最大距离,如果交点距离大于此值,连接效果会变成了 bevel。注意,最大斜接长度(即交点距离)是当前坐标系测量线宽与此 miterLimit 属性值(HTML <canvas> 默认为 10.0)的乘积,所以 miterLimit 可以单独设置,不受显示比例改变或任何仿射变换的影响:它只影响线条边缘的有效绘制形状。

更准确的说,斜接限定值(miterLimit)是延伸长度(在 HTML Canvas 中,这个值是线段外连接点与路径中指定的点之间的距离)与一半线宽的最大允许比值。它也可以被等效定义为线条内外连接点距离(miterLength)与线宽(lineWidth)的最大允许比值(因为路径点是内外连接点的中点)。这等同于相交线段最小内夹角(θ)的一半的余割值,小于此角度的斜接将不会被渲染,而仅渲染斜边连接:

  • miterLimit = max miterLength / lineWidth = 1 / sin ( min θ / 2 )
  • 斜接限定值默认为 10.0,这将会去除所有小于大约 11 度的斜接。
  • 斜接限定值为 √2 ≈ 1.4142136(四舍五入)时,将去除所有锐角的斜接,仅保留钝角或直角。
  • 1.0 是合法的斜接限定值,但这会去除所有斜接。
  • 小于 1.0 的值不是合法的斜接限定值。

蚂蚁线

学习的 api:

参数为交替描述线段和间隙的数组。如果数组元素为奇数,会被重复与复制。

js
ctx.setLineDash([10, 4])

设置虚线起始位置的偏移量。方向为从绘制线段的起始点到结束点。

js
ctx.lineDashOffset = offset

代码:

vue
<script setup lang="ts">
import { onMounted } from 'vue';
let offset = 0
let ctx: CanvasRenderingContext2D
let startTime = 0
function draw() {
  ctx.clearRect(0, 0, 200, 200)
  // 设置线段和间隙的长度
  ctx.setLineDash([5, 8, 10]) 
  ctx.lineDashOffset = -offset 
  ctx.strokeRect(20, 20, 160, 160)
}
function start() {
  const now = new Date().getTime()
  if (now - startTime > 100) {
    startTime = now
    draw()
    offset ++
    if (offset > 2000) {
      offset = 0
    }
  }
  requestAnimationFrame(start)
}
onMounted(() => {
  ctx = (document.getElementById('ants-line') as HTMLCanvasElement).getContext('2d')
  start()
})
</script>
<template>
  <div class="border-t-1 border-b-1 border-l-none border-r-none border-solid border-t-gray-700 border-b-gray-700 pa-4 my-4">
    <canvas id="ants-line" width="200" height="200" class="border border-solid border-red border-l-none border-r-none">
    </canvas>
  </div>
</template>

渐变

createLinearGradient 方法接受 4 个参数。x1、y1 表示渐变的起点。x2、y2 表示渐变的终点。

js
createLinearGradient(x1, y1, x2, y2)

createRadialGradient 方法接受 6 个参数。

x1、y1、r1 表示第一个圆的圆心坐标、半径。x2、y2、r2 表示第二个圆的圆心坐标、半径。

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

创建出 canvasGradient 对象后,我们就可以用 addColorStop 方法给它上色了。

js
gradient.addColorStop(position, color)

addColorStop 方法接受 2 个参数,第一个参数必须是一个 0.0 与 1.0 之间的数值,表示渐变中颜色所在的相对位置。例如,0.5 表示颜色会出现在正中间。第二个参数必须是一个有效的 CSS 颜色值。

可以根据需要添加任意多个色标(color stops)。

黑白渐变(addColorStop API 示例)

addColorStop 第一个参数为 0 到 1 之间的偏移值,第二个参数为颜色值。表示在这个偏移值下,颜色必须是参数所给的颜色。

代码:

vue
<script setup lang="ts">
import { onMounted } from 'vue';

onMounted(() => {
  const ctx = (document.getElementById('addColorStop-demo') as HTMLCanvasElement).getContext('2d')
  // 创建一个线性渐变对象,返回值可以赋给 fillStyle 或 strokeStyle 对象。
  const gradient = ctx.createLinearGradient(0, 0, 200, 0)
  gradient.addColorStop(0, 'black')
  gradient.addColorStop(1, 'white')
  // 画一个渐变边框
  ctx.strokeStyle = gradient 
  ctx.strokeRect(20, 20, 160, 160) 
  // 画一个渐变矩形
  ctx.fillStyle = gradient 
  ctx.fillRect(40, 40, 120, 120) 
})
</script>
<template>
  <div class="border-t-1 border-b-1 border-l-none border-r-none border-solid border-t-gray-700 border-b-gray-700 pa-4 my-4">
    <canvas id="addColorStop-demo" width="200" height="200" class="border border-solid border-red border-l-none border-r-none">
    </canvas>
  </div>
</template>

渐变突变(createLinearGradient 示例)

注意调用 addColorStop 的顺序。

代码:

vue
<script setup lang="ts">
import { onMounted } from 'vue';

onMounted(() => {
  const ctx = (document.getElementById('createLinearGradient-demo') as HTMLCanvasElement).getContext('2d')
  const gradient = ctx.createLinearGradient(0, 0, 0, 200)

  gradient.addColorStop(0, '#40c3ef')
  gradient.addColorStop(0.5, 'white') 
  gradient.addColorStop(0.5, '#36c62a') 
  gradient.addColorStop(1, 'white')

  ctx.fillStyle = gradient
  ctx.fillRect(0, 0, 200, 200)
})
</script>
<template>
  <div class="border-t-1 border-b-1 border-l-none border-r-none border-solid border-t-gray-700 border-b-gray-700 pa-4 my-4">
    <canvas id="createLinearGradient-demo" width="200" height="200" class="border border-solid border-red border-l-none border-r-none">
    </canvas>
  </div>
</template>

径向渐变示例(createRadialGradient)

从左到右,从上到下看。

  1. 第一个渐变圆,从外圆向内圆渐变。

    • 注意不要让内圆有一部分超出了外圆的边界,否则会有意想不到的效果。
    • 从外圆开始,初始颜色之所以设置为透明色,是为了防止除了圆位置之外画布其它位置颜色被改变。
  2. 第二个圆,从内圆向外圆渐变。

    • 和第一个差不多,只不过渐变方向改变了。
  3. 第三个圆,从外圆向内圆渐变。

    • 设置内圆半径为 1,更有渐变的感觉。
  4. 第四个圆,从外圆向内圆渐变。

    • 内圆的整个的颜色等于渐变的终止颜色。
vue
<script setup lang="ts">
import { onMounted } from 'vue';

onMounted(() => {
  const ctx = (document.getElementById('createRadialGradient-demo') as HTMLCanvasElement).getContext('2d')
  // 创建渐变
  // 从外圆向内圆渐变。不要让内圆有一部分超出了外圆的边界,否则会有意想不到的效果。
  const gradient1 = ctx.createRadialGradient(50, 50, 50, 25, 25, 10)
  gradient1.addColorStop(0, 'rgb(0, 0, 255, 0)')
  gradient1.addColorStop(0.1, 'rgb(0, 0, 255)')
  gradient1.addColorStop(1, 'rgba(255, 0, 0, 0.3)')
  ctx.fillStyle = gradient1
  ctx.fillRect(0, 0, 200, 200)

  // 从内圆向外圆渐变。
  const gradient2 = ctx.createRadialGradient(175, 25, 10, 150, 50, 50)
  gradient2.addColorStop(0, 'rgb(0, 0, 255, 0)')
  gradient2.addColorStop(0.9, 'rgb(0, 0, 255)')
  gradient2.addColorStop(1, 'rgba(255, 255, 255, 0)')
  ctx.fillStyle = gradient2
  ctx.fillRect(0, 0, 200, 200)

  // 从外圆向内圆渐变。
  const gradient3 = ctx.createRadialGradient(50, 150, 50, 50, 150, 1)
  gradient3.addColorStop(0, 'rgb(0, 0, 0, 0)')
  gradient3.addColorStop(0.1, 'rgb(0, 0, 0)')
  gradient3.addColorStop(1, 'rgba(255, 0, 0)')
  // 内圆的颜色等于 offset 为 1 时设置的颜色。//
  ctx.fillStyle = gradient3
  ctx.fillRect(0, 0, 200, 200)

  // 从外圆向内圆渐变。
  const gradient4 = ctx.createRadialGradient(150, 150, 50, 150, 150, 30)
  gradient4.addColorStop(0, 'rgb(0, 0, 0, 0)')
  gradient4.addColorStop(0.1, 'rgb(0, 0, 0)')
  gradient4.addColorStop(1, 'rgba(255, 0, 0)')
  // 内圆的颜色等于 offset 为 1 时设置的颜色。//
  ctx.fillStyle = gradient4
  ctx.fillRect(0, 0, 200, 200)
})
</script>
<template>
  <div class="border-t-1 border-b-1 border-l-none border-r-none border-solid border-t-gray-700 border-b-gray-700 pa-4 my-4">
    <canvas id="createRadialGradient-demo" width="200" height="200" class="border border-solid border-red border-l-none border-r-none">
    </canvas>
  </div>
</template>

createPattern API 学习

使用指定的图像创建一个模式,可以复制给 fillStyle。

代码:

vue
<script setup lang="ts">
import { onMounted } from 'vue';
import {withBase} from 'vitepress'

onMounted(() => {
  const cpd = document.getElementById('createPattern-demo') as HTMLCanvasElement
  const ctx = cpd.getContext('2d')
  const img = new Image() 
  img.src =  withBase('https://lukecheng2.oss-cn-chengdu.aliyuncs.com/public/img/Snipaste_2023-06-23_18-02-55.png') 
  img.onload = () => {
    const width = img.width
    const height = img.height
    cpd.width = width
    cpd.height = height
    const ptn = ctx.createPattern(img, 'no-repeat') 
    ctx.fillStyle = ptn 
    ctx.fillRect(0, 0, width, height)
  }
})
</script>
<template>
  <div class="border-t-1 border-b-1 border-l-none border-r-none border-solid border-t-gray-700 border-b-gray-700 pa-4 my-4">
    <canvas id="createPattern-demo" width="400" height="800" class="border border-solid border-red border-l-none border-r-none w-full">
    </canvas>
  </div>
</template>

文字阴影

填充规则

CanvasRenderingContext2D.fill() 是 Canvas 2D API 根据当前的填充样式,填充当前或已存在的路径的方法。采取非零环绕或者奇偶环绕规则。

语法:

js
void ctx.fill();
void ctx.fill(fillRule);
void ctx.fill(path, fillRule);

参数:

fillRule:
  一种算法,决定点是在路径内还是在路径外。 允许的值:
    nonzero
      非零环绕规则,默认的规则。
    evenodd
      奇偶环绕规则。
nonzero,非零环绕规则,默认的规则。

如何判断某个区域是在路径内,还是路径外?在这个区域任找一点,发射一条射线。定义一个计数器 0,当射线与路径相交时,如果射线的方向与绘制路径线的方向连起来的顺序是逆时针,计数器减 1;如果射线的方向与绘制路径线的方向连起来的顺序是顺时针,计数器加 1。最后,只要计数器不为 0,那么就认为这个区域是在路径内,否者为路径外。

evenodd,奇偶环绕规则。

这种情况下判断是在路径内,还是路径外,也是在这个区域任找一点,发射一条射线。如果射线与相交的路径线的条数为奇数,那么就认为在路径内,否则为路径外。

代码:

vue
<script setup lang="ts">
import { onMounted } from 'vue';

onMounted(() => {
  const cpd = document.getElementById('fill-rule-demo') as HTMLCanvasElement
  const ctx = cpd.getContext('2d')
  const width = cpd.width
  const height = cpd.height
  const x = width / 2
  const y = height / 2
  const r = 100
  const start = - Math.PI / 2
  const end = Math.PI * 3 / 2
  ctx.arc(x, y, r, start, end)
  ctx.fillStyle = '#0a5bae'
  ctx.fill()

  ctx.beginPath()
  ctx.moveTo(x, y - r)

  // 顶点连左下点
  ctx.lineTo(x - r * Math.sin(Math.PI / 5), y + r * Math.cos(Math.PI / 5))
  // 左下点连右上点
  ctx.lineTo(x + r * Math.cos(Math.PI / 10), y - r * Math.sin(Math.PI / 10))
  // 右上点连左上点
  ctx.lineTo(x - r * Math.cos(Math.PI / 10), y -r * Math.sin(Math.PI / 10))
  // 左上点连右下点
  ctx.lineTo(x + r * Math.sin(Math.PI / 5), y + r * Math.cos(Math.PI / 5))

  ctx.fillStyle = '#fff'
  // 设置填充规则,奇偶规则
  ctx.fill('evenodd')
})
</script>
<template>
  <div class="border-t-1 border-b-1 border-l-none border-r-none border-solid border-t-gray-700 border-b-gray-700 pa-4 my-4">
    <canvas id="fill-rule-demo" width="200" height="200" class="border border-solid border-red border-l-none border-r-none">
    </canvas>
  </div>
</template>

Released under the MIT License.