I’m working on a text truncation feature in plain JavaScript that’s supposed to shorten the text within HTML elements while preserving the structure. For example, given an HTML element with data-limit="10", it should truncate the content to 10 words and append a "Read more" link to expand the full text.
However, the script isn’t truncating as expected. Instead, it’s cutting off too early or immediately after the headers. Here’s a simplified version of my code and the HTML I’m testing with:
document.addEventListener("DOMContentLoaded", function() {
function shortenText(element, limit) {
const originalContent = element.innerHTML; // Save the original content
const nodes = Array.from(element.childNodes); // All direct child nodes
let wordCount = 0;
let truncatedContent = '';
// Helper function for word counting and truncation
function traverseNodes(node) {
if (wordCount >= limit) {
return;
}
if (node.nodeType === Node.TEXT_NODE) {
const words = node.nodeValue.split(/s+/);
for (let i = 0; i < words.length; i++) {
if (wordCount < limit) {
truncatedContent += words[i] + ' ';
wordCount++;
} else {
truncatedContent += '... ';
return;
}
}
} else if (node.nodeType === Node.ELEMENT_NODE) {
if (node.nodeName.toLowerCase() === 'span' && node.classList.contains('more')) {
return;
}
truncatedContent += `<${node.nodeName.toLowerCase()}`;
Array.from(node.attributes).forEach(attr => {
truncatedContent += ` ${attr.name}="${attr.value}"`;
});
truncatedContent += '>';
node.childNodes.forEach(childNode => traverseNodes(childNode));
truncatedContent += `</${node.nodeName.toLowerCase()}>`;
}
}
for (let childNode of nodes) {
if (wordCount < limit) {
traverseNodes(childNode);
}
}
element.innerHTML = truncatedContent.trim() + `<span class="more">Read more</span>`;
element.dataset.originalContent = originalContent; // Save the original content
}
function expandText(element) {
element.innerHTML = element.dataset.originalContent;
}
document.querySelectorAll('.shorten-text').forEach(el => {
const limit = parseInt(el.dataset.limit) || 100; // Default limit to 100 words
shortenText(el, limit);
el.addEventListener('click', function(event) {
if (event.target.classList.contains('more')) {
expandText(el);
}
});
});
});
* {
font-family: Calibri, Arial;
margin: 0px;
padding: 25px;
text-align: center;
}
.shorten-text {
margin: 20px 0;
padding: 10px;
border: 1px solid #ccc;
background-color: #f9f9f9;
}
.more {
color: blue;
cursor: pointer;
text-decoration: underline;
}
<h1>Text Shortening Test</h1>
<div class="shorten-text" data-limit="10">
<h1>A Headline!</h1>
<div>A wonderful serenity has taken possession of my entire soul, <strong>like these sweet mornings</strong> of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like
mine.</div>
<p>I am so happy, my dear friend, so absorbed in the <a href="#">exquisite</a> sense of mere tranquil existence, that I neglect my talents.</p>
</div>
<div class="shorten-text" data-limit="5">
<h2>Another Header</h2>
<div>This is a test paragraph to check the shortening function. It should only show a few words.</div>
<p>This paragraph should be mostly hidden.</p>
</div>
<div class="shorten-text" data-limit="15">
<h3>Yet Another Header</h3>
<div>Testing the function with a longer limit to see how it handles more text. The goal is to ensure that the text is truncated properly without breaking the structure of the HTML content.</div>
</div>
What I expect:
- For the text within the .shorten-text elements to be truncated after
the specified number of words (excluding any HTML tags or child
elements). - A "Read more" link to appear, which when clicked, expands
to show the full text. - The truncation should not occur within tags
like<h1>
,<b>
,<strong>
,<a>
, etc. It should always cut text outside
these elements, ensuring the integrity of HTML tags.
What’s happening:
- Text is sometimes cut off too early or immediately after headers.
Any insights on why the script isn’t working as intended and how to ensure it accurately truncates the text based on the word count would be greatly appreciated!
Thanks!
2
Answers
In your for(…i < words.length…) loop, you are counting empty strings created with your node.nodeValue.split(/s+/)
Just change the if() in that loop to check for empty strings.
i.e. change:
to:
Run the code snippet to see it working.
As you are using NODE_TYPE for checking the type of node, but
h1
is also considered as a text node thus its words are also counted in the truncated string. So you can explore on some other way to distinguish between these nodes, or you can apply the shortenText logic on the div that contain the actual text.Also before checking for words length you can filter those by checking is there are some empty string or not in the words array, as you are using regex for removing specific strings thus there are some occurences of empty string in the words array.