CSS animation - revert on mouseout - css

I'm sure this must have been asked before and I've found related questions but I can't quite seem to crack this.
I have an element which receives a class and, on doing so, expands. Later, when that class is removed, it should revert (animate) back to its original width.
let el = document.querySelector('#side-bar');
el.addEventListener('click', evt => el.classList.toggle('contracted'));
#side-bar {
height: 100%;
width: 75px;
background: red;
position: fixed;
left: 0;
top: 0;
}
#side-bar.contracted {
animation: .5s side-bar-contract forwards;
}
#side-bar:not(.contracted) {
animation: .5s side-bar-expand forwards;
}
#keyframes side-bar-expand {
to {
width: 350px;
}
}
#keyframes side-bar-contract {
to {
width: 75px;
}
}
<div id='side-bar' class='contracted'></div>
The expansion animation works fine. But the reversion animation doesn't happen; it just snaps back to its original properties, no anim.
Fiddle
What am I doing wrong?
[ EDIT ]
OK I should obviously have mentioned why I'm not doing this with transition. This is part of a wider set of dependent animations which run in a sequence, one after another. My understanding is that this sort of chronologically non-trivial situation is better for animation rather than transition.

UPDATE: (Removing the animation at the beginning)
let init = 0,
el = document.querySelector('#side-bar');
el.addEventListener('click', function() {
if (init < 1) {
init++;
el.classList.remove("init");
el.classList.add('contracted');
}
el.classList.toggle('contracted');
});
#side-bar {
height: 100%;
width: 75px;
background: #d4653c;
position: fixed;
left: 0;
top: 0;
padding: .8rem;
}
#side-bar:not(.init) {
animation: .5s side-bar-expand forwards;
}
#side-bar.contracted {
animation: .5s side-bar-contract forwards;
}
#keyframes side-bar-expand {
to {
width: 350px;
}
}
#keyframes side-bar-contract {
from {
width: 350px;
}
}
<div id='side-bar' class='init'>Click me</div>
Just change to to from in side-bar-contract
#keyframes side-bar-expand { to { width: 350px; } }
#keyframes side-bar-contract { from { width: 350px; } }
let el = document.querySelector('#side-bar');
el.addEventListener('click', evt => el.classList.toggle('contracted'));
#side-bar {
height: 100%;
width: 75px;
background: #d4653c;
position: fixed;
left: 0;
top: 0;
padding: .8rem;
}
#side-bar:not(.contracted) {
animation: .5s side-bar-expand forwards;
}
#side-bar.contracted {
animation: .5s side-bar-contract forwards;
}
#keyframes side-bar-expand {
to {
width: 350px;
}
}
#keyframes side-bar-contract {
from {
width: 350px;
}
}
<div id='side-bar' class='contracted'>Click me</div>

Why not just use a transition animation:
let el = document.querySelector('#side-bar');
el.addEventListener('click', evt => el.classList.toggle('contracted'));
#side-bar {
height: 100%;
width: 350px; /* have width at 350px when not contracted */
background: #d4653c;
position: fixed;
left: 0;
top: 0;
padding: .8rem;
transition: width .5s; /* animate the width */
}
#side-bar.contracted {
width: 75px;
}
<div id='side-bar' class='contracted'>Click me</div>
If you need to use keyframes then you need to start the second one off at 350px - you start it at 75 to 75 which is why it doesn't animate:
let el = document.querySelector('#side-bar');
el.addEventListener('click', evt => el.classList.toggle('contracted'));
#side-bar {
height: 100%;
width: 75px;
background: #d4653c;
position: fixed;
left: 0;
top: 0;
padding: .8rem;
}
#side-bar:not(.contracted) {
animation: .5s side-bar-expand forwards;
}
#side-bar.contracted {
animation: .5s side-bar-contract forwards;
}
#keyframes side-bar-expand {
to {
width: 350px;
}
}
#keyframes side-bar-contract {
0% {
width: 350px;
}
100% {
width: 75px;
}
}
<div id='side-bar' class='contracted'>Click me</div>

First, I would recommend you do this with hover styles and css transition instead of an animation for something as simple as animating a single property.
.class {
width: 400px;
transition: width 1500ms ease-in-out;
}
.class:hover {
width: 100px;
}
CSS transition will actually stop part way through the transition and reverse to the initial size for you.
Second, I would recommend that you do not animate or transition the width property in CSS. Here's a great article about what properties you should avoid animating.
If you need to delay a transition from happening on other elements, you can use the transition-delay property. This property can also be applied in hover effects... including with hover effects on parent elements. So you may potentially have multiple hover effects in play at a given time to accomplish your desired effect.

Related

Collapsing a div with Animation: how to improve this code?

I'm trying to make a div that appear and disappear on touch, like the navigation bar of android phones.
Should I use transition for this or is animation ok? In the fiddle example i use the mouse click and the setTimeout to simulate the touches and the auto disappear if you dont touch the screen for some seconds.
.custom-row{
position: fixed;
width: 100%;
height: 50px;
bottom: -100px;
left: 0px;
background-color: yellow;
opacity: 0;
}
.slidein {
animation: slidein 1s ease-in forwards;
}
.slideout {
animation: slideout 1s ease-in forwards;
}
#keyframes slidein {
0% {
}
100% {
bottom: 0px;
opacity: 1;
}
}
#keyframes slideout {
0% {
bottom: 0px;
opacity: 1;
}
100% {
bottom: -100px;
opacity: 0;
}
}
https://jsfiddle.net/1rm64q8z/1/
For this use case, transition seems to be a better solution. With animation, alerting position is a compute-intensive approach. The CSS will also be much more readable and scalable with transitions in this case.
const bar = document.getElementById("bottom-bar");
bar.addEventListener("click", (el) => {
el.target.classList.toggle("slide-out");
setTimeout(() => {
el.target.classList.toggle("slide-out");
el.target.classList.toggle("slide-in");
}, 2000)
})
body {
overflow: hidden;
}
#bottom-bar {
position: absolute;
bottom: 0px;
left: 0px;
width: 100%;
background: yellow;
padding: 16px;
text-align: center;
transform-origin: bottom;
transition: transform 0.4s ease-in-out;
}
.slide-in {
transform: translateY(0%);
}
.slide-out {
transform: translateY(100%);
}
<div id="bottom-bar">
Hello
</div>
The performance of CSS transitions and animations should be almost the same as they are both hardware accelerated so on most modern browsers the behaviour should be the same.
Animations are often used to create a more complex series of movements and they do not lift the rendering process to the GPU and resulting in being slower than transitions.
This article gives a great breakdown of when to use animations vs transitions.

VueJs no animation trigger on variable classname

I have a VueJs component and 2 classes with animations.
I have a v-bind:class on a div, but when the class changes, the animation doesn't run.
When the update_page computed property changes, the class changes as well.
What am I doing wrong?
The div:
<div
class="top-0 right-0 bottom-0
absolute h-full bg-primary"
:class="update_page ? 'detail-header-hide' : 'detail-header'"
></div>
The css class:
.detail-header {
width: 0;
animation: fadeIn 1500ms;
}
.detail-header-hide {
width: 100%;
animation: fadeIn 1500ms;
animation-direction: reverse;
}
#keyframes fadeIn {
0% {
width: 100%;
}
60% {
width: 100%;
}
100% {
width: 0;
}
}
I know snippet looks bad, but i mean problem will be at reusing one animation. Try to add one more to your project like me at snippet ('fadeOut')
let x = document.querySelector('div')
function toggle(){
x.classList.toggle('detail-header-hide')
x.classList.toggle('detail-header')
}
.detail-header {
width: 0;
animation: fadeIn 1500ms;
}
.detail-header-hide {
width: 100%;
animation: fadeOut 1500ms;
}
#keyframes fadeIn {
0% {
width: 100%;
}
60% {
width: 100%;
}
100% {
width: 0;
}
}
#keyframes fadeOut {
0% {
width: 0%;
}
60% {
width: 0%;
}
100% {
width: 100%;
}
}
<div
class="detail-header-hide"
>Some text Some text Some text Some text Some text</div>
<button onClick='toggle()'> btn </button>

How to make CSS animation to do vice versa after being completed?

The code below is a part of my code :
.myBox:hover::after {
animation-name: underline;
animation-duration: 350ms;
animation-fill-mode: forwards;
}
#keyframes underline {
from { width: 0; }
to { width: 100%; }
}
It works nicley, but I want to do it vice versa when animation completed, I mean when it finished then width should be 0 again, In fact for this part I want to do it when my element is not hovered. Which property can help me ?
You need to use alternate and run 2 iterations of the animation:
.box {
height:200px;
background:red;
animation: underline 500ms alternate 2 forwards;
}
#keyframes underline {
from { width: 0; }
to { width: 100%; }
}
<div class="box">
</div>
Or consider the use of transition if you want the effect on hover:
.box {
height: 200px;
background: red;
width: 0;
transition: 500ms;
}
body:hover .box {
width: 100%;
}
<div class="box">
</div>
You can specify multiple values for animations rather then from and to using percentage:
#keyframes underline {
0%, 100% { width: 0; }
50% { width: 100%; }
}
More detailed information can be found here.
.myBox:hover::after {
animation-name: underline infinite;
animation-duration: 350ms;
animation-fill-mode: forwards;
}
#keyframes underline {
from { width: 0; }
to { width: 100%; }
}
You infinite for this

CSS - How to reverse the Animation on removal

I have a page in my website where I display to panels, side by side. I'm displaying these 2 panels in 2 views: Horizontal and Vertical. I have a button that switches between these 2 views. I'm trying to add some CSS animation on the transition between the two views. However my animation work only in one direction (from Vertical to Horizontal), the reverse animation appear in the wrong order.
var isVertical = false;
var boxes = $(".box");
function toggleViews()
{
isVertical = !isVertical;
if (isVertical)
{
boxes.addClass("vertical-box");
}
else
{
boxes.removeClass("vertical-box");
}
}
.container
{
display: block;
width: 400px;
height: 150px;
border: 2px solid black;
overflow: hidden;
}
.box
{
-webkit-transition-property: width, height;
-webkit-transition-duration: 2s, 2s;
-webkit-transition-delay: 0s, 2s;
-webkit-transition-timing-function: ease;
display: inline-block;
width: 50%;
height: 100%;
}
.vertical-box
{
width: 100%;
height: 50%;
}
.a { background-color: darkred; }
.b { background-color: darkorchid; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html>
<body>
<button onclick="toggleViews()">toggle</button>
<div class="container">
<div class="a box">A</div><div class="b box">B</div>
</div>
</body>
</html>
var isVertical = false;
var boxes = $(".box");
function toggleViews()
{
isVertical = !isVertical;
if (isVertical)
{
boxes.addClass("vertical-box");
}
else
{
boxes.removeClass("vertical-box");
}
}
.container
{
display: block;
width: 400px;
height: 150px;
border: 2px solid black;
overflow: hidden;
}
.box
{
-webkit-transition-property: height, width; /* swapped */
-webkit-transition-duration: 0.5s, 0.5s;
-webkit-transition-delay: 0s, 0.5s;
-webkit-transition-timing-function: ease;
display: block; /* TRY THIS */
float: left; /* AND THIS */
width: 50%;
height: 100%;
}
.vertical-box
{
-webkit-transition-property: width, height; /* added */
width: 100%;
height: 50%;
}
.a { background-color: darkred; }
.b { background-color: darkorchid; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html>
<body>
<button onclick="toggleViews()">toggle</button>
<div class="container">
<div class="a box">A</div><div class="b box">B</div>
</div>
</body>
</html>
Explained
Added transition-property: width, height; to .vertical-box
Desired behavior: expand width, shink height; expand height shrink width.
.box has transition-property first height then width
.vertical-box overwrites and flippes transition-property: first width, then height
You might think this is the wrong order, but as soon as you click the class is applied immideately, but the transition takes time. So you transition from .box to .vertical-box with the transition-property of .vertical-box and vise versa.
EDIT Answer using animation (little hacky, since i found no way to reset current keyframe)
var isVertical = false;
var boxes = $(".box");
function toggleViews()
{
isVertical = !isVertical;
if (isVertical)
{
boxes.removeClass("vertical-box-reverse");
window.requestAnimationFrame(function() { // apply to forget animation state
window.requestAnimationFrame(function() { // re-apply animation
boxes.addClass("vertical-box");
});
});
}
else
{
boxes.removeClass("vertical-box");
window.requestAnimationFrame(function() { // apply to forget animation state
boxes.addClass("vertical-box-before-reverse"); // apply to set animation end-like state
window.requestAnimationFrame(function() { // re-apply animation
boxes.removeClass("vertical-box-before-reverse");
boxes.addClass("vertical-box-reverse");
});
});
}
}
.container
{
display: block;
width: 400px;
height: 150px;
border: 2px solid black;
overflow: hidden;
}
.box
{
display: block;
float: left;
width: 50%;
height: 100%;
}
.a.vertical-box { animation: boxAnimationA 1s normal forwards; }
.b.vertical-box { animation: boxAnimationB 1s normal forwards; }
.a.vertical-box-reverse { animation: boxAnimationA 1s ease-in reverse forwards; }
.b.vertical-box-reverse { animation: boxAnimationB 1s ease-in reverse forwards; }
.vertical-box-before-reverse { width: 100%; height: 50%; }
.a { background-color: darkred; }
.b { background-color: darkorchid; }
/* Keyframes */
#keyframes boxAnimationA {
0% { width: 50%; }
50% { width: 100%; height: 100%; }
100% { width: 100%; height: 50%; }
}
#keyframes boxAnimationB {
0% { width: 50%; }
50% { width: 0%; height: 100%; }
51% { width: 100%; height: 100%; }
100% { width: 100%; height: 50%; }
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html>
<body>
<button onclick="toggleViews()">toggle</button>
<div class="container">
<div class="a box">A</div><div class="b box">B</div>
</div>
</body>
</html>

Transitioning from animated to not animated in Chrome vs Firefox

Firefox has a nice behavior when turning off animation in a transition enabled element, it takes the element wherever it is and transition back to original form.
In Chrome it just jumps without transitioning.
Why the inconsistency? Is there any way to replicate in Chrome without using too much JS?
.wrapper {
width: 400px;
height: 200px;
}
.move {
width: 100px;
height: 100px;
position: absolute;
background-color: #f66;
transition: 1s;
cursor: pointer;
}
.move {
animation: move 2s linear infinite;
}
.wrapper:hover .move {
animation: none;
}
#keyframes move {
50% {
transform: translateX(200px);
}
}
<div class="wrapper">
<div class="move"></div>
</div>
$(".spinny").bind("webkitAnimationEnd mozAnimationEnd animationEnd", function(){
$(this).removeClass("spin")
})
$(".spinny").hover(function(){
$(this).addClass("spin");
})
div {
width: 100px;
height: 100px;
background-color: #f66;
transition: 1s;
cursor: pointer;
}
.spin {
animation: spin 1s linear 1;
}
#keyframes spin {
100% {
transform: rotate(180deg);
}
}
<script src="https://code.jquery.com/jquery-3.1.0.js"></script><div class="spinny"></div>
Taken from this answer, JS can be used to add and remove classes on hover and animation finish, respectively. jQuery is used in this example, but it is not necessary for the functionality.
EDIT: Now without jQuery, this will play the animation whenever hovered over by remembering the state and checking after the end of every animation.
hover = 0;
s = document.getElementById("spinny");
s.addEventListener("animationend", function(){
s.className = "";
if (hover)
setTimeout(function(){s.className = "spin"},0);
})
s.onmouseover = function(){
s.className = "spin";
hover = 1;
}
s.onmouseout = function(){
hover = 0;
}
div {
width: 100px;
height: 100px;
background-color: #f66;
transition: 1s;
cursor: pointer;
}
.spin {
animation: spin 1s linear 1;
}
#keyframes spin {
100% {
transform: rotate(180deg);
}
}
<div id="spinny"></div>
Add transition along side your transform
.rotate {
width: 200px;
height: 200px;
transform: rotate(0deg);
transition: 0.5s ease all;
background-color: #222;
}
.rotate:hover {
transform: rotate(20deg);
transition: 0.5s ease all;
}
This should prevent it from jumping.

Resources