I might have developed a good solution for you! I am not exactly sure what kind of "limit" you desire and the newsletter link you originally posted is now dead, but I'll try my best!
A lot of accepted solutions out there seem to be using CSS with the text-overflow
property and width
using character-units (ch
).
This is a good solution but I have seen others wanting a JavaScript answer to this for direct programmatic control.
Perhaps you'll find my solution easier to work with than a bunch of CSS properties and rules!
Anywho, just to let you know, my solution stems from Josh's answer in this post:
How to limit the length of text in a paragraph.
I thought Josh was onto something with their idea to use JS and decided to take it further.
If we think about the problem at hand here, generally the idea is to limit the number of characters on each "line" in the
paragraph's text string. This would essentially prevent your text from overflowing or going out of bounds.
But as we may know, paragraph text strings themselves are just one long line of text so we have to define what a "line"
is for the problem's solution. What better way to define a "line" than with a "newline" character! (Ok, that was a bit cheesy).
But you get the point. We'll stop the amount of characters on each line in the paragraph's text using newlines or '\n'.
For the implementation, I thought this would be ugly to code iteratively and I was just "feeling" the recursive route.
If you're unfamiliar with recursion, all I am doing is making the function or method call itself and smash down the
call stack until it reaches my defined base case, the end of the road to say that we have solved it!
Here's version 1, where it simply appends a newline character if the current substring is longer than our max char limit
length; else, it just goes to the end and eventually the length, or one past the last valid char index, is reached.
/*=======================*/
/*Made by: Brendan Sting */
/*=======================*/
/* ---Limit text inside a <p> tag with vanilla JavaScript, version 1; bad wrapping/cutoff--- */
function truncateText(selector, maxLength, startAt) {
var element = document.querySelector(selector);
var truncated = element.innerText;
// BASE CASE:
if (startAt >= truncated.length)
{
return "";
}
// If the currently looked at substring is longer than the limit, cut it:
if (truncated.substring(startAt).length > maxLength) {
truncated = truncated.substring(startAt, (startAt+maxLength)) + '\n';
}
// Else, just write it out with no newline needed:
else
{
truncated = truncated.substring(startAt);
}
// Recursive return:
return truncated + truncateText(selector, maxLength, (startAt + maxLength));
}
document.querySelector('p').innerText = truncateText('p', 30, 0);
/* --- END OF CODE SNIPPET V1 --- */
<!-- TEST COVERAGE -->
<!--Test case 1: PASS -->
<p>Breaking India: Western Interventions in Dravidian and Dalit Faultlines is a book written by Rajiv Malhotra and Aravindan Neelakandan which argues that India's integrity is being undermined.</p>
<!-- Test case 2: PASS -->
<!--<p> Breaking India: Western Interventions in Dravidian and Dalit Faultlines is a book written by Rajiv Malhotra and Aravindan Neelakandan which argues that India's integrity is being undermined.</p> -->
<!-- Test case 3: PASS -->
<!--<p> Breaking India: Western Interventions in Dravidian and Dalit Faultlines is a book written by Rajiv Malhotra and Aravindan Neelakandan which argues that India's integrity is being undermined.<br><br> Now, what if I started typing down here, huh? Can you handle this text as well?</p> -->
<!--Test case 4: PASS -->
<!--<p>aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.</p>-->
<!-- Test case 5: PASS -->
<!--<p></p>-->
<!-- Test case 6: PASS -->
<!--<p>12345678910 111213141516170000000000000000000000 1819202122</p> -->
<!-- END OF TESTS -->
This is...well, it does what we told it strictly to do. It just cuts off the chars past a limit we define in its query call.
But it can be improved! I don't know about you, but I hate that my words get split up across the different lines.
Instead, a better solution should take from like what Microsoft Word does where each WORD is moved to a newline if the line
exceeds the character limit set on the page. This keeps the word together and keeps our lines at our desired limit!
Alrighty, take two! Let's expand our problem that if the last word on the line is cutoff past the limit, then its whole word should be
moved to the next line for a full-print.
At a glance this seems simple enough, but my other 7 versions of my original version 1 JS code say otherwise. We need to not only accomplish
preserving words while respecting the char limit, but we also need to account for edge-cases such as a really long word that itself
exceeds the limit. To accomplish preserving words while respecting the char limit on each line, we can search for the nearest blank space found
earlier in the paragraph's string and move our ending index, where we'll append the newline character, to that blank space which essentially
puts the word right after the newline! If there's no blank spaces available to resort to, well, we'll just go ahead and cut words normally like
in version 1; but that should never happen as paragraphs are bound to have spaces in them! Furthermore, we can actually use this principle to
handle the edge-case example I mentioned earlier with the really long word via using some helper functions' returns.
Whew, okay, that was a LOT! But I just want to make sure you understand the design of the solution I made.
Now, let's see that final implementation:
/*=======================*/
/*Made by: Brendan Sting */
/*=======================*/
/* --- Limit text inside a <p> tag with vanilla JavaScript; version 8 --- */
/* === LATEST WORKING VERSION === */
// Tries to find the last available blank space to resort to:
function findLastBlankSpaceStartingFrom(inString, startIndex)
{
for (let loopIndex = startIndex; loopIndex > -1; loopIndex--)
{
if (inString[loopIndex] == " ")
{
return loopIndex;
}
}
return -1; // Darn, we couldn't find it; handle it
}
// Frees any chars that have been disconnected from the current substring line by past newline characters (also from <br> tags)
function freeUpAnyNewlineSeparatedCharSpace(inString, upUntilThisPoint, startingFromHere) //upUntilThisPoint = startAt index, startingFromHere = lastCharIndex
{
var extraAvailableChars = 0;
let startCountingSwitchEnabled = false;
if (startingFromHere > upUntilThisPoint)
{
for (let loopIndex = startingFromHere; loopIndex >= upUntilThisPoint; loopIndex--)
{
if (inString[loopIndex] == '\n')
{
startCountingSwitchEnabled = true;
}
if (startCountingSwitchEnabled)
{
extraAvailableChars++;
}
}
}
else
{
for (let loopIndex = startingFromHere; loopIndex <= upUntilThisPoint; loopIndex++)
{
if (inString[loopIndex] == '\n')
{
startCountingSwitchEnabled = true;
}
if (startCountingSwitchEnabled)
{
extraAvailableChars++;
}
}
}
return extraAvailableChars;
}
function getNewEndpointBeforeLineOverflow(ofThisString, atLastCharIndex, startOfLine)
{
var finalCharIndex = atLastCharIndex;
// If last char index on the line is below the last char of the entire string, then:
if (atLastCharIndex < (ofThisString.length - 1))
{
// If there's a non-blank, non-space character past the last char to limit to, then:
if ((ofThisString[atLastCharIndex] != ' ') && (ofThisString[atLastCharIndex + 1] != ' '))
{
// Try and find a previous blank space to resort to and end on:
var prevBlankIndex = findLastBlankSpaceStartingFrom(ofThisString, (atLastCharIndex));
if ((prevBlankIndex != -1) && (prevBlankIndex >= startOfLine)) // Did we find a valid blank space within this portion of the line?
{
finalCharIndex = prevBlankIndex; //Index of the blank space now
}
}
}
else
{
// If the initial overflow index point, the last char point, is past or at the string's final valid char index,
// then we have no choice but to reposition it at the text's final char index:
finalCharIndex = (ofThisString.length - 1);
}
return finalCharIndex;
}
function truncateText(selector, maxLength, startAt) {
var element = document.querySelector(selector);
var truncated = element.innerText;
let lastCharIndex = (startAt + maxLength) - 1;
// BASE CASE:
if (startAt >= truncated.length)
{
return "";
}
// If the currently looked at substring is longer than the limit, cut it:
if (truncated.substring(startAt).length > maxLength)
{
lastCharIndex = getNewEndpointBeforeLineOverflow(truncated, lastCharIndex, startAt);
var extraLineSpace = freeUpAnyNewlineSeparatedCharSpace(truncated, startAt, lastCharIndex);
if (extraLineSpace > 0)
{
lastCharIndex = lastCharIndex + extraLineSpace;
lastCharIndex = getNewEndpointBeforeLineOverflow(truncated, lastCharIndex, startAt)
}
truncated = truncated.substring(startAt, (lastCharIndex+1)); //Stop at either maxLength or blankSpace+1
// Allow the front-end programmer to input tabs or spaces at the front with   or (etc.), but if it's a raw space then cut it out:
// (This conditional structure has been reduced thanks to the power of .innerText)
if (truncated[startAt] == ' ')
{
truncated = truncated.trim() + '\n';
}
else
{
truncated = truncated.trimEnd() + '\n';
}
}
// Else, just write it out with no newline needed:
else
{
truncated = truncated.substring(startAt);
}
// Recursive return:
return truncated + truncateText(selector, maxLength, (lastCharIndex+1) );
}
document.querySelector('p').innerText = truncateText('p', 30, 0);
/* --- END OF CODE SNIPPET V8 --- */
<!-- TEST COVERAGE -->
<!--Test case 1: PASS -->
<p>Breaking India: Western Interventions in Dravidian and Dalit Faultlines is a book written by Rajiv Malhotra and Aravindan Neelakandan which argues that India's integrity is being undermined.</p>
<!-- Test case 2: PASS -->
<!--<p> Breaking India: Western Interventions in Dravidian and Dalit Faultlines is a book written by Rajiv Malhotra and Aravindan Neelakandan which argues that India's integrity is being undermined.</p> -->
<!-- Test case 3: PASS -->
<!--<p> Breaking India: Western Interventions in Dravidian and Dalit Faultlines is a book written by Rajiv Malhotra and Aravindan Neelakandan which argues that India's integrity is being undermined.<br><br> Now, what if I started typing down here, huh? Can you handle this text as well?</p> -->
<!--Test case 4: PASS -->
<!--<p>aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.</p>-->
<!-- Test case 5: PASS -->
<!--<p></p>-->
<!-- Test case 6: PASS -->
<!--<p>12345678910 111213141516170000000000000000000000 1819202122</p> -->
<!-- END OF TESTS -->
Hopefully my code and solution helps you out in some way, if at the very least gives you an idea about how to solve it using JavaScript.