SweetTomato
SweetTomato

Reputation: 569

CSS Transition on Target's Toggled ClassList Not Working

I want the height to expand smoothly, but 95% of the time, the transition doesn't happen. Is this because the classList.toggle creates a new class string, and therefore the transition is lost? Either way, how do I resolve this? I must use vanilla JS for this.

document.querySelectorAll('.toggleMe').forEach(function (query) {
    query.onclick = function (e) {
        e.target.classList.toggle('open');
    }
});
.toggleMe {
    background: #e3e3e3;
    max-height: 1.3rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    width: 100%;
    
    transition: all ease 2s;
    -moz-transition: all ease 2s; /* Firefox 4 */
    -webkit-transition: all ease 2s; /* Safari and Chrome */
    -o-transition: all ease 2s; /* Opera */
    -ms-transition: all ease 2s; /* Explorer 10 */
}

.toggleMe.open {
    max-height: 999px;
    white-space: normal;
}
<div class="toggleMe">I am a really long sentence that wants to stay hidden on load, but when clicked will expand for all to see. But 95% of the time, the transition doesn't happen. Is this because the classList.toggle creates a new class string, and therefore the transition is lost?</div>

Upvotes: 2

Views: 736

Answers (4)

greim
greim

Reputation: 9437

Think of the max-height setting as a window/viewport kind of thing which opens and closes vertically in an animated way.

Meanwhile, the white-space setting causes the paragraph to increase or decrease in height instantly, without animation.

When the paragraph instantly increases in height, you only see the animation if the window starts out in a fully closed position, because as the window begins to open, it reveals the paragraph as it goes.

When you collapse, the paragraph collapses instantly, but it takes a full 2 seconds for the max-height window to close. It isn't obvious because all of that time is spent traversing almost 999px of imaginary real estate.

So, you must wait the full 2 seconds for that window to close, in order to see an expand animation the next time. Otherwise, if you don't wait, the window is still partly open, and it begins to open again without first settling to its minimum height. Thus, it fails to re-create the animation.

I assume that the reason it seems to intermittently fail, is that during your testing you were waiting varying amounts of time between expand, collapse, and re-expand.

I hope that makes sense. It's definitely a bit tricky to create an expand/collapse effect on text which has unpredictable height. If you reduce the max-height: 999px setting, it could minimize the problem. But then it might cut off bigger paragraphs :/

Upvotes: 3

Kouta Nakano
Kouta Nakano

Reputation: 643

Problem

These below are the problems you are facing in your code.

transition duration set to 2s prevents animations in the next 2s.

If you click the element before the animation ends in 2s, you will not be able to see next animation correctly.

white-space: nowrap; prevents closing animation from occuring.

When you take off .open from the element class by toggling, white-space: nowrap instantly comes into effect, which causes an immediate height change without animation.

It is because the content does not exit to fill the decreasing height by animation.

Solution

  • set the transition duration to a shorter length, such as 0.2s ~ 1s.
  • stop using white-space: nowrap

If you really want the text ellipsis effect, you can show it by pseudo element like ::after.

    .toggleMe {
        background: #e3e3e3;
        overflow: hidden;
        text-overflow: ellipsis;
        line-height: 18px;
        position: relative;
        max-height: 18px;
        padding-right: 2px;
        width: 100%;
        cursor: pointer;

        transition: all ease 0.2s;
        -moz-transition: all ease 0.2s;
        /* Firefox 4 */
        -webkit-transition: all ease 0.2s;
        /* Safari and Chrome */
        -o-transition: all ease 0.2s;
        /* Opera */
        -ms-transition: all ease 0.2s;
        /* Explorer 10 */
    }

    .toggleMe.open {
        max-height: 999px;
    }

    .toggleMe::after {
        content: '...';
        position: absolute;
        right: 4px;
        top: 0;
    }

    .toggleMe.open::after {
        display: none;
    }

Upvotes: 2

Mister Jojo
Mister Jojo

Reputation: 22386

you can use arrow function to ignore event target

document
  .querySelectorAll('.toggleMe')
  .forEach( el => el.onclick = _ => el.classList.toggle('open') )

Upvotes: 1

Lucas Coutinho
Lucas Coutinho

Reputation: 21

Try use e.currentTarget instead e.target

document.querySelectorAll('.toggleMe').forEach(function (query) {
    query.onclick = function (e) {
        e.currentTarget.classList.toggle('open');
    }
});
.toggleMe {
    background: #e3e3e3;
    max-height: 1.3rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    width: 100%;
    
    transition: all ease 2s;
    -moz-transition: all ease 2s; /* Firefox 4 */
    -webkit-transition: all ease 2s; /* Safari and Chrome */
    -o-transition: all ease 2s; /* Opera */
    -ms-transition: all ease 2s; /* Explorer 10 */
}

.toggleMe.open {
    max-height: 999px;
    white-space: normal;
}
<div class="toggleMe">I am a really long sentence that wants to stay hidden on load, but when clicked will expand for all to see. But 95% of the time, the transition doesn't happen. Is this because the classList.toggle creates a new class string, and therefore the transition is lost?</div>

Upvotes: 1

Related Questions