为什么Text.splitText()会影响布局?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (18)

假设我们在页面中有一段文字,只有一段文字。

<p>laborum beatae est nihil, non hic ab, deserunt repellat quas. Est molestiae ipsum minus nesciunt tempore voluptate laboriosam</p>

DOM明智的结构是:

HTMLParagraphElement
  Text [laborum beatae est nihil...]

现在我们分Text.splitText()两次(分开),将“deserunt repellat quas。est”片段分开。结构变成:

HTMLParagraphElement
  Text [laborum beatae est nihil...]
  Text [deserunt repellat quas. Est]
  Text [ molestiae ipsum minus nesciunt...]

虽然此操作影响DOM,但它在元素级别(Text!==元素)上从不改变它,所以我预计没有视觉变化。

然而splitText(),还会影响布局,触发所有测试过的浏览器(Chrome 60,Firefox 55,Edge 14 - 都在Windows 10 OS上)的重新布局和重新绘制。我们打电话时也会发生同样的情况ParagraphElement.normalize(),将Text节点的数量减少到1; 同样触发重新布局和重绘。

这是一个令人讨厌的副作用,可以在这个演示中看到。如果你检查'quas'附近的单词。Est',你看他们实际上改变了立场!

它在Firefox中清晰可见,并且在Chrome中更加微妙(但也有区别)。为了我的喜悦,Edge没有发生这样的“舞蹈”。

这个重要的原因显示在这个(某种)shimmed选择引擎的演示中。这个特殊的版本在Firefox中不起作用(不支持caretRangeFromPoint- 不!),但即使将“point2dom”重新连线到caretPositionFromPoint突出显示的文本上,也会在Chrome中重新定位 - 甚至更糟糕。再次,它似乎在Edge中运行良好。

所以,事实上,我最感兴趣的是理解原因并找出解决方法。

这里是动画的gif,展示了第一个演示如何在Chrome中播放(我只是触发点击间隔)

这里的颤抖很微妙,但仍然可以在所有单词上观察到。我特别困惑于为什么imolestiae摇晃,因为周围的信件似乎留在原地。

而且它变得更糟(更糟糕),使用不太常见的字体和更多文本,就像在选择演示中一样。

切换到font-family:monospace没有解决这个问题,但它看起来更糟糕:

切换font-kerningnone没有帮助。

提问于
用户回答回答于

tl:dr版本

splitText()可能是触发更改的动作,但更改实际上是由更新的dom通过文本调整引擎运行引起的。从text-align: justify;改变 text-align: left;到看我的意思。

附录

这处理转移的话。至于在证明被关闭时转移的字母,这有点难以量化,但它似乎是一个四舍五入的阈值。

文本理由中的注意事项

文字证明至少可以说是很复杂的。为了简化,有三个基本考虑因素:

  1. 准确性
  2. 美学
  3. 速度

任何一方的收益都需要其中一方或双方的损失。例如,有利于美学的InDesign使用字间距(正面和负面),字母间距(正面和负面),并考虑段落中的所有线条,以速度为代价找到最令人愉快的布局,并允许光学以准确性为代价的边缘对齐。但是因为它只经历了所有这一切,并将结果缓存在文件中,它可能会因(相对)非常慢而消失。

浏览器往往更关心速度,部分原因是它们需要能够在过时的硬件上快速找到正确的文本,还因为由于我们现在享受的交互性,有时需要重新证明同一块文本有成千上万在会议期间。

浏览器实现的差异

这个规范在辩护这个话题上有点模糊,他说:

在对文本进行验证时,用户代理会占用线条内容的末端与其线条边缘之间的剩余空间,并在整个内容中分配该空间,以便内容完全填充线框。用户代理可以替代地分配负空间,在正常间隔条件下放置更多的内容。

对于自动调整,本规范没有定义所有的调整机会是什么,它们是如何优先的,或何时以及如何多层次的调整机会相互作用。

因此,每个浏览器都可以根据他们认为合适的条件自由优化此功能。

一个例子

现代文本对齐引擎比我们在这里所能够合理解释的要复杂得多。补充说,他们不断调整,以找到主要考虑因素之间的更好平衡,我写在这里的任何内容将在几纳秒内过时,我将使用一个非常旧的(并且更简单的)文本调整算法来演示在这种情况下引擎可能难以一致地呈现。

假设我们有字符串,'Lorem ipsum dolor sit amet, consectetur.'并且我们可以在一行中容纳35个字符。那么让我们使用这个调整算法:

  1. 创建一个数组
  2. 对字符串进行处理,直到找到单词或标点符号的尾部,后跟空格或字符串的结尾
  3. 当您找到单词的结尾时,请检查单词的长度+数组中所有单词的长度以及对齐机会的数量。如果是这样,请将其从字符串中删除并放入数组中。
  4. 当没有更多的单词可以添加到数组中时,请将所需空间和可用空间之间的差异除以对齐机会的数量,然后绘制数组,在每个单词之间放置该空间量。

使用这个算法:

  1. 拆分字符串 'Lorem ipsum dolor sit amet, consectetur.' => ['Lorem','ipsum','dolor','sit','amet,'] & 'consectetur.'
  2. 有23个字符的文字宽度和35个可用空间字符,我们在每个单词之间添加3个空格(我将重点空间翻两番,这在以后很重要) ------------------------------------------------------------------------- |Lorem ipsum dolor sit amet,| |consectetur. | -------------------------------------------------------------------------

这很快,因为我们可以从左到右绘制所有内容,无需回溯,而且我们不需要向前看。

如果我们运行textSplit这个并有效地将它变成一个数组: ['Lorem ipsum dolor ','sit',' amet, consectetur.'] 所以我们需要修改规则,让我们改变规则2来处理数组中的每个字符串,遵循与以前相同的规则。

  1. 拆分字符串,请注意,在amet之前有一个空格,所以字边界将无法捕捉它 `['Lorem ipsum dolor ','sit',' amet, consectetur.']` => ['Lorem','ipsum','dolor','sit',' amet,'] & 'consectetur.'
  2. 使用24个字符的文字宽度和35个可用空格字符,我们在每个单词之间添加2.75个空格(再次四倍空间)。amet字符串中的额外空间也被绘制。 ------------------------------------------------------------------------- |Lorem ipsum dolor sit amet,| |consectetur. | -------------------------------------------------------------------------

如果我们看一下这两条线,我们可以看到差异。

  -------------------------------------------------------------------------
a |Lorem            ipsum            dolor            sit            amet,|
b |Lorem           ipsum           dolor           sit               amet,|
  -------------------------------------------------------------------------

再次,这些被夸大了,现实生活中的四分之一空间这只会是一两个像素。

我们的规则很简单,所以我们可以很容易地解决这个问题。

考虑一下当调试引擎需要支持时调试的复杂程度:

  1. 其他(可调整的)css属性用于对齐
  2. 多种语言及其所有规则
  3. 有以下字体:
    • 可变字符宽度
    • 克宁度量
    • 不一定与像素对齐的矢量

  4. 内联(和内联块)具有自己样式的元素
  5. ...继续

更不用说,其中大部分实际上已交给GPU来实际绘制。

无论如何,这一切说

请记住,事实上,你正在改变dom并迫使整个区块重新渲染。鉴于所涉及的因素数量众多,很难期望两个不同的dom结构总是呈现完全相同的结果。

附录

关于似乎偶尔会有所转变的信件,我所说的关于如何处理这些事情的复杂性的大部分内容仍然适用于以下内容。

同样,为了提高速度,数字通常会在向GPU传递渲染之前舍弃。

通过提供一个简化的例子,百分之一像素没有太大的差别,它对人眼来说是不可感知的,因此浪费了处理能力。所以你决定四舍五入到最近的像素。

假设字符绘制顺序是:

  1. 以容器的开始
  2. 添加偏移量
  3. 在此位置绘制字符,四舍五入到最近的像素
  4. 通过添加字符的未包围宽度来更新偏移量。

字符宽度:

10.2 10.3 10.4 10.2 10.6 11 8.9 9.9 7.6 9.2 9.8 10.4 10.4 11.1 10.5 10.5

从0点开始将画在:

0    10   21   31   41   52 63  72  82  89  98  108  119  129  140  151

但是如果你到达节点的末端呢?那么很简单,只需在下一个抽签位置开始新节点并继续?

字符宽度:

10.2 10.3 10.4 10.2 10.6 11 8.9|9.9 7.6 9.2 9.8 10.4 10.4 11.1 10.5 10.5

从0点开始将画在:

0    10   21   31   41   52 63  72  82  89  99  108  119  129  140  151

即使底层数字不同,由于四舍五入,渲染位置对于除第11个字符之外的每个位置都保持相同。

它可能不一定是开始的位置,再次,这里的复杂性非常大。症状确实指向某种类型的舍入阈值。正如我之前所说,在渲染两种不同的dom树时,应该预料到不同之处。

用户回答回答于

关于重新布局/重新绘制它是可以预料的,因为文本节点也是DOM节点...不是DOM元素,但浏览器必须重新考虑布局,即使您希望它保持不变,它们可能不得不移动。也许是因为字距变化。

现在,为什么分割文本会导致一些移动?我期望的是因为浏览器分别绘制文本部分。两个相邻的字母通常有一个空间,根据字母的不同,字体可能会减少字体,例如以“WA”为例,W的结尾高于A的开头,即所谓的字距(Thx Ismael Miguel)。当文本节点分开绘制时,每个文本节点必须在下一次开始之前完成,因此它可以在这些字母之间创建更大的空间,因为它可以防止任何字距。

对不起,字母之间的空格确实有名字,但我忘了...

.one {
  background-color: #FF9999;
}

.two {
  background-color: #99FF99;
}

body {
  font-size: 40px;
}

div>span {
  border: 1px solid black;
}

<div><span>AW</span> - in the same DOM node.</div>
<div><span><span>A</span><span>W</span></span> - in two different nodes</div>
<div><span><span class="one">A</span><span class="two">W</span></span> - in two different nodes, colored</div>

至于如何防止这种行为,最简单的解决方案是使用等宽字体。这可能并不总是美学上可行的。Kerning是嵌入在字体文件中的信息,从这些信息中剥离字体似乎是防止闪烁的最可靠的方法。此外,设置为时CSS属性font-kerning可以帮助none

另一种方法是在文本后面或前面添加绝对元素以模仿将文本的一部分包含到元素中的事实,但这完全取决于最终目标。

扫码关注云+社区