canvas 学习
两个矩形
首先设置画布的宽高都为 150 像素,如案例所示,红色边框矩形为我们设置的画布。
画矩形,就是先找到矩形的左上角顶点位置,然后固定宽和高。
二维画布的坐标系的原点在画布的左上角,水平向右为 x 轴,垂直向下为 y 轴。
下面代码使用到了 fillRect(x, y, width, height) 方法,类似的方法还有 strokeRect(x, y, width, height)、clearRect(x, y, width, height)。它们的作用分别是绘制一个矩形边框、清除矩形区域。
案例代码:
<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>镂空矩形
源代码:
<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>三角形
笑脸
<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:
CanvasPath.arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): void代码:
<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/
画一个星形。
代码:
<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 示例
代码:
<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,那么后续新绘制的图形都会使用这个样式。如果需要不同的样式,需要重新上色。
// 设置为橙色
ctx.fillStyle = 'orange'
ctx.fillStyle = '#bfa'
ctx.fillStyle = 'rgb(255, 0, 255)'
ctx.fillStyle = 'rgb(255, 0, 255, 0.3)'fillStyle 示例(调色板)
代码:<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 示例(方格圆)
代码:<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 示例
代码:<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 示例
代码:
<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 示例
<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 的末端为长为线宽,高为一半线宽的长方形。
<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:
参数为交替描述线段和间隙的数组。如果数组元素为奇数,会被重复与复制。
ctx.setLineDash([10, 4])设置虚线起始位置的偏移量。方向为从绘制线段的起始点到结束点。
ctx.lineDashOffset = offset代码:
<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 表示渐变的终点。
createLinearGradient(x1, y1, x2, y2)createRadialGradient 方法接受 6 个参数。
x1、y1、r1 表示第一个圆的圆心坐标、半径。x2、y2、r2 表示第二个圆的圆心坐标、半径。
createRadialGradient(x1, y1, r1, x2, y2, r2)创建出 canvasGradient 对象后,我们就可以用 addColorStop 方法给它上色了。
gradient.addColorStop(position, color)addColorStop 方法接受 2 个参数,第一个参数必须是一个 0.0 与 1.0 之间的数值,表示渐变中颜色所在的相对位置。例如,0.5 表示颜色会出现在正中间。第二个参数必须是一个有效的 CSS 颜色值。
可以根据需要添加任意多个色标(color stops)。
黑白渐变(addColorStop API 示例)
addColorStop 第一个参数为 0 到 1 之间的偏移值,第二个参数为颜色值。表示在这个偏移值下,颜色必须是参数所给的颜色。
代码:
<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 的顺序。
代码:
<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,更有渐变的感觉。
第四个圆,从外圆向内圆渐变。
- 内圆的整个的颜色等于渐变的终止颜色。
<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。
代码:
<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 根据当前的填充样式,填充当前或已存在的路径的方法。采取非零环绕或者奇偶环绕规则。
语法:
void ctx.fill();
void ctx.fill(fillRule);
void ctx.fill(path, fillRule);参数:
fillRule:
一种算法,决定点是在路径内还是在路径外。 允许的值:
nonzero
非零环绕规则,默认的规则。
evenodd
奇偶环绕规则。如何判断某个区域是在路径内,还是路径外?在这个区域任找一点,发射一条射线。定义一个计数器 0,当射线与路径相交时,如果射线的方向与绘制路径线的方向连起来的顺序是逆时针,计数器减 1;如果射线的方向与绘制路径线的方向连起来的顺序是顺时针,计数器加 1。最后,只要计数器不为 0,那么就认为这个区域是在路径内,否者为路径外。
这种情况下判断是在路径内,还是路径外,也是在这个区域任找一点,发射一条射线。如果射线与相交的路径线的条数为奇数,那么就认为在路径内,否则为路径外。
代码:
<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>