首页 > 其他分享 >我写了一个 HTML 画布数据网格,所以你不必

我写了一个 HTML 画布数据网格,所以你不必

时间:2022-09-06 09:36:58浏览次数:100  
标签:渲染 单元格 网格 画布 HTML 文本

我写了一个 HTML 画布数据网格,所以你不必

A grid of data being displayed in an excel like visual with rich data renderings, including inline charts, links, checkboxes, and images.

Glide Data Grid in all its Canvas based glory

对于 Web 开发人员来说,HTML 5 画布是一个非常强大且未被充分利用的工具。它也很难以高性能、美观和可访问的方式使用。我写 滑翔数据网格 因为 滑行 需要一个包含所有这些东西的数据网格,我选择了 HTML 5 画布,因为它是我唯一可以通过非凡的努力实现我所有目标的东西。

在我们谈论我创造的傲慢纪念碑之前,让我告诉你所有你不应该自己尝试的原因。

您可能不需要性能

您有 1000 多行数据吗?您的用户真的需要以 60FPS 的速度滚动而没有视觉障碍吗?加载所有单元格是否需要几秒钟?如果没有,你应该坚持使用 HTML。

现代浏览器在真正开始挣扎之前会很好地处理绝对愚蠢的 HTML。一百万个细胞?可能需要一到五分钟,但最终会呈现。如果你能等到一切都膨胀,你最好还是这样做。

你可能没有经验

用画布画东西很容易。用画布绘制一些东西,让它像素完美,调整高 DPI 显示,并且不陷入各种可怕的性能黑客和渲染伪像的困境,这真的很难。我并不是说我是比你更好的程序员,我不是,我只是在我职业生涯的大部分时间里都被 2D 绘图库打败了。

有时你需要文本来换行

Canvas 不适合你,我希望你喜欢编写文本布局引擎。 我知道我做到了 .

可访问性已经够难了,不应该是事后的想法

我们生活在一个社会中;使您的网站易于访问。如果您要忽略我上面所说的一切并继续前进,请记住,无论如何您都必须基本上实现数据网格的 HTML 版本以使其可访问。您仍然必须确保屏幕阅读器可以看到数据,并且他们不会说画布,但他们会说画布子域。您需要确保可以将输入路由到这些子域组件。对你来说,这将是乐趣的反面。

其他会很糟糕的事情

其他会很糟糕的事情的非详尽列表:阴影,过滤器,带下划线的文本,删除线文本,任何不是简单文本的东西,图像加载,绘制 svg,为 hidpi 显示绘制图像,处理 hidpi 而不会降低fps,意识到 GPU 已经饱和,而不是 CPU。不要忘记,即使你只想画一条 1px 的线,你也必须考虑半像素值。

如果我忽略你刚才所说的一切会发生什么?

据我所知,有两种可能的结果。

  1. 您失败并最终使用 ag-grid 或 handsontable
  2. 你制作了像 Glide Data Grid 这样的东西

让自己看起来像什么?首先让我们忽略所有简单的问题,无论渲染解决方案如何,这些问题对每个人来说都基本相同。像这样的问题

  • 您需要一种获取数据的方法
  • 如果该数据是声明性的,则您需要一种廉价获取大量数据的方法
  • 您需要一种用于排序、过滤和重新排列数据的机制
  • 你需要大量的输入编辑器
  • 多得多

此外,您将不得不解决许多基于画布的解决方案所独有的晦涩和有趣的问题。

绘图状态机现在由您管理

通常,浏览器会弄清楚如何以高效的方式为您绘制内容。现在这是你的问题。必须仔细管理 CanvasRenderingContext2D 的每次调用、每次状态更改。这里有一些提示可以帮助您完成。

将您的抽奖电话分组在一起

尽最大努力在一次笔画调用中笔画所有线条,在一次填充调用中完成尽可能多的填充。请记住,您可以跟踪多个路径并为每个调用定义多个区域。用它。如果您正在绘制网格线,请一次绘制它们。

剪辑您的损坏区域

如果您知道只有一个单元格发生了变化,请将您的剪辑设置为该单元格并仅重绘该单元格。确保不要为剪辑区域之外的东西发出绘制调用,否则你不会让事情变得更快。

避免状态更改并保存/恢复

HTML 画布的状态更改和保存/恢复非常慢。比您预期的要慢几个数量级。一个简单的画布实现可能最终会花费一半的 CPU 时间来进行不必要的状态转换。你可能认为调用 ctx.font = 我的字体; 因为每个单元格在第一个单元格之后都是无操作的,但是性能分析器会迅速消除您的这种想法。

事实上,简单地从 ctx 非常昂贵,比访问简单的 JavaScript 对象属性慢一个数量级。这意味着维护您上次设置属性的缓存通常比不断检查它实际设置的内容要快得多。将这种策略与许多 节省 或者 恢复 通话可能会引起头痛,因此您可能也希望将其最小化。

批量加载图像

加载图像现在是您的问题,有效地为您的图像创建和分发下载是很困难的。使用一个池 HTMLImageElements 管理您的下载。不要忘记,无论您创建多少个,一次只会下载这么多,浏览器会限制打开的连接。因此,您不仅需要自己管理这些图像,还要确保取消对于已滚动到视图之外的单元格仍处于活动状态的下载。

当然,这里也有 gatchyas。

 常量图像 = 新图像(); // 昂贵的  
 图片.src = "<https://myimage.server/image.png> “; // 非常贵 // 发现你不再出现在屏幕上 图片.src = ""; // 取消下载,代价惊人

因为设置成本 图片.src 回到一个空字符串(取消下载的唯一可靠方法),您需要平衡何时值得与何时将其推回池中而不取消。我的建议:让你的参数可调并测试,直到它按你喜欢的方式工作。

自己动手……自己

CanvasRenderingContext2D.drawImage 命令能够使用相同的画布作为源和目标。如果您正在垂直滚动,使用它可以快速上下复制像素,因此您只需要绘制新显示的区域。

例如 ctx.drawImage(ctx.canvas, 0, 0 10, 10, 0, 5, 10, 10);

请注意,这仅适用于垂直或水平偏移。当对角线传输到自己身上时,所有主要浏览器似乎都进入了缓慢的绘制调用路径,显着提高了 GPU 利用率,并且几乎抵消了传输带来的任何节省。

requestAnimationFrame 有点烂

window.requestAnimationFrame 是惊人的,直到它不是。它的好处是仅限于您的垂直同步速率,坏处是它只限于您的垂直同步速率。你可能会发现你的数据网格实现效果很好,动画效果很好,直到有人用 120hz 显示器开始抱怨它占用了 50% 的 CPU,坐在那里渲染一个微调器。哎呀。

这里有很多解决方案,但它们的要点都归结为只在第 N 次调用 window.requestAnimationFrame 当你觉得时间已经足够了。我选择允许全速播放 10 秒,然后在动画期间切换到每隔一帧。这可以让短暂的动画全速运行,同时防止长时间的动画消耗过多的电池寿命。

逐列渲染

大多数单元格样式将在列中是同质的,这为优化提供了机会。设置你的字体一次,你的颜色一次,哎呀,你可以在一次调用中渲染许多单元格,这取决于它们是什么。将您的剪辑区域设置为整个列,而不是逐个单元格地剪辑。

避免阿尔法混合

如果你能侥幸成功,渲染纯色会明显更快。它避免了在绘图库中逐像素混合 alpha 的需要。关闭画布上下文中的 alpha 通道。与其使用平坦的透明颜色在彼此的顶部绘制多个框,不如预先计算所有分层的最终结果颜色并绘制一次该颜色。它看起来一样,并且速度至少快 5 倍。

规范化你的画布

将画布缩放到显示器的 DPI。如果数学对你和我一样可怕,你可能想把它四舍五入到最接近的整数。这将使您编写大部分代码,就好像 hi-dpi 不存在一样,直到您需要将画布粘贴到自身上,然后您才需要考虑它。

可访问性比您想象的要容易得多

可访问性很难,真的很难。你不会做对的,幸运的是,你基本上可以从 W3C 复制和粘贴可访问性示例,无需再三思而后行,而不必真正关心它们的外观!为什么?

 <canvas>  
 <p>这不会正常呈现,但屏幕阅读器会看到我。</p>  
 <button>他们可以点击我!</button>  
 </canavas>

现实情况是屏幕阅读器有这种超能力来检查画布的子域。他们可以聚焦对您的用户隐藏的元素,他们可以检查所有这些数据,如果您很聪明,您可以非常有效地虚拟化该子域。确保您注意来自该 DOM 的事件,并且一切顺利!

这意味着你的 DOM 看起来像

 <canvas>  
 <!-- You will want to place more aria-tags, but keeping the example clean -->  
 <table>  
 <tr aria-rowindex="2">  
 <td aria-colindex="5">单元格 1</td>  
 <td aria-colindex="8">一张狗的照片</td>  
 </tr>  
 <!-- More stuff here -->  
 </table>  
 </canvas>

将此与其他基于 HTML 的解决方案进行比较,一旦他们开始需要固定列、排序、虚拟化等内容,您就会发现他们通常无法以一种对屏幕阅读器有效的方式来构建 DOM实际阅读。其中许多解决方案将具有严重影响性能的可访问性特殊模式,其中一些甚至默认关闭这些模式,将需要可访问性工具的人视为二等公民。如果您已经在使用其中一个主要的数据网格,您可能需要检查一下……

然而,你不会犯这个错误。默认情况下,您将拥有此 subdom 渲染,不允许将其关闭,您将使其成为网格的主要输入机制。下次当你提到你的基于画布的解决方案时有人对你说“祝你好运,ADA 合规性”时,你可以说每个程序员最喜欢的两个词:“好吧,实际上……”

这不仅仅是关于屏幕阅读器

屏幕阅读器还有更多可访问性。您应该尝试尊重其他设置,例如暗模式(是的,这应该被视为可访问性的一部分)并且更喜欢减少运动。这两个都可以在 JavaScript 中读取,如果您对动画进行了合理的编码,您可以直接将它们跳到最后,或者更好地使用低运动替代方案或没有运动替代方案。

处理输入

当单元格有焦点时,不要尝试在画布上呈现输入,只需弹出一个带有 <input> 在里面。如果它对 Google 表格足够好,那么对您来说就足够了!

渲染字体很慢,真的很慢

一个实施良好的数据网格将花费大约 50% 的 CPU 时间在 填充文本 功能。为什么?因为 字体渲染起来非常复杂 并且您正在跨越 JS/Native 桥进行文本渲染调用,而浏览器在呈现 HTML 时不会。你可以做一些事情来让它更快。

保守截断

测量文字也很慢,尽量避免测量文字。为避免测量文本,请考虑在单元格边缘剪裁文本,而不是计算省略号的放置位置。如果出现一根特别长的绳子,它会被剪成一个小区域,请先将绳子截断成更小的东西。无论剪辑区域如何,您都需要支付字符串的全部渲染和布局成本。

如果您正在处理非罗马语言,您不会不小心半个字母,请小心。

使用 textAlign 避免测量

如果要右对齐文本,请使用 文本对齐 参数来右对齐文本,而不是自己测量文本。请记住尽量减少设置 文本对齐 参数,因为设置起来非常昂贵。

当您使用它时,请考虑使用 文本基线 可以在不测量的情况下将文本居中。有些字体会比其他字体更好地对此做出反应。

当一切都失败时

通常这是我推销的部分 滑翔数据网格 告诉你,与其自己建造,不如让我为你建造。问题是,我不卖 Glide Data Grid,所以来帮我构建它, 提交公关 ,让我知道您的项目需要什么功能,我们可以一起构建一个快速、零妥协、高度可访问的 React 数据网格。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明

本文链接:https://www.qanswer.top/18540/15150609

标签:渲染,单元格,网格,画布,HTML,文本
From: https://www.cnblogs.com/amboke/p/16660593.html

相关文章