Why doesn't a css transition fire immediately after display change - javascript

TL;DR A css transition on opacity does not work immediately after display change, but works with setTimeout(.., 100). Why?
What do I want?
I want to flash a message for a couple of seconds and then fade it out. Seems pretty basic, right?
What do I have?
Well, here's a jsfiddle, but let me explain in detail.
Say I have a message block
<div id="message" class="message">
Here be dragons
</div>
Which starts hidden but opaque
.message {
opacity: 1;
display: none;
}
Once I've prepared my message I want to show it.
document.getElementById("message").style.display = "block"
Now I want the message to fade out so I added a simple transition on opacity.
.flash {
opacity: 0;
transition: opacity 2s ease-out 1s;
}
Which I apply with the following
document.getElementById("message").classList.add("flash")
What goes wrong?
The message div is shown but it stays invisible as the opacity: 0 immediately applies. Besides, the transitionend event is not firing, which makes me think the transition does not happen at all for some reason. Weird, right?
However, everything's fine once I add the timeout
document.getElementById("message").style.display = "block"
setTimeout(() => (document.getElementById("message").classList.add("flash")), 100)
That works but seems like a totally dirty hack. Why is it like this?
You can see this behaviour on jsfiddle with two buttons aptly named 'Working' and 'Not working';

There are two things that together cause this:
When items have display: none the opacity is ignored (as not relevant). And so when you apply display: block to them they render the provided opacity with the current value without any transition effect.
Changes you apply to the style attribute all apply together at the moment a paint (asynchronous) happens, and so the transition definition comes too late.
First, make sure to set the transition effect definition before the actual application of it, in the message CSS class.
I would then suggest using height instead of display to get the same effect. You would need to switch the border on and off also (through its width):
document.getElementById("working").addEventListener("click" , () => {
document.getElementById("message").classList.add("flash");
})
// reset
document.getElementById("message").addEventListener("transitionend" , () => {
document.getElementById("message").classList.remove("flash")
})
.message {
border: solid 0px;
background: grey;
opacity: 1;
transition: opacity 2s ease-out 1s;
overflow: hidden;
height: 0;
}
.flash {
height: 100%;
opacity: 0;
border-width: 1px
}
<div id="message" class="message">
Here be dragons
</div>
<button id="working"> Working </button>

Why it doesn't work is because you are applying display:block and opactiy: 0 at the same time. When you set the attribute display in css it ignores all transition, there has to be an event in between setting display and transitions. An alternative is using visibility:hidden and visibility:visible instead of display but note that this only hides the element and the element is still present in its position

Related

Transition elements below a transitioned v-if

I have two elements, and the top one's visibility is controlled by a v-if on a simple boolean.
transition(name="fade")
#element1(v-if="showFirst")
p Foo
#element2
p Bar
The first element is wrapped in a <transition> tag, exactly as per the Vue documentation.
However, while this does create a fading animation, the rest of the content on the page still jumps very jarringly.
How can I create a transition that will also smoothly transform the position of any and all siblings that follow?
A fiddle demoing this issue.
You need to use a transition-group and key your dynamic div and static div
<transition-group name="fade">
<div v-if="switc" key="dynamic" class="animated">
...
</div>
<div key="main-content" class="animated">
...
</div>
</transition-group>
And use this css classes
.fade-enter,
.fade-leave-active {
opacity: 0;
transform: translateY(-10px);
}
.fade-leave-active {
position: absolute;
}
.animated {
transition: all 0.5s;
/*display: flex;*/
width: 100%;
}
The real trick is to change position to absolute when leaving, then any other content can take correct position.
To know more about how Vue animate things please see this FLIP explanation post
And please see this working fiddle
https://jsfiddle.net/bjfhth7c/4/
Edit
By mistake I did set display: flex; in .animated class, that was causing to every inner element to render in a strange way.
So now, I completely remove .animate class, and instead apply transition: all 0.5s and width:100% to every direct inner element of .wrapper
My final scss looks like this:
.wrapper {
display: flex;
flex-direction: column;
>* {
transition: all 0.5s;
width:100%;
};
}
.fade-enter,
.fade-leave-active {
opacity: 0;
transform: translateY(-10px);
}
.fade-leave-active {
position: absolute;
}
Flex layout is a extend subject, but in short for this particular case flex-direction: column is arranging elements one bellows previous one.
If one of those elements has absolute position will be ignored in flex layout so any other elements will be redistributed on available space.
Please see this guide about flexbox and last working fiddle hope it helps.
You can use a slideDown/slideUp animation instead. For achieve this you don't need to know a height of a sliding element, the principles of max-height transition explained there.
So, as a result it will cause animated moving of elements below target.
Check out my example based on your fiddle.
vue js provides different transition classes, you have to use those properly to smooth the transition, I have tried with your example in this fiddle with some CSS, have a look.
.fade-enter-active, .fade-leave-active {
transition: all .5s;
height: 100px;
opacity: 0.5;
}
.fade-enter, .fade-leave-to /* .fade-leave-active in <2.1.8 */ {
height: 0px;
opacity: 0;
}
Some details from documentation:
There are six classes applied for enter/leave transitions.
v-enter: Starting state for enter. Added before element is inserted, removed one frame after element is inserted.
v-enter-active: Active state for enter. Applied during the entire entering phase. Added before element is inserted, removed when transition/animation finishes. This class can be used to define the duration, delay and easing curve for the entering transition.
v-enter-to: Only available in versions >=2.1.8. Ending state for enter. Added one frame after element is inserted (at the same time v-enter is removed), removed when transition/animation finishes.
v-leave: Starting state for leave. Added immediately when a leaving transition is triggered, removed after one frame.
v-leave-active: Active state for leave. Applied during the entire leaving phase. Added immediately when leave transition is triggered, removed when the transition/animation finishes. This class can be used to define the duration, delay and easing curve for the leaving transition.
v-leave-to: Only available in versions >=2.1.8. Ending state for leave. Added one frame after a leaving transition is triggered (at the same time 7. v-leave is removed), removed when the transition/animation finishes.
You can as well use CSS animations where you can provide on different phases of transition what will be your css property to make your transitions more smooth, like following and demo fiddle:
.fade-enter-active {
animation: bounce-in .5s;
}
.fade-leave-active {
animation: bounce-out .5s;
}
#keyframes bounce-in {
0% {
height: 5px;
}
30% {
height: 30px;
}
50% {
height: 50px;
}
100% {
height: 100px;
}
}
#keyframes bounce-out {
0% {
height: 90px;
}
50% {
height: 50px;
}
100% {
height: 0px;
}
}

Ajax image page transition

I just saw a really cool page transition on Behance where you click a image and it just expands to a new div (I think) with a with of 50%. Can someone explain to me how to make this work or have an example? Transition here:
https://vimeo.com/162486588
You can use css transitions if you like. Check the example that I wrote for you.
.normal {
float: left;
margin-right: 10px;
width: 10%;
height: auto;
transition: width 2s, height 2s;
}
.transition {
width: 50%;
height: auto;
}
Basically they work by defining 'start' values (in our case width & height inside .normal) and also a definition of how to make the transition and what properties to apply it to (in our case width and height with 2s duration each).
If you now add a class to the element that has the properties with different values (in our case .transition), they'll be animated to the new value.
To complete the example, I also added some text that is faded in after the transition has been completed.
The javascript part is fairly simple: When clicking the image, add the .transition class, then wait for 2 seconds (the transition duration) and finally fade in the text.
$('img').click(function() {
$(this).addClass('transition');
setTimeout(function() {
$('.text').fadeIn();
}, 2000);
});

How to prevent transition to run on page load after height is set with javascript in Safari?

I have a div on a page with liquid height that i want to animate with CSS transitions to collapse/expand.
I set the default height of the div using JS, so if i change the height with CSS, it can easily revert back to the original state. Works fine, the issue is that the height animation will run on page load in Safari. (works fine in Chrome) Any idea how to fix this?
CSS:
div {
background: red;
transition: all 1s cubic-bezier(0.77, 0, 0.175, 1) 0s;
overflow: hidden;
}
div.hide {
height:10px !important;
}
JS:
$div = $('div');
$div.height($div.height());
$div.click(function(){
$div.toggleClass('hide');
});
Demo:
https://jsfiddle.net/69taau5m/1/
It might be a little hacky but you could always apply the transition to your div on click as well.
Did this pretty quick but it works. Check out the fiddle. Could always add some logic to only apply css on the first click.

Disable JQuery automatic animation

Sometimes when using JQuery to set the css attribute of an element such as "height", "max-height", it automatically binds animation to the change. Sometimes it is awesome, but it is not always necessary. Is there a way to disable this kind of animation?
Actually what is the exact situation that causes JQuery to automatically bind animations? because I don't always see this kind of behavior. I am not using JQuery-UI.
Perhaps the element you are changing the height of has a css transition property that is responsibe for the animation.
$(function() {
$('.myClass').css('width', '100px');
});
.myClass {
height: 50px;
width: 300px;
background-color: red;
}
.transition {
transition: width 3s;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
with transition
<div class="myClass transition">
</div>
without transition
<div class="myClass">
</div>
Borrowing from What is the cleanest way to disable CSS transition effects temporarily?
You can then create a class that will override the transition property and toggle that class
.notransition {
-webkit-transition: none !important;
-moz-transition: none !important;
-o-transition: none !important;
-ms-transition: none !important;
transition: none !important;
}
Note
If you go this route, you may run into the issue of needing to reflow the css
From What is the cleanest way to disable CSS transition effects temporarily? once again:
There are various ways to do this - see here for some. The closest
thing there is to a 'standard' way of doing this is to read the
offsetHeight property of the element.
One solution that actually works, then, is
$someElement.addClass('notransition'); // Disable transitions
doWhateverCssChangesYouWant($someElement);
$someElement[0].offsetHeight; // Trigger a reflow, flushing the CSS
changes $someElement.removeClass('notransition'); // Re-enable
transitions
You can use jQuery.fx.off parameter to define if animation should be used. Read about it here
Also you can modify css values directly without modifying function like
$("#id").css('height', '100px');
instead of
$("#id").height(100);

Slide + fade effect using CSS transitions

I'm trying to replicate this effect using CSS effects or transitions.
Using animations I can animate the opacity, but only fadeIn, and the height (which should control the slide) doesn't seem to work at all :(
The closest I've got is by using javascript to set a temporary class on the element I want to animate, and on which I apply the initial opacity. But height doesn't work either. And there seems to be a slight delay on animation start.
Any other ideas?
So I ended up using the solution posted in the question Simon mentioned: With javascript I wrap the element I want to animate within a "wrapper" DIV on which I apply the animation. The wrapper will get its height changed from 0 to the height of the content DIV every time the label is clicked:
fiddle here
I know it requires some javascript, but the idea is to make the animation in CSS, and this is what it does. And if JS is disabled, the toggle will still work...
You can't currently animate on height when one of the heights involved is auto, you have to set two explicit heights. There's an extensive workaround posted as an answer to this similar question.
I made an alteration to your JS Fiddle, I beleive this is what you want; please see it here.
You need to specify a height on the div originally (0) and don't forget overflow:hidden; so that the content doesn't 'spil out' of the div. You will still need jQuery / Javascript however, to toggle a class but it means much less Javascript is required. (I toggled the class "change" which you will see on that fiddle)
input {
display:none;
}
label {
display:inline-block;
}
div {
white-space: pre;
background: #eee;
color: #333;
overflow:hidden;
height:0;
opacity:0;
-moz-transition:height 1s opacity 1s;
-webkit-transition:height 1s opacity 1s;
-o-transition:height 1s opacity 1s;
-ms-transition:height 1s opacity 1s;
transition:height 1s, opacity 1s;
}
.changed {
height:200px;
opacity: 1;
}
I added a few vendor prefixes to the transition CSS propery as I'm not sure what browser you'll be using and I'm on firefox so I need the -moz- prefix lol :)
The only problem I can see with this is that height:auto or height:100% doesn't animate, so you'll need to specify ems or px... If this is going to be a problem (like if the content will be dynamic), I would advise using jQuery for the height animation.

Categories