Ellipsis in the middle of a text (Mac style)

JavascriptJqueryCssTruncation

Javascript Problem Overview


I need to implement ellipsis ("...") in the middle of a text within a resizable element. Here is what it might look like. So,

"Lorem ipsum dolor sit amet. Ut ornare dignissim ligula sed commodo."

becomes

"Lorem ipsum dolor sit amet ... commodo."

When the element is stretched out to the width of the text, I want the ellipsis to disappear. How can this be done?

Javascript Solutions


Solution 1 - Javascript

In the HTML, put the full value in a custom data-* attribute like

<span data-original="your string here"></span>

Then assign load and resize event listeners to a JavaScript function which will read the original data attribute and place it in the innerHTML of your span tag. Here is an example of the ellipsis function:

function start_and_end(str) {
  if (str.length > 35) {
    return str.substr(0, 20) + '...' + str.substr(str.length-10, str.length);
  }
  return str;
}

Adjust the values, or if possible, make them dynamic, if necessary for different objects. If you have users from different browsers, you can steal a reference width from a text by the same font and size elsewhere in your dom. Then interpolate to an appropriate amount of characters to use.

A tip is also to have an abbr-tag on the ... or who message to make the user be able to get a tooltip with the full string.

<abbr title="simple tool tip">something</abbr>

Solution 2 - Javascript

I'd like to propose mine example of solving this problem.

The main idea is to split text in two even parts (or first is bigger, if the length is odd) one of which has ellipsis in the end and another aligned right with text-overflow: clip.

So all you need to do with js, if you want to make it automatic/universal - is to split string and set attributes.

It has some disadvantages, though.

  1. No nice wrapping by words, or even letters (text-overflow: '' works only in FF at the moment)
  2. If the split happens between words - space should be in the first part. Otherwise, it will be collapsed.
  3. End of the string should not have any exclamation marks, due to direction: rtl - they will be moved to the left of the string. I think, it is possible to fix this with putting right part of the word in the tag and exclamation mark in the ::after pseudo-element. But I haven't yet made it properly working.

But, with all of these, it looks really cool to me, especially when you dragging the border of the browser, which you can do on the jsfiddle page easily: https://jsfiddle.net/extempl/93ymy3oL/. Or just run the snippet with fixed max-width below.

Gif under the spoiler: >! Gif

body {
  max-width: 400px;
}

span::before, span::after {
  display: inline-block;
  max-width: 50%;
  overflow: hidden;
  white-space: pre;
}

span::before {
  content: attr(data-content-start);
  text-overflow: ellipsis;
}

span::after {
  content: attr(data-content-end);
  text-overflow: '';
  direction: rtl;
}

<span data-content-start="Look deep into nature, and then you " 
      data-content-end=  "will understand everything better"></span>

<br>
<span data-content-start="https://www.google.com.ua/images/branding/g" 
      data-content-end=  "ooglelogo/2x/googlelogo_color_272x92dp.png"></span>

Solution 3 - Javascript

So my colleague came up with a solution that uses no extra dom elements. We check to see if the div overflows and add a data attribute of the last n characters. The rest is done in css.

Here is some HTML:

<div class="box">
    <div class="ellipsis" data-tail="some">This is my text it is awesome</div>
</div>
<div class="box">
    <div class="ellipsis">This is my text</div>
</div>

And the css:

.box {
    width: 200px;
}

.ellipsis:before {
    float: right;
    content: attr(data-tail);
}

.ellipsis {
    white-space: nowrap;
    text-overflow: ellipsis;
    overflow: hidden;
}

Here is the obligatory jsfiddle for this: http://jsfiddle.net/r96vB/1/

Solution 4 - Javascript

The following Javascript function will do a middle truncation, like OS X:

function smartTrim(string, maxLength) {
    if (!string) return string;
    if (maxLength < 1) return string;
    if (string.length <= maxLength) return string;
    if (maxLength == 1) return string.substring(0,1) + '...';

    var midpoint = Math.ceil(string.length / 2);
    var toremove = string.length - maxLength;
    var lstrip = Math.ceil(toremove/2);
    var rstrip = toremove - lstrip;
    return string.substring(0, midpoint-lstrip) + '...' 
    + string.substring(midpoint+rstrip);
}       

It will replace characters in the middle with ellipsis. My unit tests show:

var s = '1234567890';
assertEquals(smartTrim(s, -1), '1234567890');
assertEquals(smartTrim(s, 0), '1234567890');
assertEquals(smartTrim(s, 1), '1...');
assertEquals(smartTrim(s, 2), '1...0');
assertEquals(smartTrim(s, 3), '1...90');
assertEquals(smartTrim(s, 4), '12...90');
assertEquals(smartTrim(s, 5), '12...890');
assertEquals(smartTrim(s, 6), '123...890');
assertEquals(smartTrim(s, 7), '123...7890');
assertEquals(smartTrim(s, 8), '1234...7890');
assertEquals(smartTrim(s, 9), '1234...67890');
assertEquals(smartTrim(s, 10), '1234567890');
assertEquals(smartTrim(s, 11), '1234567890');

Solution 5 - Javascript

This may be a bit late in the game, but I was looking to find a solution to this, and a colleague suggested a very elegant one, which I'll share. It requires some JS, but not a lot.

Imagine you have a div of a size you need to put your label into:

<div style="width: 200px; overflow: hidden"></div>

Now, you have a function which will take two params: a string with the label, and a DOM element (this div) to fit it into:

function setEllipsisLabel(div, label) 

The first thing you do is create a span with this label, and put it into the div:

var span = document.createElement('span');
span.appendChild(document.createTextNode(label));
span.style.textOverflow = 'ellipsis';
span.style.display = 'inline-block';
div.appendChild(span);

We set the text-overflow property to "ellipsis" so that as the text gets chopped off, a nice "..." is added at the end to illustrate this. We also set display to be "inline-block" so that these elements have real pixel dimensions we can manipulate later. So far, nothing we could not have done with pure CSS.

But we want the ellipsis in the middle. First, we should find out if we need it at all... This can be done by comparing div.clientWidth to span.clientWidth - ellipsis is only needed if the span is wider than the div.

If we do need an ellipsis, let's start by saying that we want a fixed number of characters shown at the end of the word - say 10. So let's create a span containing only the last 10 characters of the label, and stick it into the div:

var endSpan = document.createElement('span');
endSpan.style.display = 'inline-block';
endspan.appendChild(document.createTextNode(label.substring(label.length - 10)));
div.appendChild(endSpan);

Now, let's override the width of the original span to accommodate the new one:

span.style.width = (div.clientWidth - endSpan.clientWidth) + 'px';

As a result of this, we now have a DOM structure that looks something like this:

<div style="width: 200px; overflow: hidden">
   <span style="display: inline-block; text-overflow: ellipsis; width: 100px">
      A really long label is shown in this span
   </span>
   <span style="display: inline-block"> this span</span>
</div>

Because the first span has text-overflow set to "ellipsis", it will show "..." at the end, followed by the 10 characters of the second span, resulting in the ellipsis showing approximately in the middle of the div.

You don't need to hardcode the 10 character length for the endSpan either: this can be approximated by calculating ratio of the span's initial width to that of the div, subtracting the appropriate proportion from the length of the label and dividing by two.

Solution 6 - Javascript

You can't do that with CSS. The problem is that HTML and CSS are supposed to work in a variety of browsers and fonts and it is almost impossible to calculate the width of a string in a consistent way. This is an idea that might help you. However, you would need to do that a number of times, until you find the string with the appropriate width.

Solution 7 - Javascript

After some research on flex boxes I found this pure CSS solution which I believe is pretty cool.

<div style="width:100%;border:1px solid green;display:inline-flex;flex-wrap:nowrap;">
   <div style="flex: 0 1 content;text-overflow: ellipsis;overflow:hidden;white-space:nowrap;"> Her comes very very very very very very very very very very very very very very very very very very very long </div>
   <div style="flex: 1 0 content;white-space:nowrap;"> &nbsp;but flexible line</div>
</div>

Solution 8 - Javascript

None of the solutions that I saw here take into account that different characters have different width. My solution takes this into account. Basically, it strips text by one character until it fits, take a look:

const span = document.getElementById("span");
const div = document.getElementById("div");
const originalText = span.textContent;
const textLength = originalText.length;
let part1 = originalText.substr(0, Math.floor(textLength / 2));
let part2 = originalText.substr(Math.floor(textLength / 2));
let trimPart1 = true;
while (span.clientWidth > div.clientWidth) {
  if (trimPart1) {
    part1 = part1.substr(0, part1.length - 1);
  } else {
    part2 = part2.substr(-1 * (part2.length - 1));
  }
  span.textContent = part1 + "..." + part2;
  trimPart1 = !trimPart1;
}

<div id="div" style="overflow: hidden; width: 200px; white-space: nowrap;">
  <span id="span" style="display: inline-block">this is a quite long text that has some words and I want it to be split in half</span>
</div>

https://jsfiddle.net/maxim_mazurok/oujctpz8/56/

Mac's Finder works the same way, it first tries to strip the left part, then the right part. So, it may have WWWW...WWWWW, just as my solution.

It's not the most efficient one though. Perhaps, the same can be achieved using virtual DOM or canvas to better optimize performance.

Solution 9 - Javascript

Here's the shortest bit I could find which replaces 3 characters in the middle with ....

function shorten(s, max) {
  return s.length > max ? s.substring(0, (max / 2) - 1) + '...' + s.substring(s.length - (max / 2) + 2, s.length) : s
}

Solution 10 - Javascript

This solution is a mix of the above solutions and puts the last whole word at the end of the shortened text. However in case the last word is longer then a third of the available space it is also shortend from the left. If a dash("-") is found, cut it of there, if not, cut it of anyway.

function truncate(text, textLimit) {
    if (!text) return text;
    if (textLimit < 1) return string;
    if (text.length < textLimit) return text;
    if (textLimit === 1) return text.substring(0,1) + '...';
    /* extract the last word */
    var lastPart = text.slice( string.lastIndexOf(' ')+1 );
    /* if last word is longer then a third of the max available space
       cut it from the left */
    var lastPartLimit = Math.ceil(textLimit / 3);
    if(lastPart.length > lastPartLimit) {
        var truncatedLastPart = lastPart;
        /* Try to find a dash and cut the last word there */
        var lastDashPart = text.slice( text.lastIndexOf('-')+1 );
        if(lastDashPart.length < lastPartLimit){
            truncatedLastPart = lastDashPart;
        }
        /* If the last part is still to long or not available cut it anyway */
        if(truncatedLastPart.length > lastPartLimit) {
            var lastIndex = lastPart.length - lastPartLimit;
            truncatedLastPart = lastPart.substring( lastIndex );
        }
        lastPart = truncatedLastPart;
    }
    var dots = '... ';
    var firsPartLength = textLimit - lastPart.length - dots.length;
    return text.substring(0, firstPartLength) + dots + lastPart;
}

console.log( truncate("New York City", 10) ); // Ne... City (max of 10 characters)
console.log( truncate("New York Kindergarden", 14) ); // Ne...ergarden (max of 14 characters, last word gets cut from the left by a third)
console.log( truncate("New York Kinder-garden", 14) ); // Ne...garden (max of 14 characters, last word gets cut by the dash from the left)

Solution 11 - Javascript

Here's an elegant solution:

function truncateMiddle(word) {
    const tooLongChars = 15; // arbitrary
    
    if (word.length < tooLongChars) {
        return word;
    }

    const ellipsis = '...';
    const charsOnEitherSide = Math.floor(tooLongChars / 2) - ellipsis.length;

    return word.slice(0, charsOnEitherSide) + ellipsis + word.slice(-charsOnEitherSide);
}

Solution 12 - Javascript

Here's my take on it – a CSS-only solution that sadly only works in Firefox right now:

div {
  width: 20em;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  text-overflow: " … is also your text.";
}

<div>Here's the long string of letters that accidentally is also your text.</div>

You can specify the second text-overflow declaration directly on the element if you need your text to be dynamic. Browsers that do not support this syntax (Chrome, notably :-) will fall back to plain old ellipsis.

Solution 13 - Javascript

I just created a function that can trim at the middle, nearEnd and End but havent been tested yet because I finally was needing it at the server side

//position acceptable values : middle, end, closeEnd
function AddElipsis(input, maxChars, position) {
    if (typeof input === 'undefined') {
        return "";
    }
    else if (input.length <= maxChars) {
        return input;
    }
    else {
        if (position == 'middle') {
            var midPos = Math.floor(maxChars / 2) - 2;
            return input.substr(0, midPos) + '...' + input.substr(input.length - midPos, input.length);
        }
        else if (position == 'closeEnd') {
            var firstPart = Math.floor(maxChars * 0.80) - 2;
            var endPart = Math.floor(maxChars * 0.20) - 2;
            return input.substr(0, firstPart) + '...' + input.substr(input.length - endPart, input.length);
        }
        else {
            return input.substr(0, maxChars - 3) + '...';
        }
    }
}

Solution 14 - Javascript

Another stab:

function truncate( str, max, sep ) {
    max = max || 10;
    var len = str.length;
    if(len > max){
        sep = sep || "...";
        var seplen = sep.length;
        if(seplen > max) { return str.substr(len - max) }

        var n = -0.5 * (max - len - seplen);
        var center = len/2;
        return str.substr(0, center - n) + sep + str.substr(len - center + n);
    }
    return str;
}

console.log( truncate("123456789abcde") ); // 123...bcde (using built-in defaults) 
console.log( truncate("123456789abcde", 8) ); // 12...cde (max of 8 characters) 
console.log( truncate("123456789abcde", 12, "_") ); // 12345_9abcde (customize the separator) 

Solution 15 - Javascript

This will give you a little more control over the position of the ellipsis and the placeholder text:

function ellipsis(str, maxLength, ellipsisLocationPercentage,placeholder) {
	/*
	ARGUMENTS:
	str - the string you want to maninpulate
	maxLength -  max number of characters allowed in return string
	ellipsisLocationPercentage (optional) - How far (percentage wise) into the return string you want the ellipses to be placed
		Examples:
		.85 : This is a very long string. This is a very long string. This is a very long string. This is a ver[...]very long string.
		.25 : This is a very long string. [...]g. This is a very long string. This is a very long string. This is a very long string.
	placeholder (optional) - this will be used to replace the removed substring. Suggestions : '...', '[..]', '[ ... ]', etc....
	*/
	if(ellipsisLocationPercentage == null || isNaN(ellipsisLocationPercentage) || ellipsisLocationPercentage >= 1 || ellipsisLocationPercentage <= 0){
		//we've got null or bad data.. default to something fun, like 85% (that's fun, right??)
		ellipsisLocationPercentage = .85;
	}
	if(placeholder == null || placeholder ==""){
		placeholder = "[...]";
	}
	
	if (str.length > (maxLength-placeholder.length)) {
		//get the end of the string
		var beginning = str.substr(0, (maxLength - placeholder.length)*ellipsisLocationPercentage );
		var end = str.substr(str.length-(maxLength - placeholder.length) * (1-ellipsisLocationPercentage));
		return beginning + placeholder + end;
	}
	return str;
}

You can call this function by calling:

ellipsis("This is a very long string. Be Scared!!!!", 8);//uses default values
ellipsis("This is a very long string. Be Scared!!!!", 8,.5);//puts ellipsis at half way point
ellipsis("This is a very long string. Be Scared!!!!", 8,.75,'<..>');//puts ellipsis at 75% of the way into the string and uses '<..>' as the placeholder

Solution 16 - Javascript

To make a clean cut and have a whole word at the end of the shortened text, I used the below function.

function prepareText(text){
  var returnString = text;
  var textLimit = 35;
  if(text.length > textLimit){
    var lastWord = text.slice( text.lastIndexOf(' ')+1 );
    var indexFromEnd = lastWord.length;
    var ellipsis = '... ';

    returnString = text.slice(0, textLimit - indexFromEnd - ellipsis.length);
    returnString = returnString + ellipsis + lastWord;
  }
  return returnString;
}

$('#ex1Modified').html( prepareText( $('#ex1').html() ) );

$('#ex2Modified').html( prepareText( $('#ex2').html() ) );

$('#ex3Modified').html( prepareText( $('#ex3').html() ) );

body{color:#777; font-family: sans-serif;}

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<h2>Shortened Quotes from Albert Einstein</h2>

<div id="ex1">"The true sign of intelligence is not knowledge but imagination."</div>
<div id="ex1Modified"></div>
<br>
<div id="ex2">"Look deep into nature, and then you will understand everything better."</div>
<div id="ex2Modified"></div>
<br>
<div id="ex3">"You can't blame gravity for falling in love."</div>
<div id="ex3Modified"></div>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser102465View Question on Stackoverflow
Solution 1 - JavascriptStefan LundströmView Answer on Stackoverflow
Solution 2 - JavascriptextemplView Answer on Stackoverflow
Solution 3 - JavascriptDan PerryView Answer on Stackoverflow
Solution 4 - JavascriptjohnveyView Answer on Stackoverflow
Solution 5 - JavascriptlevikView Answer on Stackoverflow
Solution 6 - JavascriptkgiannakakisView Answer on Stackoverflow
Solution 7 - JavascriptNikola SchouView Answer on Stackoverflow
Solution 8 - JavascriptMaxim MazurokView Answer on Stackoverflow
Solution 9 - JavascriptJochen BedersdorferView Answer on Stackoverflow
Solution 10 - JavascriptflaskolnView Answer on Stackoverflow
Solution 11 - JavascriptJeremy MoritzView Answer on Stackoverflow
Solution 12 - JavascriptAleView Answer on Stackoverflow
Solution 13 - JavascriptMenelaos VergisView Answer on Stackoverflow
Solution 14 - JavascriptbobView Answer on Stackoverflow
Solution 15 - JavascriptbuddamusView Answer on Stackoverflow
Solution 16 - JavascriptaeroView Answer on Stackoverflow