84

I have a list of paths (for lack of a better word, maybe bread crumb trails describes them better). Some of the values are too long to display in their parent so I'm using text-overflow: ellipsis. The problem is that the important information is on the right, so I'd like the ellipsis to appear on the left. Something like this this ascii art:

----------------------------
|first > second > third    |
|...second > third > fourth|
|...fifth > sixth > seventh|
----------------------------

Notice that the first row is short enough so it remains left aligned, but the other two are too long so the ellipsis appears on the left hand side.

I'd prefer a CSS only solution, but JS is fine if it can't be avoided. It's ok if the solution only works in Firefox and Chrome.

EDIT: At this point I'm looking for a work around for the bugs in Chrome that prevent it from rendering properly when a document is mixed RTL and LTR. That was all I really needed from the outset, I just didn't realize it.

Jesper Rønn-Jensen
  • 106,591
  • 44
  • 118
  • 155
Hemlock
  • 6,130
  • 1
  • 27
  • 37
  • 1
    Ouch, mixed RTL and LTR langages? I had lots of problem with that recently. What I found helped a lot was when I was separating each RTL and LTR elements in different divs. Otherwise both langage were getting mixed in relly weird ways... – Zwik Mar 26 '12 at 18:52
  • 1
    FWI: I have a bug open on the chrome issue: http://code.google.com/p/chromium/issues/detail?id=155836 – Mbrevda Feb 13 '13 at 10:26
  • possible duplicate of [Needs use right "text-overflow" when "direction" is set to "rtl"](http://stackoverflow.com/questions/18532256/needs-use-right-text-overflow-when-direction-is-set-to-rtl) – Mr_Green Aug 30 '13 at 15:20
  • See https://stackoverflow.com/a/65897516/2194590 for a clean ltr CSS and bdi child solution. – HolgerJeromin Jan 26 '21 at 09:08

10 Answers10

113

How about something like this jsFiddle? It uses the direction, text-align, and text-overflow to get the ellipsis on the left. According to MDN, there may be the possibility of specifying the ellipsis on the left in the future with the left-overflow-type value however it's considered to still be experimental.

p {
  white-space: nowrap;
  overflow: hidden;
  /* "overflow" value must be different from "visible" */
  text-overflow: ellipsis;
  width: 170px;
  border: 1px solid #999;
  direction: rtl;
  text-align: left;
}
<p>first > second > third<br /> second > third > fourth > fifth > sixth<br /> fifth > sixth > seventh > eighth > ninth</p>​
j08691
  • 204,283
  • 31
  • 260
  • 272
  • 10
    This is close. Looks good in FF but in Chrome it renders the second and third lines wrong. Looks like the just prepends the ellipsis and then clips on the right. – Hemlock Mar 20 '12 at 19:43
  • @Hemlock - Oh so according to your example, the first element of the second line and first four elements of the third line should be cutoff? – j08691 Mar 20 '12 at 19:49
  • 5
    The solution is right, but Chrome has a rendering bug with mixed ltr and rtl documents. – Sven Bieder Mar 20 '12 at 19:53
  • @j08691 yes, just like your example does in Firefox. – Hemlock Mar 20 '12 at 20:04
  • Its a real shame this css solution doesn't quite cut it in chrome. – Blowsie Feb 05 '13 at 10:00
  • 6
    This is very close however, adding symbols is doesn't work. Adding an '@' for instance causes the '@' to be added on the left hand side and adding further text appends to the left hand side of the '@' symbol. Most symbols seem to cause this issues (testing only in chrome). – Luke Mar 20 '15 at 06:37
  • I am facing the same problem stated by @Luke. Tested in Chome and IE11. Anyone can make it work? – robsonrosa Apr 27 '15 at 21:27
  • 4
    @Luke and @robsonrosa You can precede them with `‎` as discssed [here](https://stackoverflow.com/questions/27957443/strange-special-characters-issue-in-left-side-ellipsis) – AntalOvc Dec 20 '17 at 14:17
  • Neither the left nor right edges are properly aligned in Chrome: https://i.imgur.com/xxWJ2e5.png Beats what I had though. Thanks! – mpen Feb 08 '18 at 01:37
  • 1
    To add to what @AntalOvc said, you can also do a RTL "embed" to affect the whole sequence. Adding `‪` seems to fix things nicely. – Jacob Jul 29 '18 at 17:43
  • It does not works for `direction: rtl;` ! multiline. – 151291 Nov 10 '18 at 09:12
  • and for intrepid Googlers arriving now, it seems to work fine in Chrome but be broken in Safari, so ... good times – steve Jul 19 '19 at 19:10
  • 3
    @Luke and @robsonrosa adding `unicode-bidi: plaintext` worked for me. Shame about the [poor browser support](https://caniuse.com/#search=unicode-bidi). – poshest Sep 02 '19 at 15:48
14

I finally had to crack and do something in JavaScript. I was hoping that someone would come up with a hail-mary CSS solution but people seem to just be up-voting the answer that should be correct if it weren't for the Chrome bugs. j08691 can have the bounty for his work.

<html>
    <head>
        <style>
            #container {
                width: 200px;
                border: 1px solid blue;
            }

            #container div {
                width: 100%;
                overflow: hidden;
                white-space: nowrap;
            }
        </style>
        <script>
            function trimRows() {

                var rows = document.getElementById('container').childNodes;
                for (var i=0, row; row = rows[i]; i++) {
                    if (row.scrollWidth > row.offsetWidth) {
                        var textNode = row.firstChild;
                        var value = '...' + textNode.nodeValue;
                        do {
                            value = '...' + value.substr(4);
                            textNode.nodeValue = value;

                        } while (row.scrollWidth > row.offsetWidth);
                    }
                }
            }
        </script>
    </head>
    <body onload='trimRows();'>
    <div id="container" >
        <div>first > second > third</div>
        <div>second > third > fourth > fifth > sixth</div>
        <div>fifth > sixth > seventh > eighth > ninth</div>​
    </div>
    </body>

</html>

Fiddle

Hemlock
  • 6,130
  • 1
  • 27
  • 37
  • 1
    I took your solution a step further, so it works when the element being trimmed has more than a single text node inside: http://jsfiddle.net/bgmort/qSyBU/3/ – undefined Oct 24 '12 at 18:33
  • 4
    Thanks so much for that Brian! I forked your code as I needed something that would replace with '...' at the beginning, but also when the window is resized, and then revert the text back to it's initial state when the window is increased in size. It's probably not the most elegant, but here is the fork for anyone who might find it useful: http://jsfiddle.net/sP9AE/1/ – Jimbo Oct 30 '12 at 14:06
  • Very nice, @Jimbo. now maybe someone can abstract it into a jQuery plugin. I might do it sometime... – undefined Jan 08 '13 at 21:16
  • Ah, nevermind. Apparently I already did that in the code where I implemented this. I have pasted it into a gist: https://gist.github.com/4489877 This does not include the changes that @Jimbo made, so you might want to compare his solution if you need that functionality. – undefined Jan 09 '13 at 01:51
  • 1
    You can also try trunk8 or jquery-truncate plugins – djjeck Sep 29 '14 at 20:28
  • 1
    It's probably better to use '\u2026' for the ellipsis character, rather than '...' – Spooky Nov 17 '14 at 20:46
7

Why not just using direction:rtl;

YGT
  • 111
  • 1
  • 6
  • 4
    Because of bugs in Chrome when RTL and LTR are mixed. – Hemlock Jun 05 '14 at 13:54
  • 1
    This should actually be the answer for today. RTL works just fine now. View this answer on another post: https://stackoverflow.com/questions/34811197/text-overflow-with-ellipsis-on-the-left#34811257 – Megaroeny Apr 25 '18 at 17:12
  • tested this in 2022, it works just fine. I was using `flex: row-reverse` to change the direction of text/images in a container, i removed the flex and added the direction, everything was the same + the ellipsis now work on the correct side – Ricardo Dias Morais Feb 01 '22 at 15:57
7

It's a little buggy, but maybe a point in the right direction

http://jsfiddle.net/HerrSerker/ZfbaD/50/

$('.container')
    .animate({'width': 450}, 4000)
    .animate({'width': 100}, 4000)
    .animate({'width': 170}, 4000)
.container {  
  white-space: nowrap;                   
  overflow: hidden;              /* "overflow" value must be different from "visible" */   
  text-overflow: ellipsis;  
    width:170px;
    border:1px solid #999;
    direction:rtl;
}  
.container .part {
  direction:ltr;

}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="container">
    <span class="part">second</span> 
    <span class="part">&gt;</span> 
    <span class="part">third</span> 
    <span class="part">&gt;</span> 
    <span class="part">fourth</span> 
    <span class="part">&gt;</span> 
    <span class="part">fifth</span> 
    <span class="part">&gt;</span> 
    <span class="part">sixth</span>
</div>
yunzen
  • 32,854
  • 11
  • 73
  • 106
  • This is close. That is so strange how it adds the ellipsis at the start of the word, but trims the word from the end. – Hemlock Mar 27 '12 at 18:07
6

These solutions solve the problem with misinterpreted preceding or trailing weak or neutral BiDi characters such as /, \, ~, ., etc. (basically any punctuation or special characters).

CSS Solution

Use a combination of:

p {
  direction: rtl;
  max-width: 180px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap; /* or pre (e.g. preserve multiple spaces) */
}
span {
  direction: ltr;
  unicode-bidi: bidi-override; /* or isolate, isolate-override, embed */
}
<p><span>/path/to/a/very/long/file.name</span></p>

<bdo> Solution

Another possibility uses the <bdo> Bidirectional Text Override element:

p {
  max-width: 180px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap; /* or pre (e.g. preserve multiple spaces) */
}
<bdo dir="rtl">
  <p>
    <bdo dir="ltr">/path/to/a/very/long/file.name</bdo>
  </p>
</bdo>
DJDaveMark
  • 2,669
  • 23
  • 35
1

Using @Hemlocks, @Brian Mortenson and @Jimbo's solutions, I've built a jQuery plugin to solve this problem.

I've also added support to return the initial value using .html() rather than having it return the current innerHTML. Hopefully it will be useful to someone...

(function($) {

$.trimLeft = function(element, options) {

    var trim = this;

    var $element = $(element), // reference to the jQuery version of DOM element
         element = element;    // reference to the actual DOM element

    var initialText = element.innerHTML;

    trim.init = function() {
        overrideNodeMethod("html", function(){ return initialText; });
        trimContents(element, element);
        return trim;
    };

    trim.reset = function(){
        element.innerHTML = initialText;
        return trim;
    };

    //Overide .html() to return initialText.
    var overrideNodeMethod = function(methodName, action) {
        var originalVal = $.fn[methodName];
        var thisNode = $element;
        $.fn[methodName] = function() {
            if (this[0]==thisNode[0]) {
                return action.apply(this, arguments);
            } else {
                return originalVal.apply(this, arguments);
            }
        };
    };

    var trimContents = function(row, node){
        while (row.scrollWidth > row.offsetWidth) {
            var childNode = node.firstChild;
            if (!childNode)
                return true;            
            if (childNode.nodeType == document.TEXT_NODE){
                trimText(row, node, childNode);
            }
            else {
                var empty = trimContents(row, childNode);
                if (empty){
                    node.removeChild(childNode);
                }
            }
        };
    };

    var trimText = function(row, node, textNode){
        var value = '\u2026' + textNode.nodeValue;
        do {
            value = '\u2026' + value.substr(4);
            textNode.nodeValue = value;
            if (value == '\u2026'){
                node.removeChild(textNode);
                return;
            }
        }
        while (row.scrollWidth > row.offsetWidth);
    };

    trim.init();

};

$.fn.trimLeft = (function(options){
  var othat = this;

  var single = function(that){
      if (undefined == $(that).data('trim')) {
          var trim = new $.trimLeft(that, options);
          $(that).data('trim', trim);
          $(window).resize(function(){
              $(that).each(function(){
                    trim.reset().init();
              });
          });
       }   
   };

   var multiple = function(){
        $(othat).each(function() {
            single(this);
        });
    };

    if($(othat).length>1)
        multiple(othat);            
    else
        single(othat);

    //-----------        
    return this;
});


})(jQuery);

Initiate using:

//Call on elements with overflow: hidden and white-space: nowrap 
$('#container>div').trimLeft();
//Returns the original innerHTML
console.log($('#test').html());

fiddle

Graham Dixon
  • 11
  • 1
  • 1
1

Using a slightly more complex markup (using the bdi-tag and an extra span for the ellipsis), we can solve the problem fully in CSS, no JS required at all -- cross browser (IE, FF, Chrome) and including keeping punctuation marks to the right:

http://jsbin.com/dodijuwebe/1/edit?html,css,output

Granted, this is something of a hack, involving pseudo-element goodness. However, our team has been using this code in production and we haven't had any issues whatsoever.

The only caveats are: The height of the line needs to be fixed and the background color needs to be known explicitly (inherit won't work).

mbaer3000
  • 479
  • 4
  • 5
1

If you don't care the indexing of those texts, you could use this method (it reverses the text lines):

If you have in your texts other HTML elements besides <br> you need to make some arrangements to use this method.

HTML code:

<p>first > second > third<br/>
second > third > fourth <br>
fifth > sixth > seventh</p>

CSS code:

p{
  overflow: hidden;
  text-overflow: ellipsis;
  unicode-bidi: bidi-override;
  direction: rtl;
  text-align: left;
  white-space: nowrap;
  width: 140px;
}

JavaScript code

[].forEach.call(document.getElementsByTagName("p"), function(item) {

  var str = item.innerText;

  //Change the operators
  str = str.replace(/[<>]/g, function(char){ return ({"<" : ">", ">" : "<"})[char] });

  //Get lines
  var lines = str.split(/\n/);

  //Reverse the lines
  lines = lines.map(function(l){ return l.split("").reverse().join("") }); 

  //Join the lines
  str = lines.join("<br>");

  item.innerHTML = str;

});

jsfiddle

ElChiniNet
  • 2,778
  • 2
  • 19
  • 27
0

Based on your edit:

At this point I'm looking for a work around for the bugs in Chrome that prevent it from rendering properly when a document is mixed RTL and LTR. That was all I really needed from the outset, I just didn't realize it.

Have you looked into the unicode-bidi css property (see Sitepoint or W3C)? I actually just learned about this myself on another recent post. My guess is you would want to use the embed value for those pieces going the opposite direction to the main site. So in j08691's answer where it is direction: rtl add unicode-bidi: embed to the CSS. This should solve "mixed RTL and LTR" issues you are having.

Community
  • 1
  • 1
ScottS
  • 71,703
  • 13
  • 126
  • 146
  • I had high hopes for this but I tried `unicode-bidi` [here](http://jsfiddle.net/ZfbaD/9/) and had no luck. – Hemlock Mar 27 '12 at 00:07
  • Sorry. Based off the dates [on this](http://code.google.com/p/chromium/issues/detail?id=2821) (top 2008, bottom 2011), there has been issues with this (`rtl` and `ellipsis`) for a while in Chrome. It almost appears as if it was solved and then maybe resurfaced, but it is hard to tell. – ScottS Mar 27 '12 at 02:58
  • @ScottS there definitely is a bug, issue filed here: http://code.google.com/p/chromium/issues/detail?id=155836 – Mbrevda Feb 13 '13 at 10:26
0

I put some JavaScript together to regex out three items and add the ellipsis in where necessary. This does not explicitly look at how much text will fit in the box but if the box is fixed this may not be an issue.

<style>
p {  
    white-space: nowrap;                     
    overflow: hidden;
    text-overflow: ellipsis; 
    width:170px;
    border:1px solid #999;
    direction:rtl;
    text-align:left;
} 
</style>

<p>first &gt; second &gt; third<br />
second &gt; third &gt; fourth &gt; fifth &gt; sixth<br />
fifth &lt; sixth &lt; seventh &lt; eighth &lt; ninth</p>

<script>
    var text = $( 'p' ).text(),
        split = text.split( '\n' ),
        finalStr = '';
    for( i in split ){
        finalStr = finalStr.length > 0 ? finalStr + '<br />' : finalStr;
        var match = /(\w+\s?(<|>)?\s?){3}$/.exec( split[i] );
        finalStr = finalStr + ( split[i].length > match[0].length ? '...' : '' ) + match[0];
    }
    $( 'p' ).empty().html( finalStr );
</script>
  • Js is probably necessary to solve this. I'm not able to use this answer though because I'm not using jquery and I'm sure measurement will be necessary. – Hemlock Mar 30 '12 at 17:55
  • It's probably better to use '\u2026' for the ellipsis character, rather than '...' – Spooky Nov 17 '14 at 20:48