Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ellipsis in the middle of a text (Mac style)

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>

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>

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/


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');

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.


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.