views:

7558

answers:

9

I have a webpage with an elastic layout that changes it's width if the browser window is resized.

In this layout there are headlines (<h2>) that will have a variable length (actually being headlines from blogposts that I don't have control over). Currently - if they are wider than the window - they are broken into two lines.

Is there an elegant, tested (cross-browser) solution - for example with jQuery - that shortens the innerHTML of that headline tag and adds "..." if the text would be too wide to fit into one line at the current screen/container width?

+2  A: 

Well, one simple solution, that doesn't quite add the "...", but does prevent the <h2> from breaking into two lines would be to add this bit of css:

h2 {
    height:some_height_in_px; /* this is the height of the line */
    overflow:hidden; /* so that the second (or third, fourth, etc.)
                        line is not visible */
}

I gave it some more thought, and I came up with this solution, you have to wrap the textual contents of your h2 tag with another tag (e.g. a span) (or alternatively wrap the h2s with something that has the given height) and then you can use this sort of javascript to filter out the unneeded words:

var elems = document.getElementById('conainter_of_h2s').
                     getElementsByTagName('h2');

    for ( var i = 0, l = elems.length; i < l; i++) {
     var span = elems.item(i).getElementsByTagName('span')[0];
     if ( span.offsetHeight > elems.item(i).offsetHeight ) {
      var text_arr = span.innerHTML.split(' ');
      for ( var j = text_arr.length - 1; j>0 ; j--) {
       delete text_arr[j];
       span.innerHTML = text_arr.join(' ') + '...';
       if ( span.offsetHeight <= 
                                        elems.item(i).offsetHeight ){
        break;
       }
      }
     }
    }
Ramuns Usovs
Actually I thought about using this as a basis for a possible solution, but I have no idea if - based on this - it would be possible to find out, if the whole text is now displayed or if I need to shorten it and add "...". Just cutting it off would look weird.
BlaM
+2  A: 

There's actually a pretty straightforward way to do this in CSS exploiting the fact that IE extends this with non-standards and FF supports :after

You can also do this in JS if you wish by inspecting the scrollWidth of the target and comparing it to it's parents width, but imho this is less robust.

Edit: this is apparently more developed than I thought. CSS3 support may soon exist, and some imperfect extensions are available for you to try.

That last one is good reading.

annakata
Actually I prefer the JS solution - because it only adds "..." if the text is wider than the available space.
BlaM
+2  A: 

I'd done something similar for a client recently. Here's a version of what I did for them (example tested in all latest browser versions on Win Vista). Not perfect all around the board, but could be tweaked pretty easily.

Demo: http://enobrev.info/ellipsis/

Code:

<html>
    <head>
     <script src="http://www.google.com/jsapi"&gt;&lt;/script&gt;
     <script>   
      google.load("jquery", "1.2.6");
      google.setOnLoadCallback(function() {
       $('.longtext').each(function() {
        if ($(this).attr('scrollWidth') > $(this).width()) {
         $more = $('<b class="more">&hellip;</b>');

         // add it to the dom first, so it will have dimensions
         $(this).append($more);

         // now set the position
         $more.css({
          top: '-' + $(this).height() + 'px',
          left: ($(this).attr('offsetWidth') - $more.attr('offsetWidth')) + 'px'
         });
        }
       });
      });
     </script>

     <style>
      .longtext {
       height: 20px;
       width: 300px;
       overflow: hidden;
       white-space: nowrap;
       border: 1px solid #f00;
      }

      .more {
       z-index: 10;
       position: relative;
       display: block;
       background-color: #fff;
       width: 18px;
       padding: 0 2px;
      }
     </style>
    </head>
    <body>
     <p class="longtext">This is some really long text.  This is some really long text.  This is some really long text.  This is some really long text.</p>
    </body>
</html>
enobrev
+2  A: 

There's another JavaScript solution here: http://www.hedgerwow.com/360/dhtml/text_overflow/demo2.php. It uses text-overflow:ellipsis; for Internet Explorer and Safari, -o-text-overflow:ellipsis; for Opera, and some JavaScript for Firefox that listens for overflow and underflow events.

It claims to support IE 5.5+, Opera 9+, Firefox 1.0+, and Safari 2.0+.

Matthew Crumley
+2  A: 

I have found a (new) simple jQuery solution here:

http://devongovett.wordpress.com/2009/04/06/text-overflow-ellipsis-for-firefox-via-jquery/

To use, just call ellipsis() on a jQuery object. For example:

$("span").ellipsis();

BlaM
I was just about to post the same link. :)
Gumbo
+6  A: 

I've got a solution working in FF3, Safari and IE6+ with single and multiline text

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

.ellipsis.multiline {
 white-space: normal;
}

<div class="ellipsis" style="width: 100px; border: 1px solid black;">Lorem ipsum dolor sit amet, consectetur adipisicing elit</div>
<div class="ellipsis multiline" style="width: 100px; height: 40px; border: 1px solid black; margin-bottom: 100px">Lorem ipsum dolor sit amet, consectetur adipisicing elit</div>

<script type="text/javascript" src="/js/jquery.ellipsis.js"></script>
<script type="text/javascript">
$(".ellipsis").ellipsis();
</script>

jquery.ellipsis.js

(function($) {
 $.fn.ellipsis = function()
 {
  return this.each(function()
  {
   var el = $(this);

   if(el.css("overflow") == "hidden")
   {
    var text = el.html();
    var multiline = el.hasClass('multiline');
    var t = $(this.cloneNode(true))
     .hide()
     .css('position', 'absolute')
     .css('overflow', 'visible')
     .width(multiline ? el.width() : 'auto')
     .height(multiline ? 'auto' : el.height())
     ;

    el.after(t);

    function height() { return t.height() > el.height(); };
    function width() { return t.width() > el.width(); };

    var func = multiline ? height : width;

    while (text.length > 0 && func())
    {
     text = text.substr(0, text.length - 1);
     t.html(text + "...");
    }

    el.html(t.html());
    t.remove();
   }
  });
 };
})(jQuery);
alex
Nice, I've been looking how to handle overflow with multiple lines. One improvement: instead of appending three periods, append the ellipsis character, '…'.
Simon Lieschke
This works very well. You should publish this on the jQuery site.
Ed
+2  A: 

Justin Maxwell has an excellent CSS solution to this problem. Unlike some of the JavaScript solutions presented here it doesn't cut off the text mid-letter. It does come with the downside however of not allowing the text to be selected in Firefox. Check out his guest post on Matt Snider's blog for the full details on how this works.

CSS

.ellipsis {
 white-space: nowrap;
 overflow: hidden;
 text-overflow: ellipsis;
 -o-text-overflow: ellipsis;
 -moz-binding: url('assets/xml/ellipsis.xml#ellipsis');
}

ellipsis.xml file contents

<?xml version="1.0"?>
<bindings
  xmlns="http://www.mozilla.org/xbl"
  xmlns:xbl="http://www.mozilla.org/xbl"
  xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
>
 <binding id="ellipsis">
  <content>
   <xul:window>
    <xul:description crop="end" xbl:inherits="value=xbl:text"><children/></xul:description>
   </xul:window>
  </content>
 </binding>
</bindings>
Simon Lieschke
+5  A: 

I really cool jQuery plugin for handling all varieties of ellipsis of text is one called ThreeDots @ http://tpgblog.com/threedots

It's much more flexible than the CSS approaches, and supports much more advanced, customizable behaviors and interactions.

Enjoy.

Jeremy Horn
+1  A: 

This is similar to Alex's but does it in log time instead of linear, and takes a maxHeight parameter.

jQuery.fn.ellipsis = function(text, maxHeight) {
  var element = $(this);
  var characters = text.length;
  var step = text.length / 2;
  var newText = text;
  while (step > 0) {
    element.html(newText);
    if (element.outerHeight() <= maxHeight) {
      if (text.length == newText.length) {
        step = 0;
      } else {
        characters += step;
        newText = text.substring(0, characters);
      }
    } else {
      characters -= step;
      newText = newText.substring(0, characters);
    }
    step = parseInt(step / 2);
  }
  if (text.length > newText.length) {
    element.html(newText + "...");
    while (element.outerHeight() > maxHeight && newText.length >= 1) {
      newText = newText.substring(0, newText.length - 1);
      element.html(newText + "...");
    }
  }
};
Dave Aaron Smith