Canvas 基础 & 在 React 项目中使用
January 09, 2024Canvas
开始之前
Canvas 显示模糊问题
- https://www.cnblogs.com/thyshare/p/13228473.html
- canvas 标签上的 width height 决定画布的像素点,style.width 和 style.height 只会拉伸变形画布不会改变像素点。
- 默认情况下,设备像素比大于 1 时,画布会出现模糊。需要设置 dpr * width 和 dpr * height 增加像素数量,并且 style.width = 1 倍 width,style.height = 1 倍 height,保证画布大小不变,但像素数量增加。
canvas in react
https://medium.com/@pdx.lucasm/canvas-with-react-js-32e133c05258
import { CanvasHTMLAttributes, useState } from "react"
import React, { useEffect, useRef, useMemo } from "react"
import cls from "classnames"
// 设备像素比
const getPixelRatio = () => {
return window.devicePixelRatio || 1
}
interface IProps<T> extends CanvasHTMLAttributes<HTMLCanvasElement> {
className?: string
dpr?: number
width: number
height: number
draw: (
ctx: CanvasRenderingContext2D,
userConfig: T,
scale: (num: number) => number
) => void
userConfig: T
}
const CanvasComponents = <T,>({
draw,
className,
style,
dpr,
width = 300,
height = 150,
userConfig,
...canvasAttributes
}: IProps<T>) => {
const canvasRef = useRef<HTMLCanvasElement>(null)
const [ratio, setRatio] = useState(1)
useEffect(() => {
// 为了支持 SSR,在当前生命周期才能调用 window 对象获取设备像素比例
setRatio(dpr || getPixelRatio())
}, [dpr])
const [hdWidth, hdHeight] = useMemo(() => {
return [+width * ratio, +height * ratio]
}, [width, height, ratio])
useEffect(() => {
const canvasDom = canvasRef.current
const ctx: CanvasRenderingContext2D | null = canvasDom
? canvasDom.getContext("2d")
: null
if (!canvasDom || !ctx) {
// canvas unsupported or not find
return
}
if (draw) {
// 使其在水平和垂直方向上都按照 ratio 缩放。这个操作会影响后续在 Canvas 上绘制的内容,将其缩放为原始尺寸的 ratio 倍。
ctx.setTransform(ratio, 0, 0, ratio, 0, 0)
const scale = (num: number) => num * ratio
draw(ctx, userConfig, scale)
}
return () => {
// 重置 setTransform,以保证矩形的位置大小是正确的
ctx.setTransform(ratio, 0, 0, ratio, 0, 0)
ctx.clearRect(0, 0, canvasDom.width, canvasDom.height)
}
}, [draw, width, height, ratio, userConfig])
return (
<canvas
ref={canvasRef}
className={cls("bg-black dark:bg-white", className)}
style={{ width: `${width}px`, height: `${height}px`, ...style }}
width={hdWidth}
height={hdHeight}
{...canvasAttributes}
/>
)
}
export default CanvasComponents
使用组件:
<CanvasComponents
className="mx-1 my-1"
width={150}
height={150}
draw={(ctx, userConfig) => {
// userConfig.useColor 根据当前页面是 light 还是 dark 模式,自动选择其中一个颜色。
// 绘制一个矩形的边框
ctx.strokeStyle = userConfig.useColor(colors.blue[400], colors.blue[700])
ctx.strokeRect(10, 10, 50, 50)
// 绘制一个填充的矩形
ctx.fillStyle = userConfig.useColor(colors.red[600], colors.red[700])
ctx.fillRect(30, 30, 50, 50)
}}
userConfig={userConfig}
/>
相关知识
MDN Cnavas 基础教程,是 MDN 上关于 Canvas 的一个系列教程文档,非常值得 Canvas 入门学习。
- Path2D:将 Canvas 的命令操作抽象到一个对象,实现重用和性能提升。
- DOMMatrix: 矩阵对象,用于对 2d/3d 对象描述旋转、拉伸、过渡等操作。更细致的讲解文档可以看张鑫旭的这两个文章:
- CanvasRenderingContext2D:canvas 绘图的上下文对象,用于操作 API。
- CanvasRenderingContext2D.globalCompositeOperation:组合合成各种参数演示介绍。
- requestAnimationFrame() 方法:实现动画时用于更新重绘。
- 剖析游戏结构:定义主循环,运用 requestAnimationFrame。
示例: