|
|
1
gman
1 年前
这里的问题是规范上说
ResizeObserver
发生回调
请求后动画帧
回调。这意味着上述示例中的操作顺序为
-
requestAnimationFrame回调
-
绘制圆
-
调整观察器回调大小
-
设置画布大小(设置大小将清除画布)
-
浏览器合成页。
有几种解决方案,它们都有问题或半涉及
解决方案1:绘制
ResizeObserver
也
这会奏效的。不幸的是,这意味着你在一帧中画了两次。如果你的画很重,那么这会导致调整大小的感觉迟钝
解决方案2:不要使用
ResizeObserver
您可以通过多种方式查找画布的显示大小。(1)
canvas.clientWidth
,
canvas.clientHeight
(返回整数),(2)
canvas.getBoundingClientRect()
(返回有理数)
例如:
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
function draw() {
const { width, height } = canvas;
ctx.clearRect(0, 0, width, height);
ctx.save();
ctx.translate(width / 2, height / 2);
const size = Math.min(width, height);
ctx.beginPath();
ctx.arc(0, 0, size / 3, 0, Math.PI * 2);
ctx.fill();
ctx.rotate(performance.now() * 0.001);
ctx.strokeStyle = 'red';
ctx.strokeRect(-5, -5, size / 4, 10);
ctx.restore();
}
function rAFLoop() {
if (canvas.width !== canvas.clientWidth ||
canvas.height !== canvas.clientHeight) {
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
}
draw();
requestAnimationFrame(rAFLoop);
}
requestAnimationFrame(rAFLoop);
html, body {
height: 100%;
margin: 0;
}
canvas {
width: 100%;
height: 100%;
display: block;
}
<canvas></canvas>
调整窗口大小,你会看到它不再闪烁
这个解决方案会起作用,但那些方法(
canvas.clientWidth/height
和/或
getBoundingClientRect
)只返回CSS像素,不返回设备像素。你可以将任意一个数字乘以
devicePixelRatio
但这也不会一直给你正确的答案。您可以在以下答案中了解原因:
https://stackoverflow.com/a/72611819/128511
解决方案3:如果画布的大小要更改,请检查rAF,如果要更改,则不渲染,而是在
ResizeObserver
回调。
我们可以猜测画布是否要调整大小,如果
getBoundingClientRect
更改大小。尽管我们无法正确地从转换
getBoundingClientRect
对于设备像素,如果画布已经调整了大小,则该值将与我们上次调用它时相比发生变化。
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
function draw() {
const { width, height } = canvas;
ctx.clearRect(0, 0, width, height);
ctx.save();
ctx.translate(width / 2, height / 2);
const size = Math.min(width, height);
ctx.beginPath();
ctx.arc(0, 0, size / 3, 0, Math.PI * 2);
ctx.fill();
ctx.rotate(performance.now() * 0.001);
ctx.strokeStyle = 'red';
ctx.strokeRect(-5, -5, size / 4, 10);
ctx.restore();
}
let lastSize = {};
function rAFLoop() {
const rect = canvas.getBoundingClientRect();
const willResize = lastSize.width !== rect.width || lastSize.height !== rect.height;
if (willResize) {
lastSize = rect;
} else {
draw();
}
requestAnimationFrame(rAFLoop);
}
requestAnimationFrame(rAFLoop);
const observer = new ResizeObserver(entries => {
for (const entry of entries) {
const canvas = entry.target;
canvas.width = entry.devicePixelContentBoxSize[0].inlineSize;
canvas.height = entry.devicePixelContentBoxSize[0].blockSize;
draw();
}
})
observer.observe(canvas, { box: 'device-pixel-content-box' });
html,正文{
高度:100%;
裕度:0;
}
帆布{
宽度:100%;
高度:100%;
显示:块;
}
<画布></画布>
使用此解决方案,我们每帧只渲染一次,并获得实际的设备像素大小
注意:第三个解决方案改变了它所观察到的
'content-box'
,默认为
'device-pixel-content-box'
。如果你不进行此更改,那么,如果用户放大或缩小,你将不会得到调整大小的回调,因为从POV到网页,元素的大小在缩放时不会改变。它仍然只是一定数量的CSS像素。
解决方案4:将大小记录在
ResizeObserver
回调,在中调整画布大小
requestAnimationFrame
如果尺寸发生变化
这个解决方案意味着,给定这个答案顶部的操作顺序,您将为1帧呈现错误的大小。通常这并不重要。事实上,运行下面的示例并调整窗口大小,您可能不会看到问题
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
function draw() {
const { width, height } = canvas;
ctx.clearRect(0, 0, width, height);
ctx.save();
ctx.translate(width / 2, height / 2);
const size = Math.min(width, height);
ctx.beginPath();
ctx.arc(0, 0, size / 3, 0, Math.PI * 2);
ctx.fill();
ctx.rotate(performance.now() * 0.001);
ctx.strokeStyle = 'red';
ctx.strokeRect(-5, -5, size / 4, 10);
ctx.restore();
}
const desiredSize = { width: 300, height: 150 };
function rAFLoop() {
const { width, height } = desiredSize;
if (canvas.width !== width || canvas.height !== height) {
canvas.width = width;
canvas.height = height;
}
draw();
requestAnimationFrame(rAFLoop);
}
requestAnimationFrame(rAFLoop);
const observer = new ResizeObserver(entries => {
for (const entry of entries) {
const canvas = entry.target;
desiredSize.width = entry.devicePixelContentBoxSize[0].inlineSize;
desiredSize.height = entry.devicePixelContentBoxSize[0].blockSize;
}
})
observer.observe(canvas, { box: 'device-pixel-content-box' });
html,正文{
高度:100%;
裕度:0;
}
帆布{
宽度:100%;
高度:100%;
显示:块;
}
<画布></画布>
如果调整窗口大小,您可能不会看到此解决方案的任何问题。另一方面,假设你有突然的大变化。例如,假设您有一个编辑器,并且可以选择显示/隐藏窗格,这样画布区域的大小就会急剧变化。然后,对于一帧,你会看到一个错误大小的图像。我们可以演示
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
function draw() {
const { width, height } = canvas;
ctx.clearRect(0, 0, width, height);
ctx.fillStyle = 'red';
ctx.save();
ctx.translate(width / 2, height / 2);
const size = Math.min(width, height);
ctx.beginPath();
ctx.arc(0, 0, size / 3, 0, Math.PI * 2);
ctx.fill();
ctx.stokeStyle = 'red';
ctx.lineWidth = 20;
ctx.strokeRect(-size / 2.5, -size / 2.5, size / 1.25, size / 1.25);
ctx.lineWidth = 3;
ctx.rotate(performance.now() * 0.001);
ctx.strokeStyle = '#000';
ctx.strokeRect(-5, -5, size / 4, 10);
ctx.restore();
}
const desiredSize = { width: 300, height: 150 };
function rAFLoop() {
const { width, height } = desiredSize;
if (canvas.width !== width || canvas.height !== height) {
canvas.width = width;
canvas.height = height;
}
draw();
requestAnimationFrame(rAFLoop);
}
requestAnimationFrame(rAFLoop);
const observer = new ResizeObserver(entries => {
for (const entry of entries) {
const canvas = entry.target;
desiredSize.width = entry.devicePixelContentBoxSize[0].inlineSize;
desiredSize.height = entry.devicePixelContentBoxSize[0].blockSize;
}
})
observer.observe(canvas, { box: 'device-pixel-content-box' });
const uiElem = document.querySelector('#ui');
setInterval(() => {
ui.style.display = ui.style.display === '' ? 'none' : '';
}, 1000);
html, body {
height: 100%;
margin: 0;
}
canvas {
width: 100%;
height: 100%;
display: block;
}
#outer {
display: flex;
width: 100%;
height: 100%;
}
#outer>* {
flex: 1 1 50%;
min-width: 0;
}
#ui {
display: flex;
justify-content: center;
align-items: center;
background-color: #888;
}
<div id="outer">
<div><canvas></canvas></div>
<div id="ui"><div>ui pane</div></div>
</div>
如果你仔细观察上面的例子,当ui窗格被隐藏或显示时,你会看到一个框架的大小错误
您选择的解决方案取决于您的需求。如果您的图形没有那么重,可以绘制两次,一次在rAF中,另一次在调整大小观察者中。或者,如果你不希望用户经常调整大小(注意:这不仅仅是调整窗口的大小,很多网页都有带滑块的窗格,可以让你调整存在所有相同问题的区域的大小)
如果你不关心像素的完美度,那么第二个解决方案也很好。例如,大多数3d游戏,AFAIK,并不关心像素的完美度。他们已经在渲染双线性过滤纹理,并且经常渲染到较低的分辨率和重新缩放。
如果你真的关心像素的完美度,那么第三种解决方案是可行的。
注意:截止到2014-01-18,Safari仍然不支持
devicePixelContentBoxSize
|