Intro to Canvas
画布尺寸
Canvas 有两种 width
和 height
:
- 一种是 width、height 属性,一般称其为 画布尺寸,即图形绘制的地方。默认值分别为
300px
、150px。
例如:
<canvas id="canvas" width="300" height="150"></canvas>
- 另一种是 CSS 样式里的 width、height 属性,可通过内联样式、内部样式表或外部样式表设置。一般称其为 画板尺寸,用于渲染绘制完成的图形。默认值为空。
例如:
<canvas id="canvas" style="width: 300px; height: 150px;"></canvas>
或
<style>
#canvas {
width: 300px;
height: 150px;
}
</style>
画布尺寸 | 画板尺寸 | 说明 |
---|---|---|
已设置 | 未设置 | 画板尺寸随画布尺寸改变 |
未设置 | 已设置 | 画板尺寸将不再随画布尺寸而改变 |
已设置 | 已设置 | 画板尺寸将不再随画布尺寸而改变 |
如果两者设置的尺寸不一样时,就会产生一个问题,渲染时画布要通过缩放来使其与画板尺寸一样,那么画布上已经绘制好的图形也会随之缩放,随之导致变形失真。
下面为绘制从原点到 200*200
的直线:
class Demo extends React.Component { componentDidMount() { const canvasList = document.querySelectorAll('.canvas'); canvasList.forEach(item => { const context = item.getContext('2d'); this.renderPath(context); }); } renderPath(ctx) { ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(200, 200); ctx.stroke(); } render() { return ( <> <p>1、已设置画布宽高属性(200px * 200px),未设置画板样式宽高,画板尺寸随画布尺寸改变</p> <canvas id="canvas1" className="canvas" width="200" height="200"></canvas> <p>2、未设置画布宽高属性,但已设置画板样式宽高(200px * 200px)</p> <canvas id="canvas2" className="canvas" style={{ width: 200, height: 200 }}></canvas> <p>3、设置画布宽高属性(300 * 300),设置画板样式宽高(200 * 200)</p> <canvas id="canvas3" className="canvas" width="300" height="300" style={{ width: 200, height: 200, border: '1px solid #000' }}></canvas> <p>4、设置画布宽高属性(200 * 200),设置画板样式宽高(300 * 300)</p> <canvas id="canvas4" className="canvas" width="200" height="200" style={{ width: 300, height: 300, border: '1px solid #000' }}></canvas> </> ); } }
高清分辨率
上面说过,避免图形变形失真,要保持画布尺寸和画板尺寸一致。
这只是针对分辨率不高的设备而言,其 window.devicePixelRatio
为 1。而高分辨率屏幕,它的 window.devicePixelRatio
大于 1。
Canvas 绘制的图形是位图,即 栅格图像 或 点阵图像,当将它渲染到高清屏时,会被放大,每个像素点会用 window.devicePixelRatio
平方个物理像素点来渲染,因此图片会变得模糊。
解决方法:
- 通过
window.devicePixelRatio
获取当前设备屏幕的 DPR - 获取或设置 Canvas 容器的画板尺寸
- 根据 DPR,设置 Canvas 元素的宽高属性(在 DPR 为 2 时,相当于扩大画布的两倍)
- 通过
context.scale(dpr, dpr)
缩放 Canvas 画布的坐标系,在 DPR 为 2 时相当于把 Canvas 坐标系也扩大了两倍,这样绘制比例放大了两倍,之后 Canvas 的实际绘制像素就可以按原先的像素值处理
function Demo() { const hdCanvasRef = useRef(null); const ldCanvasRef = useRef(null); const renderPath = context => { context.beginPath(); context.moveTo(0, 0); context.lineTo(200, 200); context.lineWidth = context.lineWidth; context.stroke(); }; // 绘制高清晰度画布 const initHDCanvas = () => { const canvas = hdCanvasRef.current; // 获取 DPR const dpr = window.devicePixelRatio; const ctx = canvas.getContext('2d'); // 获取容器高度 const { width, height } = hdCanvasRef.current.getBoundingClientRect(); // 根据 DPR 设置 Canvas 的宽高,使 1 个 Canvas 元素和 1 个物理像素相等 canvas.width = dpr * width; canvas.height = dpr * height; // 根据 DPR 设置 Canvas 的宽高属性 ctx.scale(dpr, dpr); renderPath(ctx); }; // 绘制低清晰度画布 const initLDCanvas = () => { const ctx = ldCanvasRef.current.getContext('2d'); renderPath(ctx); }; useEffect(() => { initHDCanvas(); initLDCanvas(); }, []); return ( <> <p>已适配 画布像素:画板像素(物理像素)= 1:1</p> {/* 高清画布 */} <canvas ref={hdCanvasRef} style={{ border: '1px solid #000', width: 200, height: 200 }}></canvas> <br /> <p>未适配 画布像素:画板像素(物理像素)= {window.devicePixelRatio}:1</p> {/* 普通画布 */} <canvas ref={ldCanvasRef} width="200" height="200" style={{ border: '1px solid #000' }}></canvas> </> ); }
样式设置的 width
是的元素内容宽度,不包括内边距、边框、外边距的,而 clientWidth
包括内边距,不包括边框、外边距、滚动条的(如果有)。
画布状态
CanvasRenderingContext2D 渲染环境包含了多种绘图的样式状态(属性有线的样式、填充样式、阴影样式、文本样式)。
Canvas 的 API 提供了两个名叫 CanvasRenderingContext2D.save()
和 CanvasRenderingContext2D.restore()
的方法,用于保存及恢复当前 Canvas 绘画环境的所有属性。其中 CanvasRenderingContext2D.save()
可以保存当前状态,而 CanvasRenderingContext2D.restore()
可以还原之前保存的状态。
这两个方法在绘图中有着重要的作用,比如我们在绘图的时候需要使用多种颜色,颜色需要不时的切换。那么使用 save()
和 restore()
方法即可比较方便地实现此功能。
save
CanvasRenderingContxt2D.save()
是 Canvas 2D API 通过将当前状态放入栈中,保存 Canvas 全部状态的方法。
function Demo() { const canvasRef = useRef(null); useEffect(() => { const ctx = canvasRef.current.getContext('2d'); // 保存默认的状态 ctx.save(); ctx.fillStyle = 'green'; ctx.fillRect(10, 10, 100, 100); // 还原到上次保存(save)的默认状态 ctx.restore(); ctx.fillRect(150, 75, 100, 100); }, []); return <canvas ref={canvasRef} width="200" height="200"></canvas>; }
restore
CanvasRenderingContxt2D.restore()
是 Canvas 2D API 通过在绘图状态栈中弹出顶端的状态,将 Canvas 恢复到最近的保存状态的方法。 如果没有保存状态,此方法不做任何改变。
当该方法和 save
一起使用时,恢复到 ctx.save
保存时的状态。
状态和非状态
在 Canvas 环境中绘图时,可以利用所谓的绘图堆栈状态。每个状态随时存储 Canvas 上下文数据。
下面是存储在状态堆栈的数据列表。
- 当前的坐标变换(变换矩阵)信息,比如旋转或平移时使用的
rotate()
和setTransform()
方法 - 当前剪贴区域
- 图形上下文对象(
CanvasRenderingContext2D
)的当前属性值
CanvasRenderingConext2D
的当前属性值主要包括:
属性 | 默认值 | 描述 |
---|---|---|
canvas | - | 取得画布 <canvas> 元素 |
fillStyle | #000000 | 填充路径的当前的颜色、模式或渐变 |
strokeStyle | #000000 | 指定线段颜色 |
globalCompositeOperation | source-over | 指定颜色如何与画布上已有颜色组合(合成) |
lineCap | butt | 指定线段端点的绘制方式 |
lineJoin | miter |