在所有设备上显示像素完美的画布

2023-11-25

我有一些画布,我想在每个(现代)浏览器中以像素完美的方式显示它们。默认情况下,具有高 DPI 屏幕的设备会缩放我的页面,以便所有内容看起来都大小正确,但它破坏了*我画布的外观。

如何确保画布中的一个像素 = 屏幕上的一个像素?最好这不会影响页面上的其他元素,因为我仍然想要例如针对设备适当缩放的文本。

我已经尝试根据画布尺寸设计样式window.devicePixelRatio。这使得画布尺寸合适,但内容看起来更糟。我猜这只是在它们已经错误地放大之后缩小它们。

*如果你关心的话,因为画布使用抖动并且浏览器正在执行某种 lerp 而不是最近邻居


当前标记的答案是错误的

2022 年,我们无法在所有设备、所有浏览器上获得像素完美的画布。

您可能认为它正在使用

canvas {
    image-rendering: pixelated;
    image-rendering: crisp-edges;
}

但要看到它失败,您所要做的就是按“放大”/“缩小”。缩放并不是唯一失败的地方。许多设备和操作系统都有小数 devicePixelRatio。

换句话说。假设您制作了 100x100 的画布(放大)。

100x100 image canvas

用户的devicePixelRatio是1.25(这就是我的台式电脑)。然后,画布将以 125x125 像素显示,而您的 100x100 像素画布将通过最近邻过滤(图像渲染:像素化)缩放到 125x125,并且看起来非常糟糕,因为某些像素为 1x1 像素大,而其他像素为 2x2 像素大。

enter image description here

所以不,使用

image-rendering: pixelated;
image-rendering: crisp-edges;

其本身并不是一个解决方案。

更差。浏览器可以以小数形式工作,但设备不能。例子:

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);
}
.split {
  width: 299px;
  display: flex;
}
.split>* {
  flex: 1 1 auto;
  height: 50px;
}
.left {
  background: pink;
}
.middle {
  background: lightgreen;
}
.right {
  background: lightblue;
}
pre { margin: 0; } 
<div class="split">
  <div class="left"></div>
  <div class="middle"></div>
  <div class="right"></div>
</div>
  

上面的示例有 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

把它们加起来,你可能会发现一个问题。根据getBoundingClientRect每个宽度约为设备像素的 1/3 (199.328125)。实际上您不可能拥有 0.328125 个设备像素,因此需要将它们转换为整数。让我们使用Math.round所以他们都变成了199。

199 + 199 + 199 = 597

但根据浏览器,父级的大小为 598 设备像素大。缺失的像素在哪里?

我们去问问吧

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);
}
.split {
  width: 299px;
  display: flex;
}
.split>* {
  flex: 1 1 auto;
  height: 50px;
}
.left {
  background: pink;
}
.middle {
  background: lightgreen;
}
.right {
  background: lightblue;
}
pre { margin: 0; }
<div class="split">
  <div class="left"></div>
  <div class="middle"></div>
  <div class="right"></div>
</div>

上面的代码要求使用ResizeObserver and devicePixelContentBoxSize目前仅在 Chromium 浏览器和 Firefox 上支持。对我来说,中间的 div 得到了额外的像素

left  : width = 199
middle: width = 200
right : width = 199

所有这一切的重点是

  1. 你不能只是天真地设定image-rendering: pixelated; image-rendering: crisp-edges
  2. 如果您想知道画布中有多少像素,您可能需要询问,并且只能在 Chrome/Firefox ATM 上找到

解决方案:TL;DR2022 年没有跨浏览器解决方案

  • 在 Chrome/Firefox 中,您可以使用上面的 ResizeObserver 解决方案。

  • 在 Chrome 和 Firefox 中,您还可以计算分数大小的画布

    换句话说。典型的“填充页面”画布

    <style>
    html, body, canvas { margin: 0; width: 100%; height: 100%; display: block; }
    <style>
    <body>
      <canvas>
      </canvas>
    <body>
    

    不管用。请参阅上面的原因。但你可以这样做

    1. 获取容器的大小(在本例中为主体)
    2. 乘以 devicePixelRatio 并向下舍入
    3. 除以 devicePixelRatio
    4. 使用该值作为画布 CSS 宽度和高度,使用 #2 值作为画布宽度和高度。

    这最终会在右侧和底部边缘留下尾部间隙 1 到 3 个设备像素,但它应该为您提供 1x1 像素的画布。

const px = v => `${v}px`;

const canvas = document.querySelector('canvas');
resizeCanvas(canvas);
draw(canvas);

window.addEventListener('resize', () => {
  resizeCanvas(canvas);
  draw(canvas);
});

function resizeCanvas(canvas) {
  // how many devicePixels per pixel in the canvas we want
  // you can set this to 1 if you always want 1 device pixel to 1 canvas pixel
  const pixelSize = Math.max(1, devicePixelRatio) | 0;  
  
  const rect = canvas.parentElement.getBoundingClientRect();
  const deviceWidth  = rect.width * devicePixelRatio | 0;
  const deviceHeight = rect.height * devicePixelRatio | 0;
  const pixelsAcross = deviceWidth / pixelSize | 0;
  const pixelsDown   = deviceHeight / pixelSize | 0;
  const devicePixelsAcross = pixelsAcross * pixelSize;
  const devicePixelsDown   = pixelsDown   * pixelSize;
  canvas.style.width = px(devicePixelsAcross / devicePixelRatio);
  canvas.style.height = px(devicePixelsDown / devicePixelRatio);
  canvas.width = pixelsAcross;
  canvas.height = pixelsDown;
}

function draw(canvas) {
  const ctx = canvas.getContext('2d');
  for (let x = 0; x < canvas.width; ++x) {
    let h = x % (canvas.height * 2);
    if (h > canvas.height) {
      h = 2 * canvas.height - h;
    }
    ctx.fillStyle = 'lightblue';
    ctx.fillRect(x, 0, 1, h);
    ctx.fillStyle = 'black';
    ctx.fillRect(x, h, 1, 1);
    ctx.fillStyle = 'pink';
    ctx.fillRect(x, h + 1, 1, canvas.height - h);
  }
}
html, body {
  margin: 0;
  width: 100%;
  height: 100%;
/* set to red we can see the gap */
/* set to some other color to hide the gap */
  background: red; 
}

canvas {
  display: block;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}
<canvas></canvas>

在 Safari 中没有解决方案:Safari 不提供以下支持devicePixelContentBoxSize它也不调整devicePixelRatio响应缩放。从好的方面来说,Safari 永远不会返回小数devicePixelRatio,至少从 2022 年开始,但如果用户缩放,您将无法在 Safari 上获得 1x1 设备像素。

有一个提议CSS 属性image-resolution,如果设置为snap将有助于解决 CSS 中的这个问题。不幸的是,截至 2023 年 5 月,它还没有在任何浏览器中实现。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在所有设备上显示像素完美的画布 的相关文章

  • Bootstrap 的跨浏览器数字微调器/步进器 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我正在寻找一个与 Twitter Bootstrap 2 3 集成的数字微调器 使用 HTML5input type number
  • 避免滚轮被嵌入的 youtube / flash 视频劫持

    我正在对主页中嵌入 YouTube 视频的网站进行一些改进 我自己没有添加此代码 但它看起来像
  • JavaFX:在 WebView img 标签中未加载本地图像

    以下是我的代码 一切安好 我可以加载远程页面 我可以放置 HTML 内容 但我的img标签显示一个X标志表示无法加载图像 Note 我的图像与类位于同一个包中JavaFX在 Smiley 文件夹中 我可以列出所有图像 这意味着路径没有问题
  • 在 jQuery 中获取最接近元素的形式

    我编写了这个 js jquery 脚本来检查表单中的所有复选框 它工作得很好 但是这会检查页面上的所有复选框 无论它们是什么表单包装器 这是函数 function toggleCheck state var checkboxes jQuer
  • 如何关闭 HTML 输入表单字段建议?

    我所说的建议是指当您开始输入时会出现下拉菜单 并且它的建议基于您之前输入的内容 例如 当我在标题字段中输入 a 时 它会给我大量的建议 这非常烦人 如何关闭此功能 你想要的是禁用 HTMLautocomplete属性 在这里设置 autoc
  • 使用 Jsoup 选择没有类的 HTML 元素

    考虑一个像这样的 html 文档 div p p p p p class random class name p div 我们怎样才能选择所有p元素 但不包括p元素与random class name class Elements ps b
  • 如何编码我的提交按钮转到电子邮件地址[重复]

    这个问题在这里已经有答案了 我的发送电子邮件按钮不起作用 这是我的 html 有人看到问题吗
  • 调整下拉框中列表的高度[重复]

    这个问题在这里已经有答案了 可能的重复 下拉框显示的高度 https stackoverflow com questions 5600982 height of the dropdown box display hi 如何调整下拉列表中列表
  • 如何使用div绘制曲线?

    我需要使用 CSS 绘制两条曲线 我尝试过组装一些divs 使用CSSborder radius绘制弧形面板 但结果很糟糕 还有更好的算术吗 正如我之前在评论中提到的 请不要使用CSS用于实现复杂的曲线和形状 虽然仍然可以使用 CSS 来实
  • 使用 Java 访问 HTML5 本地存储

    是否可以直接使用Java访问localstorage对象 如果是的话怎么办 更新 我知道 localstorage 是客户端 java 是服务器端 但我在网上读到 GWT 有 api 允许读取 localstorage 本地存储 顾名思义
  • 在 swift 3 的 textview 中显示属性文本?

    我想以斜体 粗体显示从服务器收到的文本 你好世界所以 responseObj text p b i hello i b i world gt i p if let postText String responseObj text as St
  • 等待网页完全加载,然后再使用 python 请求进行抓取

    我目前正在尝试从 LinkedIn 上的特定页面抓取数据 我有一个能够登录 LinkedIn 的脚本 但当我尝试访问包含数据的页面时遇到了障碍 当我打电话时requests get data url 我最终得到了 LinkedIn 加载屏幕
  • JavaScript 跨浏览器单击 HTML DOM 元素

    是否有可用的普通 JavaScript 跨浏览器函数能够触发 HTML DOM 元素 包括 div 等非表单元素 上的单击事件 大多数走这条路的人要么最终开发自己的事件管理系统 这并不难 但很烦人 要么在可用的功能范围内工作 如果所有浏览器
  • 使用 JSON 文件动态更新 HTML 内容?

    我想创建一个 JS 循环 使用 jQuery 来查看 JSON 文件 并根据是否 div ids 与 JSON id 值匹配 这需要易于扩展并且无论有多少人都可以工作 div 添加了盒子 我有一个 HTML 文件 设置如下 div clas
  • 当 MediaElementAudioSource 输出为零但 CORS 不再是问题时,如何才能播放音频?

    我正在尝试实施
  • 没有嵌套容器的桌面和移动 Flexbox 布局

    我有 3 个 div 它们必须按移动布局的特定顺序排列 但我必须将第 2 个 div 作为桌面布局的侧边栏 所以对于移动设备 分区1 分区2 分区3 对于桌面 分区1 div2 分区3 在桌面布局中 div 2 有阴影背景 因此必须是父级的
  • 使用 获取用于 javascript 的 RSA 密钥?

    我的 Web 项目需要一个 RSA 密钥对 虽然有一些库 但我认为依靠浏览器 为了安全性和速度 为我生成密钥是个好主意 是否可以使用注册机或其他浏览器 API 来执行此操作 我不知道如何从注册机获取密钥 它们似乎是在提交时生成的 但我不想将
  • 从网站存储数据的最简单方法(在服务器端)

    我有一个非常简单的网站 实际上是单页 有一个输入字段和一个按钮 我需要将用户提交的数据存储在服务器端的某个位置 完美的方法可能是简单的文本文件 并在每次单击按钮后附加新行 日志文件也可以 据我了解 JavaScript 本身是不可能的 我在
  • 显示班级图片 10 秒

    我有下面给出的代码显示9 boxes 其值如下digital time 还有一个班级box002显示digits相当于随机选择的九个盒子的值 box002 can be dragged to digital time starting wi
  • 让登录更安全

    我已使用此代码进行管理员登录 仅当用户输入正确的用户名和密码时才应打开loginhome php 但后来我意识到这根本不安全 任何人都可以直接访问 mywebsite loginhome php 而无需登录 注销后 可以使用后退按钮打开 l

随机推荐

  • 未检测到的 Chromedriver 未正确加载

    我正在尝试使用带有 selenium 的无头 chrome 浏览器 它也绕过了机器人检测测试 目前使用以下项目https github com ultrafunkamsterdam unDetected chromedriver每次我尝试实
  • 使用 ffmpeg_extract_subclip 提取视频的一部分 - 黑帧

    我正在尝试使用 ffmpeg extract subclip 用于提取视频的一部分 我面临着一些问题 1 当我剪切小视频 1 3秒 时 出现黑帧 只有音频在工作 2 当我剪切较长的视频时 输出的视频在结束前卡住了2 3秒 这是我的简单代码
  • iPhone 上的 UIWebView 电话链接检测

    我的代码中有一些奇怪的东西 我肯定会忘记一些事情 但我不知道是什么 我尝试在 UIWebView 中处理电话 短信邮件和 http 链接 这就是我尝试的方法 1 实例化 UIWebView webview UIWebView alloc i
  • 如何在 ggplot2 的轴标签中同时使用上标和变量

    我想一起使用一个变量 此处为向量元素 类型 和一个在 n 轴标签内包含上标 此处为 m 2 的单位 data lt list houses data frame surface c 450 320 280 price c 12 14 6 f
  • 如何在Android UI线程中异步执行一些代码?

    我是 Android 开发新手 我从事 Swing 和 SWT 工作已有好几年了 Swing 和 SWT 都有在 UI 线程同步和异步执行代码的策略 典型的用法是在一个线程中执行一些耗时的工作 然后在 UI 线程异步中显示结果 所以我的问题
  • OpenCV 3.0上的GPU函数在哪里?

    据我了解 在 OpenCV 3 0 中 GPU 模块已被 CUDA 模块取代 或者更好的是它已被拆分为多个模块 So cv gpu GpuMat已被替换为cv cuda GpuMat fine 但是功能呢 例如 以下内容已移至 cv gpu
  • Jenkins 上的多个 JUnit XML 结果,用单独的图表发布?

    我有多个测试作为 Android Jenkins 构建的一部分运行 包括单元测试和功能测试 我能够在 Jenkins 上成功发布测试结果 但我想查看单元测试和功能测试的单独结果图 Jenkins JUnit 发布者仅显示多个 XML 文件的
  • 数组:array_shift($arr) 还是 $arr[0]?

    您会使用哪一个 基本上我只想从数组中获取第一个元素 就是这样 嗯 他们做不同的事情 array shift arr 从数组中取出第一个元素 并将其提供给您 arr 0 只是给你 if该数组有数字键 另一种也适用于关联数组的替代方法是rese
  • MSVC errno 线程安全

    Is errnoMSVC 线程安全吗 根据中的答案这个问题POSIX 要求errno是线程安全的 但 MSVC 可能不符合 POSIX 标准并且MSDN没有告诉任何有关线程安全的信息 MSDN 矛盾地提到errno被声明为extern in
  • Python 类构造函数中是否有 `self.somevariable = somevariable` 的快捷方式?

    Python 中的构造函数通常如下所示 class SomeClass def init self a b None c defC self a a self b b or self c c 有没有捷径 例如简单地定义 init self
  • 使用 RAR 压缩文件

    我有一个文本文件 我想在它达到指定大小后对其进行压缩 我已经看过了GZipStream效果很好 但 RAR 压缩要好得多 我一直在寻找一个可以的图书馆compress一个 RAR 文件 我真的不关心解压或解压缩 但我还没有找到 As the
  • 如何使用外部 .py 文件?

    我下载了 beautifulsoup py 用于我正在制作的一个小项目 我需要在我的项目中导入这个 py 文件吗 我是否只需将代码复制并粘贴到当前 python 脚本中的某个位置 感谢您的帮助 我发现了这个 但它没有说任何有关 Window
  • 将 pandas'to_html' 保存为文件

    我有一个 DataFrame tsod 现在我将其转换为 html tsod to html 我怎样才能将其保存为文件 最好另存为 html 文件 with open my file html w as fo fo write tsod t
  • 在 Azure databricks 中创建外部表

    我是 azure databricks 的新手 并尝试创建一个外部表 指向 Azure Data Lake Storage ADLS Gen 2 位置 在 databricks 笔记本中 我尝试设置 ADLS 访问的 Spark 配置 我仍
  • 将 google-cloud 与 webpack 集成时遇到问题

    我正在将 google cloud npm 包与我的 React 应用程序集成 并且我正在使用 firebase 我遇到的错误 google cloud hash stream validation index js 模块中的警告 未找到
  • 使用新身份验证方法的 Symfony 简单登录表单不起作用

    我在设置新的 symfony 应用程序时遇到问题 我确信它与新的基于身份验证器的安全系统有关 我安装了一个新的 symfony 应用程序 版本 5 3 6 安装安全包composer require symfony security bun
  • C 中的逻辑运算符

    我在尝试理解逻辑运算符在 C 中的工作原理时遇到了困难 我已经了解位级运算符的工作原理 而且我还知道逻辑运算符将非零参数视为代表 TRUE 将零参数视为代表 FALSE 但是假设我们有 0x65 0x55 我不明白这个操作为什么以及如何给出
  • Skype 在 imo.im 和 im+ 服务中如何工作?

    Skype 在 imo im 和 im 服务中如何工作 有什么猜测吗 我认为只有3个办法 为服务器上的每个连接客户端运行多个 Skype 客户端副本 从 Skype Kit 为服务器上的每个客户端运行多个运行时副本 Skype协议的逆向工程
  • LINQ GroupBy 连续时间

    假设我有一个如下所示的简单结构 public class Range public DateTime Start get set public DateTime End get set public Range DateTime start
  • 在所有设备上显示像素完美的画布

    我有一些画布 我想在每个 现代 浏览器中以像素完美的方式显示它们 默认情况下 具有高 DPI 屏幕的设备会缩放我的页面 以便所有内容看起来都大小正确 但它破坏了 我画布的外观 如何确保画布中的一个像素 屏幕上的一个像素 最好这不会影响页面上