Text-overflow ellipsis on left side

JavascriptHtmlCss

Javascript Problem Overview


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.

Javascript Solutions


Solution 1 - Javascript

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>

Solution 2 - Javascript

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

Solution 3 - Javascript

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>

Solution 4 - Javascript

Why not just using direction:rtl;

Solution 5 - Javascript

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

Solution 6 - Javascript

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).

Solution 7 - Javascript

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

Solution 8 - Javascript

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.

Solution 9 - Javascript

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>

Solution 10 - Javascript

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>

Note

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

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
QuestionHemlockView Question on Stackoverflow
Solution 1 - Javascriptj08691View Answer on Stackoverflow
Solution 2 - JavascriptHemlockView Answer on Stackoverflow
Solution 3 - JavascriptyunzenView Answer on Stackoverflow
Solution 4 - JavascriptYGTView Answer on Stackoverflow
Solution 5 - JavascriptGraham DixonView Answer on Stackoverflow
Solution 6 - Javascriptmbaer3000View Answer on Stackoverflow
Solution 7 - JavascriptElChiniNetView Answer on Stackoverflow
Solution 8 - JavascriptScottSView Answer on Stackoverflow
Solution 9 - JavascriptMasterNoneView Answer on Stackoverflow
Solution 10 - JavascriptDJDaveMarkView Answer on Stackoverflow