我有一些画布,我想在每个(现代)浏览器中以像素完美的方式显示它们。默认情况下,具有高 DPI 屏幕的设备会缩放我的页面,以便所有内容看起来都大小正确,但它破坏了*我画布的外观。
如何确保画布中的一个像素 = 屏幕上的一个像素?最好这不会影响页面上的其他元素,因为我仍然希望例如文本能够针对设备进行适当缩放。
我已经尝试根据 设计画布尺寸window.devicePixelRatio。这使得画布尺寸合适,但内容看起来更糟。我猜这只是在它们已经错误地放大之后缩小它们。
*如果你关心的话,因为画布使用抖动并且浏览器正在执行某种 lerp 而不是最近邻居
当前标记的答案是错误的
2022 年,我们无法在所有设备、所有浏览器上获得像素完美的画布。
您可能认为它正在使用
canvas {
image-rendering: pixelated;
image-rendering: crisp-edges;
}
Run Code Online (Sandbox Code Playgroud)
但要看到它失败,您所要做的就是按“放大”/“缩小”。缩放并不是唯一失败的地方。许多设备和操作系统都有小数 devicePixelRatio。
换句话说。假设您制作了 100x100 的画布(放大)。
用户的devicePixelRatio是1.25(这就是我的台式电脑)。然后,画布将以 125x125 像素显示,而您的 100x100 像素画布将通过最近邻过滤(图像渲染:像素化)缩放至 125x125,并且看起来非常糟糕,因为某些像素为 1x1 像素大,而其他像素为 2x2 像素大。
所以不,使用
image-rendering: pixelated;
image-rendering: crisp-edges;
Run Code Online (Sandbox Code Playgroud)
其本身并不是一个解决方案。
更差。浏览器可以以小数形式工作,但设备不能。例子:
canvas {
image-rendering: pixelated;
image-rendering: crisp-edges;
}
Run Code Online (Sandbox Code Playgroud)
image-rendering: pixelated;
image-rendering: crisp-edges;
Run Code Online (Sandbox Code Playgroud)
let totalWidth = 0;
let totalDeviceWidth = 0;
document.querySelectorAll(".split>*").forEach(elem => {
const rect = elem.getBoundingClientRect();
const width = rect.width;
const deviceWidth = width * devicePixelRatio;
totalWidth += width;
totalDeviceWidth += deviceWidth;
log(`${elem.className.padEnd(6)}: width = ${width}, deviceWidth: ${deviceWidth}`);
});
const elem = document.querySelector('.split');
const rect = elem.getBoundingClientRect();
const width = rect.width;
const deviceWidth = width * devicePixelRatio;
log(`
totalWidth : ${totalWidth}
totalDeviceWidth: ${totalDeviceWidth}
elemWidth : ${width}
elemDeviceWidth : ${deviceWidth}`);
function log(...args) {
const elem = document.createElement('pre');
elem.textContent = args.join(' ');
document.body.appendChild(elem);
}Run Code Online (Sandbox Code Playgroud)
上面的示例有 299 CSS 像素宽的 div,内部有 3 个子 div,每个子 div 占其父级的 1/3。getBoundingClientRect通过在 Chrome 102 中调用我的 MacBook Pro来询问尺寸,我得到
left : width = 99.6640625, deviceWidth: 199.328125
middle: width = 99.6640625, deviceWidth: 199.328125
right : width = 99.6640625, deviceWidth: 199.328125
totalWidth : 298.9921875
totalDeviceWidth: 597.984375
elemWidth : 299
elemDeviceWidth : 598
Run Code Online (Sandbox Code Playgroud)
把它们加起来,你可能会发现一个问题。根据getBoundingClientRect每一个大约是设备像素的 1/3 宽度 (199.328125)。实际上,您不可能拥有 0.328125 个设备像素,因此需要将它们转换为整数。让我们使用Math.round这样它们都变成199。
199 + 199 + 199 = 597
Run Code Online (Sandbox Code Playgroud)
但根据浏览器,父级的大小为 598 设备像素大。缺失的像素在哪里?
我们去问问吧
.split {
width: 299px;
display: flex;
}
.split>* {
flex: 1 1 auto;
height: 50px;
}
.left {
background: pink;
}
.middle {
background: lightgreen;
}
.right {
background: lightblue;
}
pre { margin: 0; } Run Code Online (Sandbox Code Playgroud)
<div class="split">
<div class="left"></div>
<div class="middle"></div>
<div class="right"></div>
</div>
Run Code Online (Sandbox Code Playgroud)
left : width = 99.6640625, deviceWidth: 199.328125
middle: width = 99.6640625, deviceWidth: 199.328125
right : width = 99.6640625, deviceWidth: 199.328125
totalWidth : 298.9921875
totalDeviceWidth: 597.984375
elemWidth : 299
elemDeviceWidth : 598
Run Code Online (Sandbox Code Playgroud)
上面的代码询问使用ResizeObserveranddevicePixelContentBoxSize目前仅在 Chromium 浏览器和 Firefox 上支持。对我来说,中间的 div 得到了额外的像素
left : width = 199
middle: width = 200
right : width = 199
Run Code Online (Sandbox Code Playgroud)
所有这一切的重点是
image-rendering: pixelated; image-rendering: crisp-edges解决方案:TL;DR 2022 年没有跨浏览器解决方案
在 Chrome/Firefox 中,您可以使用上面的 ResizeObserver 解决方案。
在 Chrome 和 Firefox 中,您还可以计算分数大小的画布
换句话说。典型的“填充页面”画布
<style>
html, body, canvas { margin: 0; width: 100%; height: 100%; display: block; }
<style>
<body>
<canvas>
</canvas>
<body>
Run Code Online (Sandbox Code Playgroud)
不管用。请参阅上面的原因。但你可以这样做
这最终会在右侧和底部边缘留下 1 到 3 个设备像素的尾部间隙,但它应该为您提供 1x1 像素的画布。
199 + 199 + 199 = 597
Run Code Online (Sandbox Code Playgroud)
const observer = new ResizeObserver(entries => {
for (const entry of entries) {
let good = false;
if (entry.devicePixelContentBoxSize) {
// NOTE: Only this path gives the correct answer
// The other paths are imperfect fallbacks
// for browsers that don't provide anyway to do this
width = entry.devicePixelContentBoxSize[0].inlineSize;
height = entry.devicePixelContentBoxSize[0].blockSize;
good = true;
} else {
if (entry.contentBoxSize) {
if (entry.contentBoxSize[0]) {
width = entry.contentBoxSize[0].inlineSize;
height = entry.contentBoxSize[0].blockSize;
} else {
width = entry.contentBoxSize.inlineSize;
height = entry.contentBoxSize.blockSize;
}
} else {
width = entry.contentRect.width;
height = entry.contentRect.height;
}
width *= devicePixelRatio;
height *= devicePixelRatio;
}
log(`${entry.target.className.padEnd(6)}: width = ${width} measure = ${good ? "good" : "bad (not accurate)"}`);
}
});
document.querySelectorAll('.split>*').forEach(elem => {
observer.observe(elem);
});
function log(...args) {
const elem = document.createElement('pre');
elem.textContent = args.join(' ');
document.body.appendChild(elem);
}Run Code Online (Sandbox Code Playgroud)
.split {
width: 299px;
display: flex;
}
.split>* {
flex: 1 1 auto;
height: 50px;
}
.left {
background: pink;
}
.middle {
background: lightgreen;
}
.right {
background: lightblue;
}
pre { margin: 0; }Run Code Online (Sandbox Code Playgroud)
在 Safari 中,没有解决方案:Safari 既不支持缩放devicePixelContentBoxSize,也不调整缩放devicePixelRatio以响应缩放。从好的方面来说,Safari 永远不会返回小数devicePixelRatio,至少从 2022 年开始是这样,但如果用户缩放,您将不会在 Safari 上获得 1x1 设备像素。
有一个关于CSS 属性的image-resolution提案,如果将其设置为 ,snap将有助于解决 CSS 中的这个问题。不幸的是,截至 2023 年 5 月,它还没有在任何浏览器中实现。
| 归档时间: |
|
| 查看次数: |
5641 次 |
| 最近记录: |