JS,Vue:如何根据窗口高度分割文本?

时间:2018-02-16 15:33:23

标签: javascript vue.js

我有一些文字,我想在特定点分割,这样你就不需要向下滚动了。

这意味着我想知道文本何时变得比窗口的可用高度更长。在显示文本之前需要知道这一点。问题在于我需要知道在渲染之前我的布局会是什么样子,因为整个事物应该响应宽度和高度。

我还打算稍微调整字体大小。所以考虑到所有这一点,你们中的任何人都知道你知道如何在正确的点分割文本吗?

提前谢谢。

PS:文本实际上是一个数组,例如:

text = [{content: Hello, wordID: ..., offsetBegin: 0, offsetEnd: 5,
         ...},{content: World!, wordID: ..., offsetBeding: 7, offsetEnd: 12,...}]

所以我唯一需要知道的是索引在哪里拆分文本,以便主窗口上没有滚动条。拆分文本也可以多次出现。

整个事情将开始在mounted()钩子中显示和计算,并且每次触发窗口的'resize'事件时都会重新计算。

1 个答案:

答案 0 :(得分:4)

这个问题似乎有XY problem。您可能想重新考虑这是否真的是您想要解决问题的方式。

但是,如果你真的想在JS中获得截止点:

文本的高度取决于多种因素,例如元素的宽度,字体大小和重量,字距,等等。需要考虑很多变量,如果不进行任何渲染,就不可能进行计算。

相反,您应该要求浏览器呈现您的元素,然后在向用户显示呈现之前将其删除。一旦您插入文本,测量文本应该结束的位置,然后在删除文本后强制重新进行重排,这将由forcing a reflow完成。

棘手的部分是测量文本应该结束的位置。我个人会通过在每个我想要截止的位置插入元素来解决这个问题(例如在每个单词之后),然后循环遍历它们以查看哪个溢出容器。

以下是该想法的香草JS实现。您应该能够在Vue中轻松实现它。



const DEBUG = true;

const $textInp = document.getElementById("text-inp");
const $target = document.getElementById("target");
const $outp = document.getElementById("outp");

document.getElementById("calc-btn").addEventListener("click", () => {
    const text = $textInp.value;
    const data = getTextCutoff(text, $target);
    $outp.textContent = JSON.stringify(data);
    if (!DEBUG) { $target.textContent = text.substr(0, data.end); }
});

/**
 * Returns an object of format { end: <Number> }
 * Where `end` is the last index which can be displayed inside $elm without overflow
 */
function getTextCutoff(text, $elm) {
    $elm.innerHTML = ""; // empty the element
    const prevOverflow = $elm.style.overflow;
    const prevPosition = $elm.style.position;
    $elm.style.overflow = "visible";
    $elm.style.position = "relative"; // to make sure offsetHeight gives relative to parent
    const $indicators = [];
    const $nodes = [];
    // turn our text into an array of text nodes, with an indicator node after each
    let currentIndex = 0;
    const words = text.split(" ");
    words.forEach(
        (word, i) => {
            if (i > 0) {
                word = " "+word;
            }
            currentIndex += word.length;
            const $wordNode = document.createTextNode(word);
            $nodes.push($wordNode);
            const $indicatorNode = document.createElement("span");
            $indicatorNode.strIndex = currentIndex;
            if (DEBUG) { $indicatorNode.classList.add("text-cutoff-indicator"); }
            $indicators.push($indicatorNode);
            $nodes.push($indicatorNode);
        }
    );
  
    // insert our elements
    $nodes.forEach($node => $elm.appendChild($node));
  
    // then find the first indicator that is overflown
    const maxHeight = $elm.offsetHeight;
    let $lastIndicator = $indicators[$indicators.length - 1];
    for (let i = 0; i < $indicators.length; ++i) {
  	    const $indicator = $indicators[i];
        const bottomPos = $indicator.offsetTop + $indicator.offsetHeight;
        if (bottomPos > maxHeight) {
            break;
        } else { $lastIndicator = $indicator; }
    }
  
    if (DEBUG) {
        $lastIndicator.style.borderColor = "green";
        $lastIndicator.classList.add("overflown");
    }
  
    // then reset everything - this also forces reflow
    if (!DEBUG) { $elm.innerHTML = ""; }
    $elm.style.overflow = prevOverflow;
    $elm.style.position = prevPosition;
  
    // then return
    return {
        end: $lastIndicator.strIndex
    };
}
&#13;
#target {
  height: 128px;
  background: #ddd;
}

.text-cutoff-indicator {
  margin-left: -2px;
  border-left: 2px solid red;
}

.text-cutoff-indicator.overflown ~ .text-cutoff-indicator {
  opacity: 0.5;
}
&#13;
<div>
  <textarea id="text-inp" placeholder="Enter text here"></textarea>
  <button id="calc-btn">Apply text</button>
  <pre id="outp"></pre>
</div>
<div id="target"></div>
&#13;
&#13;
&#13;