CSS - How to reverse the Animation on removal - css

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>

Related

Apply transformation to CSS element after previous transformation ended

I am trying to chain together 2 transforms, but I want the 2nd one to begin after the 1st one ends.
This is how I am trying to do it:
.trailingNote {
position:absolute;
bottom:0px;
background:black;
width:20px;
transition: transform 5s;
transition-timing-function: linear;
}
.trailingNote-moveUp{
transform: scaleY(10) translateY(-200px);
}
Basically, I want the element to be scaled by 10 on the y axis, then, after scaleY ends, start translateY(-200px) to move the scaled element up.
Link to CodePen: https://codepen.io/Sederfo/pen/abqOoOP
Use CSS keyframes
function startAnimation() {
var element = document.getElementById("x");
element.classList.add("trailingNote-moveUp");
}
.trailingNote {
position:absolute;
bottom:0px;
background:black;
width: 20px;
}
.trailingNote:hover, .trailingNote-moveUp {
animation: animate 5s linear forwards;
}
#keyframes animate {
0% {
transform: none;
}
50% {
transform: scaleY(10);
}
100% {
transform: scaleY(10) translateY(-200px);
}
}
<div id="x" class="trailingNote">Note</div>
<button onclick="startAnimation()">Animate</button>
You can use something like this.
const box = document.getElementById('box');
const button = document.querySelector('button');
button.addEventListener('click', () =>{
box.classList.add('transform-1');
box.addEventListener('transitionend', () =>{
setTimeout(function (){
box.classList.add('transform-2');
},1000)
})
});
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
min-height: 100vh;
margin: 0;
background-color: bisque;
display: grid;
place-content: center;
}
button{
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
}
.box{
width: 5rem;
height: 5rem;
background-color: brown;
transition: all 1s linear;
}
.transform-1{
transform: scaleY(5);
}
.transform-2{
transform: translateY(-2000px);
}
<div class="box" id="box"></div>
<button>Click me</button>

CSS animation - revert on mouseout

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.

CSS classes conflicting

I am trying to animate a card flipping face up and then fading out. I do this by adding a class 'flipped' on click and a second 'vanish' after a timeout of 2 seconds. However, as soon as the 'vanish' class is added, the card flips back face down. I don't understand why, as the 'flipped' class is still applied.
Here is my mark up:
<div class="grid-space">
<div class="card">
<div class="front-face">
<img src="https://res.cloudinary.com/lwcqviihu/image/upload/v1512898858/Animals/Sloth_svg.svg"/>
<p>sloth</p>
</div>
<div class="back-face"></div>
</div>
</div>
The CSS (flipped and vanish classes marked)
body {
background: #333;
}
.grid-space {
perspective: 1000;
width: 200px;
margin: auto;
}
.grid-space:hover {
transform: scale(1.02);
transition: 0.3s ease-in-out;
}
.card {
position: relative;
padding-bottom: 100%;
display: flex;
border-radius: 1vw;
transition: transform 0.4s ease-in-out, opacity 2s ease-in-out;
transform-style: preserve-3d;
cursor: pointer;
}
.card p {
color: inherit;
}
/*****These are the classes applied to do the animation***********/
.flipped {
transform: rotateY(180deg);
}
.vanish {
opacity: 0;
}
/*****END**********************************************************/
.front-face, .back-face {
backface-visibility: hidden;
position: absolute;
top: 0;
left: 0;
position: absolute;
width: 100%;
height: 100%;
border-radius: 1vw;
text-align: center;
box-sizing: border-box;
}
.front-face {
transform: rotateY(180deg);
color: #EDCB7A;
background: #487360;
border-style: solid;
border-width: 2px;
}
.back-face {
/* background: #C7C6C4;
border: 1px solid #EBD787; */
background: #3A295C;
border: 1px #EBD787 solid;
z-index: 10;
}
.front-face > p {
font-size: 3vmin;
margin: 0;
position: absolute;
bottom: 5%;
width: 100%;
text-align: center;
}
.front-face > img {
width: 90%;
margin-top: 5%;
}
And finally, the javascript:
window.onload = function() {
var card = document.getElementsByClassName('card')[0];
card.addEventListener('click', function() {
this.className += " flipped";
window.setTimeout(vanish, 2000);
});
function vanish() {
card.className += " vanish";
}
};
You can see the whole thing 'working' here: https://codepen.io/timsig/pen/MVavXv
Many thanks for any help.
There seems to be something odd hiding the revealed face when applying opacity to the parent.
I sinceriously don't know why that happens (if anyone has a clue, I'd really, really like to know), but an alternate approach would be to modify the faces instead of the card itself when you apply the .vanish class
.vanish > .back-face{
visibility:hidden;
}
.vanish > .front-face{
opacity:0
}
.front-face{
transition:opacity 2s ease-in-out;
}
and of course, taking out the rule that applies opacity 0 to the .card
/*.vanish {
opacity: 0;
}*/
I think I know why it's happening. When .card's opacity is being set to 0 because of .vanish, it's setting the opacity of its default state since the opacity style is being set on .card itself.
I fixed it by moving the opacity styles to .front-face since that's the side you want to fade out.
.card {
transition: transform 0.4s ease-in-out;
}
.vanish .front-face {
opacity: 0;
}
.front-face {
transition: opacity 2s ease-in-out;
}

Achieve a smooth scale transition in parent and children div simultaneously

I am trying to scale a div and make its children look like it stays in the same place while the parent scales. To achieve it I am scaling the parent, and the children inversely:
Children-scale Parent-scale
start 2 0.5 -> 2 * 0.5 = 1
middle 1.5 0.75 -> 1.5 * 0.75 = 1.125 [It should be 1]
end 1 1 -> 1
As can be seen in the demo, the initial and final values are correct but not the intermediates. At first I thought it could be the easing function, but assigning a linear (constant speed) makes no difference. Possibly assigning an inverse easing could do it, but I am asking if there is a non-mathematical way to achieve this (as I am not confident with bezier curves).
How could I achieve a smooth transition using the transform scale property?
var $div = $('div');
$div.on('click', function() {
$div.toggleClass('scaled');
});
div {
width: 100%;
height: 200px;
overflow:hidden;
}
img {
width: auto;
height: 100%;
}
div, img {
transition: transform 1s linear;/* Other easing function make no difference */
/* transform-origin:left top; Makes no difference */
}
div.scaled {
transform:scaleY(0.5);
}
div.scaled img {
transform:scaleY(2);/* 1 / 0.5 */
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span>Click the image</span>
<div><img src="http://i.imgur.com/sbyPaAsl.jpg" alt=""></div>
JSFiddle.
Edit:
Using CSS animations and assigning the correct values each x%, I get something close but still not perfect. (This also needs to know beforehand the scale value or otherwise create the animation and assign it with JavaScript.)
var $div = $('div');
$div.on('click', function() {
$div.toggleClass('scaled');
});
div {
width: 100%;
height: 200px;
overflow:hidden;
}
img {
width: auto;
height: 100%;
}
div.scaled {
animation: ddiv 1s linear;
}
div.scaled img {
animation: iimg 1s linear;
}
#keyframes iimg {
0% {transform:scaleY(2);}
20% {transform:scaleY(1.8);}
40% {transform:scaleY(1.6);}
60% {transform:scaleY(1.4);}
80% {transform:scaleY(1.2);}
100% {transform:scaleY(1);}
}
#keyframes ddiv {
0% {transform:scaleY(0.5);}
20% {transform:scaleY(0.555);}/* 1 / 1.8 */
40% {transform:scaleY(0.625);}/* 1 / 1.6 */
60% {transform:scaleY(0.714);}/* 1 / 1.4 */
80% {transform:scaleY(0.833);}/* 1 / 1.2 */
100% {transform:scaleY(1);}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span>Click the image</span>
<div><img src="http://i.imgur.com/sbyPaAsl.jpg" alt=""></div>
I am posting an answer to the question as I found a bezier curve by testing values which is close to the desired result; but I am still open to other answers if someone has a better approach.
The function is 1/x. The scale-up element needs a linear easing, and the scale-down one a cubic-bezier(.25,.48,.52,.75) (it is not the exact curve). This way the children element almost looks like it is not scaling.
JSFiddle
var $div = $('div');
$div.on('click', function() {
$div.toggleClass('scaled');
});
div {
width: 100%;
height: 200px;
overflow:hidden;
}
img {
width: auto;
height: 100%;
}
div {
transition: transform 1s linear;
}
img {
transition: transform 1s cubic-bezier(.25,.48,.52,.75);
}
div.scaled {
transition: transform 1s cubic-bezier(.25,.48,.52,.75);
transform:scaleY(0.5);
}
div.scaled img {
transition: transform 1s linear;
transform:scaleY(2);/* 1 / 0.5 */
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div><img src="http://i.imgur.com/sbyPaAsl.jpg" alt=""></div>
Would an approach using pseudoelements work for you instead?
Here's an idea:
var $div = $('div');
$div.on('click', function() {
$div.toggleClass('scaled');
});
div {
width: 100%;
height: 200px;
overflow: hidden;
position: relative;
}
img {
width: auto;
height: 100%;
}
div:after,
div:before {
content: '';
position: absolute;
height: 0;
width: 100%;
display: block;
background: white;
}
div:before {
top: 0;
}
div:after {
bottom: 0;
}
div.scaled:after,
div.scaled:before {
transition: height 1s linear;
}
div.scaled:after,
div.scaled:before {
height: 50px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span>Click the image</span>
<div><img src="http://i.imgur.com/sbyPaAsl.jpg" alt=""></div>
I dont know if it's useful for your situation or not:
But if you don't need the overflow-hidden in the container, making it and the child rotate could give you a posibility:
var $div = $('div');
$div.on('click', function() {
$div.toggleClass('scaled');
});
div {
width: 100%;
height: 200px;
border: solid 1px green;
}
img {
width: auto;
height: 50%;
top: 25%;
}
div, img {
transition: transform 1s linear;
transform-style: preserve-3d;
position: relative;
}
div.scaled {
transform: rotateX(60deg);
}
div.scaled img {
transform: rotateX(-60deg);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span>Click the image</span>
<div><img src="http://i.imgur.com/sbyPaAsl.jpg" alt=""></div>

CSS Animation: how to trigger the reverse animation?

I'm trying to create a simple reusable CSS class so I can have this animation everywhere.
Everything works fine except that I can't find any example/documentation on how to trigger the reverse animation.
Here is my HTML:
<div class="cards">
<div class="card">
<div class="frontpage">
<img src="http://lorempixel.com/400/200/"/>
</div>
<div class="rearpage">
<img src="http://lorempixel.com/g/400/200/"/>
</div>
</div>
<div class="card">
<div class="frontpage">
<img src="http://lorempixel.com/400/200/"/>
</div>
<div class="rearpage">
<img src="http://lorempixel.com/g/400/200/"/>
</div>
</div>
</div>
My animation is a "card-flip"-like animation using a simple toggleClass in Javascript to trigger the animation:
$('.card').click(function(){
$(this).toggleClass('opened');
});
And here is my CSS:
.cards {
width: 800px;
margin: auto;
}
.card {
width: 200px;
height: 100px;
position: relative;
display: inline-block;
margin: 10px;
}
.card .frontpage, .card .rearpage {
width: 100%;
height: 100%;
overflow: hidden;
position: absolute;
}
.card .rearpage {
width: 0%;
}
.card .frontpage img, .card .rearpage img {
width: 100%;
height: 100%;
}
/***** ANIMATIONS *****/
/* ANIMATION 1 */
.card .frontpage {
-webkit-animation-duration: 1s;
-webkit-animation-direction: alternate;
-webkit-animation-timing-function: ease-in;
-webkit-animation-fill-mode: forwards;
}
.card.opened .frontpage {
-webkit-animation-name: frontToRear;
}
#-webkit-keyframes frontToRear {
0% { width: 100%; }
50% { width: 0%; margin-left: 50%; }
100% { width: 0%; }
}
/* ANIMATION 2 */
.card .rearpage {
-webkit-animation-duration: 1s;
-webkit-animation-direction: alternate;
-webkit-animation-timing-function: ease-in;
-webkit-animation-fill-mode: forwards;
}
.card.opened .rearpage {
-webkit-animation-name: rearToFront;
}
#-webkit-keyframes rearToFront {
0% { width: 0%; }
50% { width: 0%; margin-left: 50%; }
100% { width: 100%; }
}
What is the smart way of doing this? I wish I could just put some trigger on my .rearcard to trigger the reversed animation but I can't find any way of doing this.
I know I could just write 2 other "reversed" animations and apply them but it seems so dumb that I can't try to do better.
I set up a jsfiddle to help you analyze and test out: http://jsfiddle.net/9yp3U/
Your approach with margin and width to fake a rotation is very interesting, but you can do this much more simply with rotateY
.cards {
width: 800px;
margin:auto;
-webkit-perspective:1000;
}
.card {
width: 200px;
height: 100px;
position: relative;
display: inline-block;
margin: 10px;
-webkit-transition: 1s ease-in;
-webkit-transform-style: preserve-3d;
-webkit-transform:translateZ(1px);
}
.card .frontpage, .card .rearpage, img {
width: 100%;
height: 100%;
position: absolute;
top:0;
left:0;
-webkit-transform-style: preserve-3d;
-webkit-backface-visibility: hidden;
}
.card img {
width: 100%;
height: 100%;
}
.card .rearpage,
.card.opened {
-webkit-transform:rotateY(180deg);
}
Demo
As for the question you asked, you can play animations backwards by using the animation-direction:backwards property, though with CSS toggling animations is hard. Thus, I'd recommend you use a transition instead since it's only a change between two states.
And FYI just in case, CSS selector don't always have to be in the parent child format. In your case applying just .child will do the same. The parent child selector is only necessary when needing to a higher selector specificity than existing properties.
Oh, and also FYI, jQuery isn't needed for this. I included an (untested) javascript equivalent if you want. If this is the only place where you're using jQuery on your page I'd recommend not using it because loading the whole jQuery library takes some time and data.

Resources