是否有CSS方式或非回流方式来最小化元素的宽度(不增加高度)?

时间:2018-11-27 04:09:00

标签: javascript css

下面是iMessage的照片,很抱歉,它太大了。在图像中,您将看到不同的多行消息具有不同的宽度。实际上,每个似乎都针对最小宽度进行了优化,而无需创建换行符。

enter image description here

以下是一些代码,它会非常缓慢地实现此效果。

// finds the minimum width an element can be without becoming taller
function minimizeWidth(domNode) {
  if (domNode.offsetWidth < 160) { return; }
  const squinchFurther = () => {
    const startHeight = domNode.offsetHeight;
    const startWidth = domNode.offsetWidth;
    if (startWidth === 0) {
      return;
    }

    domNode.style.width = (startWidth - 1) + "px";
    // wait for reflow before checking new size
    requestAnimationFrame(() => requestAnimationFrame(() => {
      // if the height has been increased, go back
      if (domNode.offsetHeight !== startHeight) {
        domNode.style.width = startWidth + "px";
      } else {
        squinchFurther();
      }
    }));
  }
  requestAnimationFrame(() => requestAnimationFrame(squinchFurther));
}

const divs = document.querySelectorAll("div");
for (let i = 0; i < divs.length; i++) {
  minimizeWidth(divs[i]);
}
div {
  box-sizing: border-box;
  display: inline-block;
  max-width: 160px;
  padding: 5px;
  border-radius: 5px;
  margin: 10px;
  background: #08F;
  color: white;
}
<div>Here's some multi line text</div>
<br>
<div>Word</div>
<br>
<div>Crux case a a a a a a a a</div>

是否有任何CSS可以自动执行此操作?如果没有,是否有一种方法可以在JS中计算而无需等待重排?

我记得曾经看到过一些可以用WASM编码的“回流工作器”的东西,但是我现在找不到任何东西。如果有人知道我在说什么,请分享链接。

2 个答案:

答案 0 :(得分:1)

据我所知,仅凭CSS不可能做到这一点。下面的解决方案将每个文本块保持在简单的<div> + <span>结构中,然后使用getBoundingClientRect()测量<span>的宽度并将其更新为{{1} },并具有正确的宽度。

看起来好像肯定有一个最大宽度,用于换行,也就是说,如果“ McCormick”或“ interesting”在前一行上,则宽度太长。我不相信我曾经见过超过屏幕宽度约75%的消息。我为此演示设置了最大宽度为160px。

请注意,有两个display:block循环,因此可以缓存宽度,因此我们不会连续读取然后写入DOM(并导致多次重排)。

for
function updateWidths() {
  const elems = document.querySelectorAll('.inner');
  const len = elems.length;
  const widths = [];

  // Read from the DOM
  for (let i = 0; i < len; i++) {
    widths.push(elems[i].getBoundingClientRect().width);
  }

  // Write to the DOM
  for (let i = 0; i < len; i++) {
    elems[i].style.display = 'block';
    elems[i].style.width = widths[i] + 'px';
  }
}

updateWidths();
.outer {
  margin-top: 10px;
  max-width: 160px;
}

.inner {
  background-color: blue;
  border-radius: 5px;
  color: white;
  padding: 5px;
}

答案 1 :(得分:0)

我发现了一个技巧,只能用于一次回流,但这有点麻烦。它的基本要点是:

// helper fns from npm el-tool
const div = (children: HTMLElement[]) => {
  const out = document.createElement("div");
  children.forEach((child) => out.appendChild(child));
  return out;
}
const span = (text: string) => {
  const out = document.createElement("span");
  out.innerText = text;
  return out;
}


export default function minimalWidthDiv(innerText: string) {
  // split string into words with following spaces included
  const wordEls = innerText.trim().match(/\S+\s+/g).map(span);
  const thinDiv = div(wordEls);
  // set to hidden while computing width to avoid thrashy renders
  thinDiv.style.visibility = "hidden";

  // wait for first render
  requestAnimationFrame(() => requestAnimationFrame(() =>
    const numberOfLines = magicFnThatFindsNumberOfLines();
    const currentWidth = thinDiv.offsetWidth;
    const minimalWidth = currentWidth/numberOfLines;
    let bestWidth = 0;
    // figure out the best width based of the widths of the words
    // if more than two lines, the loop below won't work is most cases
    for (let i = 0; i < wordEls.length && bestWidth < minimalWidth; i++) {
      bestWidth += wordEls[i].offsetWidth;
    }
    // update the width of the thinDiv and make it visible.
    thinEl.style.width = bestWidth + "px";
    thinEl.style.visibility = "";
  ));
  return thinDiv;
}

这里的窍门是将所有单词放入单独的跨度中,以便可以计算其宽度。从那里,无需重新换行就可以算出最小宽度。