Reputation: 124
I have the following CSS:
html {
--lh: 18px;
}
.line-10 {
--max-lines: 10;
max-width: 100%;
max-height: calc(var(--lh) * var(--max-lines));
overflow: hidden;
}
and want to count chars in a 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)
but I am getting the entire longHtml
instead of limited by CSS. Any way to solve that?
Current solution for case A (1 big paragraph of text per 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)
}
Problems with this solution:
<p>
and/ or <br />
, it will make it pretty unreliable due to different fullLineLen
for each line that does not end that the div right border.It depends on the size of the div. Input examples: 1) lorem ipsum in one paragraph, 2) lorem ipsum broken into different paragraphs. Expected output - visible char count.
Upvotes: 1
Views: 283
Reputation: 36467
If we copy characters from the string gradually into a div that is offscreen with the same max-height (+1px) as div, intersectionObserver will let us know when it just enters the top of the screen. This will be when the first characters from the line after the last visible line are copied.
We have to let the system have a space in which it can tell us of such an observation so the copying has to be done in bits. This snippet copies a word at a time so on most systems it probably can count at a maximum of 60 words per second.
There are some uncertainties - what do we mean by a character being visible and what does the system mean? On a system in which there are accurately 10 lines in the visible portion and without scrolling the counting is accurate (concurs with e.g. Word or Notepad++).
On scrolling it is quite possible that just part of the characters in the top line and in the bottom line are shown (sometimes not really perceptibly), so what the system and what a human thinks is visible and should be counted may differ. It is not clear from the question what the expected result should be. This snippet does its best in the scrolled situation by counting characters that have been scrolled out at the top to adjust the overall count.
Tested on Edge/Chrome on Windows 10 and Safari on IOS 14.4. Note that on Chrome's dev tools emulators it does not work well (counts a few too many characters) as the emulation of intersectionObserver seems to be a bit laggy.
<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>
Upvotes: 2