首页 > 解决方案 > 如何使用 JS 计算 CSS 受限 div 中的字符数

问题描述

我有以下CSS:

html {
  --lh: 18px;
}

.line-10 {
  --max-lines: 10;

  max-width: 100%;
  max-height: calc(var(--lh) * var(--max-lines));
  overflow: hidden;
}

并想计算 div 中的字符:

const div = document.createElement('div')
div.classList.add('line-10')
div.innerHTML = longHtml
const _createdHTML = div.innerHTML
const _createdTxt = _createdHTML.replace(/<[^>]*>/g, '')
console.log('_createdTxt', _createdTxt.length)

但我得到的是整个longHtml而不是受 CSS 的限制。有什么办法解决吗?

案例 A 的当前解决方案(每个 div 一大段文本):

const maxLines = 10
const fullTxtLen = html.replace(/<[^>]*>/g, '').length
const fullLineLen = 150 // or whatever fits into initial div

if (fullTxtLen / fullLineLen > maxLines) {
  const totalLines = fullTxtLen / fullLineLen
  const reduceIn = totalLines / maxLines
  const final = fixHtml(html.substring(0, parseInt(html.length / reduceIn)))

  return {
    html: final,
    len: final.replace(/<[^>]*>/g, '').length // i.e. this is the answer on visible char count
  }
}

function fixHtml (html) {
  const div = document.createElement('div')
  div.innerHTML = html
  return (div.innerHTML)
}

此解决方案的问题:

  1. 与引入 more <p>and/or相比<br />,它会变得非常不可靠,因为fullLineLen每条不结束 div 右边界的行都不同。
  2. 减少的字符长度是文本字符,而不是 html 字符

常问问题

您需要保留多少个字符,输入示例和预期输出

这取决于 div 的大小。输入示例:1) lorem ipsum 在一个段落中,2) lorem ipsum 分成不同的段落。预期输出 - 可见字符数。

标签: javascriptcss

解决方案


如果我们将字符串中的字符逐渐复制到与 div 具有相同最大高度 (+1px) 的屏幕外 div 中,intersectionObserver 会在它刚刚进入屏幕顶部时通知我们。这将是在最后一个可见行之后的行中的第一个字符被复制的时候。

我们必须让系统有一个空间,它可以告诉我们这样的观察结果,所以复制必须按位完成。该片段一次复制一个单词,因此在大多数系统上,它可能每秒最多可以计算 60 个单词。

有一些不确定性——我们所说的角色可见是什么意思,系统是什么意思?在可见部分准确有 10 行且不滚动计数的系统上是准确的(与 Word 或 Notepad++ 等一致)。

在滚动时,很可能只显示顶行和底行中的部分字符(有时无法真正感知),因此系统和人类认为可见和应该计算的内容可能会有所不同。从问题中不清楚预期的结果应该是什么。此代码段通过计算顶部已滚动的字符以调整总计数来在滚动情况下发挥最大作用。

在 Windows 10 上的 Edge/Chrome 和 IOS 14.4 上的 Safari 上进行了测试。请注意,在 Chrome 的开发工具模拟器上,它不能很好地工作(计数太多字符),因为 intersectionObserver 的模拟似乎有点滞后。

<head>
<style>

html {
  --lh: 18px;
}

* {
  margin: 0;
}

.line-10, .workSpaceDiv {
  margin: 0 20px;
  --max-lines: 10;
  max-width: 100%;
  max-height: calc(var(--lh) * var(--max-lines));
  overflow-y: scroll;
}

.workSpaceDiv {
  position: absolute;
  --maxh:  calc(var(--lh) * (var(--max-lines) + 1));
  max-height: var(--maxh);
  transform: translateY(calc(2px - var(--maxh)));
  height: auto;
  display: none;
}

.clickme, .info {
  padding: 10px;
}

.infoblock {
  padding: 20px;
}

</style>
</head>
<body>
  <div class="workSpaceDiv"></div>
  <div class="infoblock">
    <button class="clickme" onclick="countChs(div);">Click me to count the visible characters</button>
    <span class="info"> Number of visible characters: </span><span class="count"></span>
  </div>

<script>
  const div = document.createElement('div');
  div.classList.add('line-10');
  document.body.appendChild(div);

  const longHtml = `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Mi in nulla posuere sollicitudin. Sed nisi lacus sed viverra tellus in. Sit amet consectetur adipiscing elit pellentesque habitant morbi tristique. Enim lobortis scelerisque fermentum dui faucibus. Varius duis at consectetur lorem donec massa sapien faucibus et. Libero enim sed faucibus turpis in eu mi bibendum. Eleifend donec pretium vulputate sapien nec sagittis aliquam malesuada bibendum. Nulla aliquet enim tortor at auctor. Mattis pellentesque id nibh tortor. Ullamcorper sit amet risus nullam eget felis. In ornare quam viverra orci sagittis eu. Pellentesque habitant morbi tristique senectus. Amet cursus sit amet dictum sit amet justo. Sit amet nulla facilisi morbi tempus. Dolor sit amet consectetur adipiscing elit duis tristique sollicitudin nibh. Facilisi morbi tempus iaculis urna id volutpat lacus laoreet. Mi in nulla posuere sollicitudin aliquam ultrices. Diam quis enim lobortis scelerisque fermentum dui faucibus in ornare. Sed augue lacus viverra vitae congue eu. Et malesuada fames ac turpis egestas integer eget aliquet nibh. Ipsum faucibus vitae aliquet nec ullamcorper. Felis bibendum ut tristique et egestas quis ipsum suspendisse ultrices. Ac felis donec et odio pellentesque diam volutpat commodo. Mauris a diam maecenas sed. Facilisis sed odio morbi quis commodo odio aenean sed. Lorem mollis aliquam ut porttitor leo a. Vivamus at augue eget arcu dictum varius duis. Nisi porta lorem mollis aliquam ut. Habitant morbi tristique senectus et netus et malesuada fames ac. Tempus iaculis urna id volutpat lacus laoreet non curabitur. Sed risus pretium quam vulputate dignissim suspendisse in. Malesuada fames ac turpis egestas maecenas pharetra. Malesuada fames ac turpis egestas maecenas. Urna nunc id cursus metus aliquam eleifend mi in. Convallis posuere morbi leo urna molestie at elementum. Facilisis leo vel fringilla est ullamcorper eget nulla facilisi. Nulla pharetra diam sit amet nisl suscipit. Posuere morbi leo urna molestie at. Risus pretium quam vulputate dignissim. Arcu dui vivamus arcu felis bibendum ut tristique et. Varius duis at consectetur lorem donec massa sapien faucibus et. Id eu nisl nunc mi ipsum faucibus vitae aliquet. Congue mauris rhoncus aenean vel elit scelerisque mauris. Nulla at volutpat diam ut venenatis tellus in. Tellus cras adipiscing enim eu turpis egestas pretium aenean pharetra. Leo integer malesuada nunc vel risus. Tortor at auctor urna nunc id cursus metus aliquam eleifend. Felis bibendum ut tristique et egestas quis ipsum. A condimentum vitae sapien pellentesque habitant morbi. Purus non enim praesent elementum facilisis leo vel fringilla. Sagittis purus sit amet volutpat consequat mauris nunc. Sed tempus urna et pharetra pharetra massa massa. Vitae proin sagittis nisl rhoncus mattis rhoncus. Non curabitur gravida arcu ac tortor dignissim convallis. Dolor sit amet consectetur adipiscing elit. Dignissim enim sit amet venenatis urna cursus. Neque ornare aenean euismod elementum nisi quis eleifend quam. Tortor at auctor urna nunc id cursus metus aliquam eleifend. Curabitur gravida arcu ac tortor dignissim convallis aenean. Neque viverra justo nec ultrices dui sapien eget mi.`;

  const createdTxt = longHtml.replace(/<[^>]*>/g, '');
  div.innerHTML = createdTxt;

  const workSpaceDiv = document.querySelector('.workSpaceDiv');
  const info = document.querySelector('.info');
  const count = document.querySelector('.count');

  let nextCh, lastWordSize, stopCount, scrolledChs, firstTime, scrolled, allChs;

  function countChsIn() {
    stopCount = false;
    nextCh = 0;
    lastWordSize = 0;

    observer.observe(workSpaceDiv);
    requestAnimationFrame(nextChFill);

    function nextChFill() {
      if (stopCount) return;
      lastWordSize = 0;
      let i, ch;
      for (i = 0; i < 10; i++) {
        ch = allChs.charAt(nextCh);
        workSpaceDiv.innerHTML = workSpaceDiv.innerHTML + ch;
        nextCh++;
        lastWordSize++;
        if ((!((/[a-zA-Z]/).test(ch))) || (nextCh >= allChs.length)) { break; }
      }

      if (nextCh < allChs.length) requestAnimationFrame(nextChFill);
      else { finish(); }

    }
  }

  function finish() {
    observer.disconnect(workSpaceDiv);
    workSpaceDiv.style.height = 'auto';
    workSpaceDiv.innerHTML = '';
    if (firstTime) {
      firstTime = false;
      stopCount = false;
      scrolledChs = nextCh - lastWordSize -1;
      workSpaceDiv.style.transform = 'translateY(calc(1px - (var(--maxh) + ' + scrolled + 'px' + ')))';
      workSpaceDiv.style.maxHeight = 'calc(var(--maxh) + ' + scrolled + 'px' + ')';
      requestAnimationFrame(countChsIn);
    }
    else {
      workSpaceDiv.style.transform = 'translateY(calc(2px - var(--maxh)))';
      workSpaceDiv.style.display = 'none';
      count.innerHTML = nextCh - lastWordSize - 1 - scrolledChs;
      stopCount = true;
    }
  }

  function overlap(entries) {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        finish();
      }
    });
  }

  let observer = new IntersectionObserver(overlap);
  
  function countChs(el) {
    allChs = el.innerHTML;
    scrolled = el.scrollTop;
    firstTime = !(scrolled <= 0);

    count.innerHTML = '...counting...';

    workSpaceDiv.style.display = 'block';
    workSpaceDiv.style.innerHTML = '';
    workSpaceDiv.style.height = 'auto';

    if (firstTime) {
      workSpaceDiv.style.transform = 'translateY(calc(2px - ' + scrolled + 'px' + '))';
      workSpaceDiv.style.maxHeight = scrolled + 'px';
    }
    else {
      workSpaceDiv.style.transform = 'translateY(calc(2px - var(--maxh)))';
      workSpaceDiv.style.maxHeight = 'var(--maxh)';
    }
    scrolledChs = 0;
    requestAnimationFrame(countChsIn);
  }
</script>
</body>


推荐阅读