如何使用JS在固定宽度div的末尾自行检测一行中的单词"orphaned"?

How to use JS to detect words "orphaned" on a line by themselves at the end of a fixed-width div?

我不确定 "orphaned" 是否合适,但这是我能想到的最好的。

我希望能够检测到 div 中文本末尾的最后一行中何时有一个或两个单词。

请注意:我并不是想通过在最后一行给他们加上另一个词来消除这些 "orphans"。

我有一个固定宽度的 div,其中包含我需要在行尾均匀结束的文本。有时,一个(或两个)词会悄悄地延伸到另一行。我希望能够检测到这种情况何时发生(这样代码就可以调整 div 的字体大小,直到孤立的 word/words 弹出到上一行)。

使用 text-align-last: justify; 本身并不能解决我的问题。

我一直在尝试 element.getClientRects(),我想我会为每一行得到一个矩形,然后只查看给定的最低矩形的宽度。但是对于单个 div,我只得到一个矩形。如果我将文本放在 div 内的一个跨度内,我有时会得到多达九个矩形,用于跨度中的六行文本。哎呀!我不知道这些矩形代表什么。

任何人都知道我可以在固定宽度的多行文本末尾单独检测一行上额外的孤立单词的方法 div?


加法

请注意:最佳行数无法提前知道;它可以是一到五行。

有人要求我举个例子。

示例1显示是三行,但应该是两行:

<div class="chunk" style="text-indent:45ex;">
<div class="bekkerLine">25</div>
<span>τὸ μὲν οὖν εἰ ἓν καὶ ἀκίνητον τὸ ὂν σκοπεῖν οὐ περὶ φύσεος ἐστι σκοπεῖν·</span>
</div>

$0.getClientRects() 在跨度 returns:

DOMRectList {0: DOMRect, 1: DOMRect, 2: DOMRect, 3: DOMRect, length: 4}
0: DOMRect {x: 475.796875, y: 328.875, width: 60.609375, height: 22, top: 328.875, …}
1: DOMRect {x: 536.40625, y: 328.875, width: 48.21875, height: 22, top: 328.875, …}
2: DOMRect {x: 139, y: 352.875, width: 445.546875, height: 22, top: 352.875, …}
3: DOMRect {x: 139, y: 376.875, width: 65.109375, height: 22, top: 376.875, …}
length: 4
__proto__: DOMRectList

示例 2 也显示为三行,但同样应为两行:

<div class="chunk">
<span> οὔ)· ἄνθρωπος γὰρ ἵππου ἕτερον τῷ εἴδει καὶ
 τἀναντία ἀλλήλων. 
 καὶ πρὸς Παρμενίδην δὲ ὁ αὐτὸς τρόπος τῶν λόγων,
 <div class="bekkerLine">22</div>
</span></div>

$0.getClientRects() 在跨度 returns:

DOMRectList {0: DOMRect, 1: DOMRect, 2: DOMRect, 3: DOMRect, 4: DOMRect, length: 5}
0: DOMRect {x: 123, y: 319.875, width: 375.5, height: 22, top: 319.875, …}
1: DOMRect {x: 498.5, y: 319.875, width: 70.203125, height: 22, top: 319.875, …}
2: DOMRect {x: 123, y: 343.875, width: 82.40625, height: 22, top: 343.875, …}
3: DOMRect {x: 205.40625, y: 343.875, width: 363.46875, height: 22, top: 343.875, …}
4: DOMRect {x: 123, y: 367.875, width: 53.578125, height: 22, top: 367.875, …}
length: 5
__proto__: DOMRectList

示例 3 我不想 "fix" 因为最后一行的单词太多:

UPDATED:现在解决方案会检测最后一行是否有 THRESHOLD 个单词。这里我把THRESHOLD设置为2。对于第二个 div 它在最后一行以三个词结尾并且没有改变。对于第三个 div 它在最后一行以一个词结束并缩小。在第四个 div 中,它从最后一行的两个单词开始并缩小。

const THRESHOLD = 2;

document.querySelectorAll("div.fixed").forEach(el => {
  const { height: originalHeight } = el.getClientRects()[0];
  let thresholdIndex;
  for (let i = el.innerHTML.length, occurences = 0; i > 0; i--) {
    if (el.innerHTML[i] === " ") {
      occurences++;
    }
    if (occurences === THRESHOLD) {
      thresholdIndex = i;
      break;
    }
  }

  if (thresholdIndex) {
    const lastWords = el.innerHTML.substring(
      thresholdIndex,
      el.innerHTML.length
    );
    const text = el.innerHTML.substring(0, thresholdIndex);
    el.innerHTML = text;
    const { height: newHeight } = el.getClientRects()[0];
    el.innerHTML += lastWords;
    if (newHeight < originalHeight) {
      let height = originalHeight;
      while (height > newHeight) {
        const currentFontSize = parseInt(
          window.getComputedStyle(el).getPropertyValue("font-size"),
          10
        );
        const newFontSize = currentFontSize - 1;
        el.style.fontSize = `${newFontSize}px`;
        height = el.getClientRects()[0].height;
      }
    }
  }
});
div {
  width: 200px;
  padding: 10px;
  font-size: 16px;
}
<div class="fixed">Text Text Text Text Text Text</div>
<div class="fixed">Text Text Text Text Text Text Text Text Text</div>
<div class="fixed">Text Text Text Text Text Text Text Text Text Text Text Text Text</div>
<div class="fixed">Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text Text</div>

我从另一个站点找到了对您问题的一种回答:

"wrap every single character in a <span> and then loop through these <span>s, checking the offsetTop value for each.".

在你的情况下,你只关心最后一个第三个元素是否在最后一个元素之上,因为你想知道最后两个单词是否挂起。

我做了这个小片段来展示它是如何工作的:(从 208 到 209 应该会产生变化)

var paragraph = document.getElementById("paragraph");
var thirdLast = document.getElementById("third-last");
var last = document.getElementById("last");
var widthSelector = document.getElementById("width");

function handleWidth(){
  var fontSize = 16;
  paragraph.style.fontSize = fontSize + "px";
  var width = widthSelector.value;
  paragraph.style.width = width + "px";
  while(thirdLast.offsetTop < last.offsetTop && fontSize > 0){
    fontSize--;
    paragraph.style.fontSize = fontSize + "px";
  }
}
<input id="width" type="number" onchange="handleWidth()"/>

<p id="paragraph">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in 
culpa qui officia deserunt mollit 
anim <span id="third-last">id</span> 
est <span id="last">laborum.</span>
</p>

发布的两个解决方案不太符合我的需要。 (可能部分归咎于我的写作方式。无论如何我都投了赞成票。)我最终编写了自己的方法来计算 div 应该占用的行数。

我在 div 中放了一个 span。此方法对每个 y 位置的客户端 rect 的宽度求和,以获得跨度的每一行的长度(不确定为什么这些 div 放在首位,但它们是!)。然后它计算这些 rects 的数量小于 div 和 returns 宽度的一半,作为最佳行数。

howManyLinesIsBest(divEl) {
    const rect0 = (divEl.getClientRects())[0];
    const width = rect0.width;
    const span = (divEl.getElementsByTagName("span"))[0];
    const lines = {};
    [].forEach.call(span.getClientRects(),
      v => {
        if (lines.hasOwnProperty(v.y)) {
          lines[v.y] += v.width;
          return;
        }
        lines[v.y] = v.width;
      }
    );
    const wideRects = Object.values(lines).filter(v => v > width/2);
    return wideRects.length > 1 ? wideRects.length : 1;
  }

效果很好!