Transition of height with “white-space: nowrap” involved: possible with fewer Javascript?

Subject

I have a text-box that hides overflowing text with ellipsis. Only one line is visible.

I accomplished this using text-overflow and white-space: nowrap. The element has a fixed height value.

When tapped/clicked the box should smoothly expand (it's height) to show the rest of the text.

I accomplished this using CSS transitions and Javascript. A hidden "shadow" description container contains the same text without white-space and overflow handling. It has the full height. When toggeling the actual box I set the height value read from the "shadow" element and add a class that will remove the white-space and overflow stuff. The transition: height option makes this change smooth.

Question

I'd like to do this without extra Javascript logic and without the shadow element.
I'd like to do this without

  • using setTimeout() to wait for the transition
  • the shadow element

Only removing white-space and text-overflow does not work because they're not transitionable.

I'm curious if there's another trick that does this.

Working code

Fiddle: http://jsfiddle.net/ywQTd/ (Make the window small enough to trigger text-overflow: hidden)

CSS:

#control {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;

    background-color: rgba(0,0,0, 0.05);
}

.description {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    background-color: rgba(0,0,0, 0.2);
    color: rgb(255,255,255);
    padding: 0 10px;

    line-height: 25px;
    transition: height 0.5s ease;

    text-align: center;
}

#description {
    height: 25px;
}

#description.expand {
    overflow: hidden;
    text-overflow: initial;
    white-space: normal;
}

#shadow-description {
    position: absolute;
    top: -10000px;

    text-overflow: initial;
    overflow: visible;
    white-space: normal;
}

Click Handler (in jQuery on-load Fn):

$("...").on("click", function(){
    var h = $("#shadow-description").height(),
        $el = $("#description");

    if ($el.hasClass("expand")) {
        $el.css({height: ""});

        setTimeout(function(){
            $el.removeClass("expand");
        }, 200);
    } else {
        $el.css({height: h + "px"}).addClass("expand");

    }

});

HTML:

<div id="control">
    <div id="description" class="description">lorem ipsum dolor sit amet lorem ipsum dolor sit amet lorem ipsum dolor sit amet</div>
    <div id="shadow-description" class="description">lorem ipsum dolor sit amet lorem ipsum dolor sit amet lorem ipsum dolor sit amet</div>
</div>

Update

The initial title was

Transition of height with "white-space: nowrap" involved: is there a JS-less way?

I've changed it because it was not clear enough. Of course it's not possible without Javascript since there has to be a click handler that will do "something". But I'd like to archive the goal with fewer Javascript logic and without timing in Javascript. ;)

Further I've clarified the first paragraph in the Question section.


Addendum

@jme11 kindly pointed out that my solution has some accessibility and SEO related issues. Yes, that's right, this example has these issues. But I've only extracted the relevant part of my code. Within the application both do not really matter: the page is rendered on the backend so that it works without Javascript, the shadow element is not present there. It then "progressively enhances" and completely re-renders. The "shadow element" might then still be a problem for clever search engines but I guess there's an attribute that would mark this as SEO-unrelated. The accessibility issue is a good point but I'm sure there is some ARIA attribute that mutes this element.

But since I'd like to get rid of the shadow element anyway all this is not that interesting, is it?

Answers:

Answer

I see the challenge with what you're trying to accomplish, but there are clearly issues (as I'm sure you know) with your current solution. Namely, accessibility problems (because the "shadow element" is hidden visually but not aurally), SEO issues (because you're duplicating content you can get blacklisted), as well as readability/maintainably issues with your code and the content itself.

I recommend the following solution. The trick here is that you actually remove the description class, get the height of the element and then add the description class back. This all happens so quickly, there's no chance for the browser to repaint, and therefore, the user sees nothing (unless they happen to be watching that element in the developer tools).

I found that, much like you've done initially, this requires a couple of milliseconds in a timeout before passing the height into the element after adding the description class back. Nonetheless, it feels very responsive and it works well.

Here's a fiddle. (note that I added the inline width to your control just for testing so I didn't have to keep making the window small).

HTML:

<a href="javascript: void(0);">open</a>
<div id="control">
    <div id="description" class="description">lorem ipsum dolor sit amet lorem ipsum dolor sit amet lorem ipsum dolor sit amet</div>
</div>

CSS: NOTE: I moved the line-height: 25px into the containing element so that when the description class is removed the height is still calculated based on the line-height.

#control {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    line-height: 25px;
    background-color: rgba(0,0,0, 0.05);
}

.description {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    background-color: rgba(0,0,0, 0.2);
    color: rgb(255,255,255);
    padding: 0 10px;
    height: 25px;
    transition: height .5s ease;
    text-align: center;
}

.description.expand {
    overflow: hidden;
    text-overflow: initial;
    white-space: normal;
}

JS:

$(function(){
    $("a").on("click", function(){
        $el = $("#description");      
        if ($el.hasClass("expand")) {
            $el.css({height: ""});

            setTimeout(function(){
                $el.removeClass("expand");
            }, 200);
        } else {

            $el.removeClass("description");
            var h = $el.css("height");
            $el.addClass("description");
            setTimeout(function(){
                $el.addClass("expand").css("height", h);
            }, 200);
        }
    });
});

I realize this isn't really answering your question: is there a JS-less way. The answer is really no, not that I can think of with your implementation or to produce the exact results you want. Plus, AFAIK, you'd have to have a click handler no matter what, so in that regard, you have to have JS involved. Nonetheless, this is a cleaner, easier to read/maintain approach that solves some of the issue with your current implementation.

Answer

The problem is that you cannot do a transition to height:auto; Try the following (uses the least amount of JS possible, to my opinion): http://jsfiddle.net/ywQTd/2/

My approach at first reads the height of the element in expanded state and then immediately puts it back to shrinked state. Then on each click at first the class "expand" is toggled, which controls the values for word-wrap and so on. And then the height is animated by setting a value with $().css().
CSS:

  #control {
        position: fixed;
        bottom: 0;
        left: 0;
        right: 0;

        background-color: rgba(0,0,0, 0.05);
    }

    .description {
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        background-color: rgba(0,0,0, 0.2);
        color: rgb(255,255,255);
        padding: 0 10px;

        line-height: 25px;

        text-align: center;
    }

    #description {
        transition: height 0.5s ease;
    }

    #description.expand {
        overflow: hidden;
        text-overflow: initial;
        white-space: normal;            
    }


JQuery Code:

$(document).ready(function(){
    var $el = $('#description'), 
        height = $el.outerHeight() + 'px', //get height in expanded state
        i=0, //initialize toggle variable
        initheight = 25 + 'px'; //sets the initial height
    $el.removeClass('expand').css('height', initheight);
    $("a").on("click", function(){
        //toggles the expand class, then sets height to either the height in
        //expanded state or 25 px (i++%2) acts as a toggle expresssion
        //(returns 25 at first click, height at second, 25 at third and so on)
        $el.toggleClass('expand').css('height', (i++%2) ? initheight : height);
    });
});

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us Javascript

©2020 All rights reserved.