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;
}
Related
I'm pretty new to react-transition-group and I'm trying to build a card flipping animation. I'm able to get the first side to flip but it doesn't like the idea of staying put onto the back side. Any ideas what I'm doing wrong here?
import {useState} from "react";
import {CSSTransition} from "react-transition-group";
import "./styles.css";
export default function App() {
const [flipped, setFlipped] = useState(false);
return (
<div className="card-container">
<button
className="card-button"
onClick={() => setFlipped(!flipped)}
>
<CSSTransition
in={flipped}
timeout={1000}
classNames="front-face-transition"
>
<div className="card-front">
<p>front-side</p>
</div>
</CSSTransition>
<CSSTransition
in={!flipped}
timeout={1000}
classNames="back-face-transition"
>
<div className="card-back">
<p>back-side</p>
</div>
</CSSTransition>
</button>
</div>
);
}
.App {
font-family: sans-serif;
text-align: center;
}
.card-container {
width: 250px;
height: 400px;
padding: 0;
margin: 0;
}
.card-container .card-button {
padding: 0;
margin: 0;
border: none;
cursor: pointer;
width: 100%;
height: 100%;
position: relative;
}
.front-face-transition-enter {
transform-style: preserve-3d;
transition: all 1000ms ease;
transform: rotateY(0deg);
}
.front-face-transition-enter-active {
transform: rotateY(180deg);
}
.front-face-transition-enter-done {
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
}
.back-face-transition-enter {
transform-style: preserve-3d;
transition: all 1000ms ease;
transform: rotateY(0deg);
display: block;
}
.back-face-transition-enter-active {
transform: rotateY(-180deg);
display: block;
}
.back-face-transition-enter-done {
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
}
.card-front {
display: none;
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
}
Also, here's a working codesandbox link to this code in case that helps as well.
There's a lot to unpack here. There was no one error causing you issues, but a lot of little errors. I will try to address them as best I can. For the most part, your React is spot on, but the CSS is where you got into trouble.
tl;dr: here is a working code sandbox
You need to place transitions on the active class. This is by convention, but with your particular example, they could be permanent properties on the card elements.
The transform-style preserve 3d needs to be on the parent of the 3d element
If you want the front to be facing on page load, then your flipped logic is backwards
You had display: none on the front face, meaning it wasn't visible at all.
You need to specify an exit-done and a enter-done if you expect the transform to stay (the 180deg turn). Set those rules to have the same transform value as the active class, that will keep the side "staying put" after the animation.
Back face visibility can be a permanent property on the face elements
Display block on the back face was doing nothing--it was already block as it is a div element. I think you meant to put it on the card-front rules.
Need to set a perspective property on the parent element to see any real 3d effect. This is a pixel value that represents how far away the viewer is from the element in z space.
Some more tweaks to the card layout are necessary to get it right. I've added my basic stylings below.
The background of the container of the 3d elements (the button in your case) should have a background of transparent, otherwise the card will cut through the background when moving in 3d space.
It's a good idea to set an explicit background on the elements that rotating in this way, so there is a smoother animation.
I think that's everything.
Code
import { useState } from "react";
import { CSSTransition } from "react-transition-group";
import "./styles.css";
export default function App() {
const [flipped, setFlipped] = useState(false);
return (
<div className="card-container">
<button className="card-button" onClick={() => setFlipped(!flipped)}>
<CSSTransition
in={!flipped}
timeout={1000}
classNames="front-face-transition"
>
<div className="card-front">
<p>front-side</p>
</div>
</CSSTransition>
<CSSTransition
in={flipped}
timeout={1000}
classNames="back-face-transition"
>
<div className="card-back">
<p>back-side</p>
</div>
</CSSTransition>
</button>
</div>
);
}
body {
background-color: darkgray;
}
.App {
font-family: sans-serif;
text-align: center;
}
.card-container {
width: 250px;
height: 400px;
padding: 0;
margin: 0;
}
.card-container .card-button {
padding: 0;
margin: 0;
border: none;
cursor: pointer;
width: 100%;
height: 100%;
position: relative;
background: transparent;
transform-style: preserve-3d;
perspective: 5000px;
}
.card-front,
.card-back {
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
width: 100%;
}
.card-front {
background: beige;
position: absolute;
top: 0;
left: 0;
}
.card-back {
background: aliceblue;
}
.front-face-transition-enter {
transform: rotateY(180deg);
}
.front-face-transition-enter-active {
transition: all 1000ms ease;
transform: rotateY(0deg);
}
.front-face-transition-enter-done {
transform: rotateY(0deg);
}
.front-face-transition-exit {
transform: rotateY(0deg);
}
.front-face-transition-exit-active {
transform: rotateY(180deg);
transition: all 1000ms ease;
}
.front-face-transition-exit-done {
transform: rotateY(180deg);
}
.back-face-transition-enter {
transform: rotateY(-180deg);
}
.back-face-transition-enter-active {
transform: rotateY(0deg);
transition: all 1000ms ease;
}
.back-face-transition-enter-done {
transform: rotateY(0deg);
}
.back-face-transition-exit {
transform: rotateY(0deg);
}
.back-face-transition-exit-active {
transform: rotateY(-180deg);
transition: all 1000ms ease;
}
.back-face-transition-exit-done {
transform: rotateY(-180deg);
}
In general your code could be a lot DRYer. Look for shared styles of similar elements and group them together to avoid unnecessary cruft. I left a few ways to optimize in my code example, but for example you can see that .front-face-transition-exit-done and .front-face-transition-enter have the same rules, put them together!
Good luck with CSSTransitions, and let me know if you have any questions.
content1.className = 'start';
window.getComputedStyle(document.getElementById('content1')).opacity;
content1.style.marginLeft = "0px";
content1.className = 'transition1';
.main {
width: 300px;
height: 250px;
top: 0px;
left: 0px;
overflow: hidden;
position: relative;
background-color: grey;
cursor: pointer;
}
#content1 {
background-color: red;
width: 300px;
height: 250px;
top: 0px;
left: 0px;
margin-left: -300px;
}
.start {
opacity: 0
}
.transition1 {
opacity: 1;
visibility: hidden;
/*margin-left: -300px !important;*/
-webkit-transition: margin-left 1.5s ease 1.5s, margin-left 1.5s ease 1.5s, visibility 1.5s ease 1.5s
}
<div id="main" class="main">
<div id="content1" class="content1 hidden">
</div>
</div>
I want the red div to start from outside and go into the grey div slowly then after a few seconds it would go out slowly again. I tried using transition but it seems to now work.
My guess is timing is wrong?
UPDATE
I have the above now What I lack is the timing to show the red div then go out again to left. I have set a visibility but I think there is a way to just use margins?
If you're wanting to do this without keyframes, then I have two ideas.
First idea is to add the transition css property to the actual #content1 element. Because as you're removing the .transition1 class, you're taking away the transition details.
If that doesn't work, then you might need to break this into 4 different "states".
That is:
Start State: Red div starts unseen
Start-to-End Transition State: .transition1 class gets added
End State: A class is added to ensure that the red div has the same margin from the .transition1 even after the .transition1 class gets taken away.
End-to-State Transition State: Essentially do the opposite of what you did in the .transition1 class.
EDIT:
Maybe ignore the "4 steps" because I likely was overthinking what you were asking.
I'm not 100% sure why you wouldn't want a keyframe, but I've added a few options you can reference depending on your overall use case. Each of these rely on some sort of trigger or event. In my case, a click. But this can be determined by any sort of event.
var main2 = document.getElementById('main2');
var content2 = document.getElementById('content2');
main2.addEventListener('click', function() {
content2.classList.toggle('active');
});
var main4 = document.getElementById('main4');
var content4 = document.getElementById('content4');
main4.addEventListener('click', function() {
content4.classList.add('animate');
setTimeout(function() {
content4.classList.remove('animate');
}, 1500)
});
.main {
width: 300px;
height: 250px;
position: relative;
background-color: grey;
cursor: pointer;
overflow: hidden;
}
#content1 {
position: absolute;
background-color: red;
width: 100%;
height: 100%;
top: 0px;
left: 0px;
transform: translate(-100%, 0);
-webkit-transition: transform 1.5s ease;
}
.main:hover #content1 {
transform: translate(0, 0);
}
/* Toggle Option */
#content2 {
position: absolute;
background-color: red;
width: 100%;
height: 100%;
top: 0px;
left: 0px;
transform: translate(-100%, 0);
-webkit-transition: transform 1.5s ease;
}
#content2.active {
transform: translate(0, 0);
}
/* SetTimeout Option */
#content4 {
position: absolute;
background-color: red;
width: 100%;
height: 100%;
top: 0px;
left: 0px;
transform: translate(-100%, 0);
-webkit-transition: transform 1.5s ease;
}
#content4.animate {
transform: translate(0, 0);
}
<h2>Hover Option</h2>
<p>Animation happens on hover and disappears after hover</p>
<div class="main">
<div id="content1">
</div>
</div>
<h2>Toggle Option</h2>
<p>Animation happens on click and disappears on second click</p>
<div id="main2" class="main">
<div id="content2">
</div>
</div>
<h2>SetTimeout Option</h2>
<p>Animation happens on click and disappears after 1 second</p>
<div id="main4" class="main">
<div id="content4">
</div>
</div>
SOLUTION:
The solution I found is using two divs on top of each other(to make the function clearer I made one of the divs red), while it has to animate two divs for the effect, it should still be cleaner and faster than using javascript:
.outerDiv {
width: 100%;
height: 50px;
position: relative;
}
.hover1 {
height: 50px;
width: 50px;
background: black;
position: relative;
top: 0px;
transition: width 1s
}
.hover2 {
height: 50px;
width: 50px;
background: red;
position: relative;
top: -50px;
transition: width 1s 1s
}
.outerDiv:hover .hover2 {
width: 100%;
transition: width 0s 0.9s;
}
.outerDiv:hover .hover1 {
width: 100%;
transition: width 1s;
}
<div class="outerDiv">
<div class="hover1"></div>
<div class="hover2"></div>
</div>
This can be achieved by specifying different transition time for the normal state and hover state.
<style>
#hoverDiv {
height: 50px;
width: 50px;
background: black;
transition: width 5s; /*Specify required time.
This affects how long it takes to transition
back to the normal state*/
}
#hoverDiv:hover {
width: 100%;
transition: width 2s; /*This affects how long it takes to transition
into the hover state*/
}
</style>
<div id="hoverDiv"></div>
Also, if you want to add a delay before width decreases back to normal, try
#hoverDiv {
height: 50px;
width: 50px;
background: black;
transition: width 5s;
transition-delay: 5s; /*Waits for 5 seconds and then decreases
back to normal size in 5 seconds*/
}
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.
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>