代码之家  ›  专栏  ›  技术社区  ›  Seph Reed

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

  •  0
  • Seph Reed  · 技术社区  · 7 年前

    下面是一张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 回复  |  直到 7 年前
        1
  •  1
  •   skyline3000    7 年前

    据我所知,单靠CSS是不可能做到这一点的。下面的解决方案将每个文本块保存在一个简单的 <div> + <span> 结构,然后使用 getBoundingClientRect() 测量 <跨度> 的宽度,并将其更新为 display:block 宽度正确。

    看起来肯定有一个最大宽度来解释换行,也就是说,如果“McCormick”或“interest”在前一行,那么宽度会太长。我不相信我见过超过屏幕宽度75%的信息。我为这个演示设置了最大宽度160px。

    注意有两个 for 循环以便可以缓存宽度,这样我们就不会连续地读写DOM(并导致多次回流)。

    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;
    }
    <div class="outer">
      <span class="inner">Yeah, this week at McCormick place apparently</span>
    </div>
    
    <div class="outer">
      <span class="inner">Negative, seems interesting tho</span>
    </div>
    
    <div class="outer">
      <span class="inner">Some other random message which is a little bit longer than the other messages</span>
    </div>
        2
  •  0
  •   Seph Reed    7 年前

    我发现了一个只会导致一次回流的技巧,但这会有点糟糕。其基本要点是:

    // 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;
    }
    

    这里的诀窍是把所有单词放在不同的跨距中,这样就可以计算出它们的宽度。从那里,不需要新行就可以算出最小宽度。