This question already has answers here:
Maintaining the final state at end of a CSS animation
(5 answers)
Closed 5 months ago.
I have a page with a lot of interactivity going on. Clicking a button here changes the text and image over there... that kind of stuff. Just about everything is controlled by click handlers adding or removing CSS classNames to show or hide the appropriate content. Almost all of the animation is achieved with CSS transitions. Pretty straight forward.
But I have one element that requires a keyframe animation. Its default state is to be hidden until it's time for it to enter the UI... at which point it needs to have this CSS keyframe animation applied and stay in the last state the animation had it in.
For the sake of this example, let's just say that when Block A becomes visible (by clicking the button), the element in question, Block A1, needs to fade in (remember in my actual use case, the animation is more complicated and can't be achieved using transitions... it requires a keyframe animation) and then remain with the properties it had in the last frame of the animation: in this case opacity: 1 after the animation runs.
Right now, the only way I'm able to do this is to use javascript to set the opacity after the animation runs. This works but I can see it getting really messy/complicated when elements now have a style attribute overriding the styles set in the CSS rulesets. You can see this start to happen when you click the button again to hide the block and then AGAIN to show it... Block A1 is still set to opacity: 1 so the animation ha no effect after the 1st time around.
Is there a better way to do this?
const $btn = document.querySelector('button');
const $block = document.querySelector('.block-a');
const $blocka1 = document.querySelector('.block-a-1');
$btn.addEventListener('click', function() {
$block.classList.toggle("is-visible")
})
$blocka1.addEventListener('animationend', function() {
console.log('done');
this.style.opacity = 1;
})
body {
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
button {
font-size: 32px;
margin-bottom: 24px;
cursor: pointer;
}
.block-a {
width: 25vw;
height: 50vh;
background-color: red;
padding: 20px;
color: #fff;
transform: translateY(150%);
transition: all .25s linear;
}
.block-a.is-visible {
transform: translateY(0);
transition: all .1s ease-in;
}
.block-a-1 {
font-size: 32px;
font-weight: bold;
color: white;
background-color: blue;
padding: 10px;
opacity: 0;
}
.block-a.is-visible .block-a-1 {
animation: fadeIn 2s linear 2s;
}
#keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
<button>Do Block 1</button>
<div class="block-a">
<span class="block-a-1">Block A-1</span>
<h1>Block A</h1>
</div>
You can maintain the last animation frame using:
animation-fill-mode:forwards;
https://developer.mozilla.org/en-US/docs/Web/CSS/animation-fill-mode
const $btn = document.querySelector('button');
const $block = document.querySelector('.block-a');
$btn.addEventListener('click', function() {
$block.classList.toggle("is-visible")
})
body {
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
button {
font-size: 32px;
margin-bottom: 24px;
cursor: pointer;
}
.block-a {
width: 25vw;
height: 50vh;
background-color: red;
padding: 20px;
color: #fff;
transform: translateY(150%);
transition: all .25s linear;
}
.block-a.is-visible {
transform: translateY(0);
transition: all .1s ease-in;
}
.block-a-1 {
font-size: 32px;
font-weight: bold;
color: white;
background-color: blue;
padding: 10px;
opacity: 0;
}
.block-a.is-visible .block-a-1 {
animation: fadeIn 2s linear 2s;
animation-fill-mode:forwards;
}
#keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
<button>Do Block 1</button>
<div class="block-a">
<span class="block-a-1">Block A-1</span>
<h1>Block A</h1>
</div>
Set the animation property animation-fill-mode to forwards or add it to animation like animation: fadeIn 2s linear 2s forwards;
Related
I'm trying to add effects on modal.
I want to make like this:
When modal--show class added, visibility set to visible and opacity continues to grow 0% to 100%.
When modal--show class removed, opacity continues to decrease 100% to 0%, and after then, visibility set to hidden.
Showing modal animation works well, but hiding modal animation doesn't. When hiding animation plays, visibility becomes hidden immediately when animation starts.
How to set visibility: hidden after opacity: 0% with CSS or pure JS?
JSFiddle: https://jsfiddle.net/p1gtranh/1/
document.querySelector('.open').addEventListener('click', () => {
document.querySelector('.modal').classList.add('modal--show');
});
document.querySelector('.close').addEventListener('click', () => {
document.querySelector('.modal').classList.remove('modal--show');
});
.modal {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.3);
visibility: hidden;
opacity: 0%;
}
.modal--show {
animation: show 0.5s both;
visibility: visible;
}
#keyframes show {
0% {
visibility: hidden;
opacity: 0%;
}
1% {
visibility: visible;
}
100% {
opacity: 100%;
}
}
<button class="open">open</button>
<div class="modal">
<button class="close">close</button>
</div>
I would probably scrap the whole animation and just make a simple transition instead.
In this case, transition is specified in a shorthand. The associated properties are:
transition-timing-function: ease-in-out - how the transition should be played, this value will play an easing fading transition when both opening and closing the modal.
transition-property: all - what property is associated with the transition. Since we are transitioning both the opacity and visibility-properties, using all will select both of these.
transition-duration: 0.5s - how long the transition should last.
More on the transition-shorthand here
document.querySelector('.open').addEventListener('click', () => {
document.querySelector('.modal').classList.add('modal--show');
});
document.querySelector('.close').addEventListener('click', () => {
document.querySelector('.modal').classList.remove('modal--show');
});
.modal {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.3);
visibility: hidden;
opacity: 0%;
transition: all 0.5s ease-in-out;
}
.modal--show {
visibility: visible;
opacity: 100%;
}
<button class="open">open</button>
<div class="modal">
<button class="close">close</button>
</div>
I am trying to find out how transitions work. I am trying to make a thumbs up appear using a fade in fade out transition for a recycling simulator program, however, I do not know how understand how transitions work.
Here is a snippet of my html
<div class="thumbs-up-bg">
<i class="fas fa-thumbs-up" id="thumbs-up"></i>
</div>
<div class="thumbs-down-bg">
<i class="fas fa-thumbs-down" id="thumbs-down"></i>
</div>
Here is the CSS
.thumbs-up-bg {
position: absolute;
visibility: hidden;
background-color: green;
display: inline-block;
border-radius: 50%;
width: 60px;
height: 60px;
text-align: center;
opacity: 0;
transition: opacity 0.1s ease-in-out;
}
#thumbs-up-bg.visible {
opacity: 1;
}
#thumbs-up {
font-size: 50px;
color: white;
}
.thumbs-down-bg {
position: absolute;
visibility: hidden;
background-color: red;
display: inline-block;
border-radius: 50%;
width: 60px;
height: 60px;
text-align: center;
opacity: 0;
transition: opacity 0.1s ease-in-out;
}
#thumbs-down {
font-size: 50px;
color: white;
}
.visible {
visibility: visible;
opacity: 1;
transition: opacity 2s linear;
}
.hidden {
opacity: 0;
transition: visibility 5s, opacity 2s linear;
}
Are transitions supposed to be set on the selector that is going to be changed? Is there a simpler way to complete this task?
Here is the Javascript
if (drop === trashDropZone){
if(!isRecyclable(draggable)){
alert("Success, it is garbage");
thumbsUp.classList.toggle("visible");
setTimeout(() =>{
thumbsUp.classList.toggle("visible");
thumbsUp.classList.toggle("hidden");
}, 1000);
makeInvisible(draggable);
} else{
//show thumbs down
alert("Thumbs down");
thumbsDown.classList.toggle("visible");
setTimeout(() =>{
thumbsDown.classList.toggle("visible");
thumbsDown.classList.toggle("hidden");
}, 1000);
This question already has answers here:
How can I delay the start of a CSS animation?
(3 answers)
Maintaining the final state at end of a CSS animation
(5 answers)
Closed 2 years ago.
How can I use setinterval() for css animation?
For example, in the example below, I want the div to come with animation after 3000ms. How do I do this?
Can I get it starting from bottom 0, like the price segment that changes when I choose the minute and day as on this page?
<div><span>$</span>2.000</div>
jsfiddle example
div {
font-size: 42px;
position: relative;
animation: mymove 0.3s;
animation-timing-function: ease-in-out;
}
div span{
font-size: 24px;
padding-right: 5px;
}
#keyframes mymove {
0% {
bottom: -70px;
}
100% {
bottom: 0px;
}
}
Set an animation-delay, together with animation-fill-mode:forwards to prevent the div from reverting to the initial state when the animation has finished. You can use opacity to control when to show the element (I've used a dark body background here so that your white text is visible):
body {
background: #333;
color: #fff;
}
.wrapper {
overflow: hidden;
}
.wrapper div {
font-size: 42px;
position: relative;
animation: mymove 0.3s;
animation-timing-function: ease-in-out;
animation-delay: 3s;
animation-fill-mode: forwards;
opacity: 0;
}
.wrapper div span {
font-size: 24px;
padding-right: 5px;
}
#keyframes mymove {
0% {
opacity: 1;
display: block;
transform: translateY(-70px);
}
100% {
opacity: 1;
display: block;
transform: translateY(0px);
}
}
<div class="wrapper">
<div><span>$</span>2.000</div>
</div>
I'm making a dropdown menu that should slide into view on click. I want to make it slide back up on a second click, but have been struggling to figure out how to do so. Most of the answers I've found online involve jQuery, but I am hoping to accomplish this with vanilla js.
My CSS:
.post-more-dropdown {
background-color: #f8f9fa;
display: block;
position: absolute;
right: 0px;
top: 60px;
min-width: 80px;
animation-name: dropdown;
animation-play-state: paused;
animation-fill-mode: backwards;
animation-duration: 0.7s;
a {
display: block;
padding-left: 10px;
padding-top: 10px;
}
a:hover {
background-color: #555555;
color: white;
cursor: pointer;
}
}
#keyframes dropdown {
0% {
max-height: 0px;
opacity: 0;
}
100% {
max-height: 200px;
opacity: 1;
}
}
My javascript:
function post_more_dropdown(post_id) {
dropdown = document.querySelector(`#post-more-dropdown-${post_id}`);
dropdown.style.animationPlayState = "running";
}
This works perfectly to get the dropdown to run on click, but I'm struggling to figure out how to reverse it on the second click. I was thinking I could create a new #keyframes dropdown-reverse animation and add it to the class, but then I realized I can't control two animations under one class with JS. I was thinking I could also create two different classes and add/remove those classes with JS along with controlling two different animations, but I feel like there's got to be a more elegant solution that I'm missing.
I have a feeling that animation-direction is going to be part of the answer, but I haven't quite figured out how that will work for this.
Use the following CSS, change class using JavaScript.
function post_more_dropdown(post_id) {
dropdown = document.querySelector(`#post-more-dropdown-${post_id}`);
if (dropdown.classList.contains('dropdown-animate-forward')) {
dropdown.classList.add('dropdown-animate-backward');
dropdown.classList.remove('dropdown-animate-forward');
} else {
dropdown.classList.add('dropdown-animate-forward');
dropdown.classList.remove('dropdown-animate-backward');
}
}
setInterval(function() {
post_more_dropdown(1)
}, 1000);
.post-more-dropdown {
background-color: #f8f9fa;
display: block;
position: absolute;
right: 0px;
top: 60px;
min-width: 80px;
a {
display: block;
padding-left: 10px;
padding-top: 10px;
}
a:hover {
background-color: #555555;
color: white;
cursor: pointer;
}
}
/* forward animation */
.dropdown-animate-forward {
animation-name: dropdownForward;
animation-fill-mode: backwards;
animation-duration: 0.7s;
}
#keyframes dropdownForward {
0% {
max-height: 0px;
opacity: 0;
}
100% {
max-height: 200px;
opacity: 1;
}
}
/* backward animation */
.dropdown-animate-backward {
animation-name: dropdownBackward;
animation-fill-mode: forward;
animation-duration: 0.7s;
}
#keyframes dropdownBackward {
0% {
max-height: 200px;
opacity: 1;
}
100% {
max-height: 0px;
opacity: 0;
}
}
<div id="post-more-dropdown-1" class="post-more-dropdown">Post more</div>
I am trying to do some transition/transform effect as you can see on this site, where the navigation switches halfway the page and kinda transfers into the other one.
I've created a jsfiddle, with two navs, the .mobilenav is the one that should be changed upon scrolling, and the .desktopnav is the one which slides out as you can see. But now I was wondering how I can make recreate that transition. (The changing nav is done in JQuery with a if, else statement and ($(window).scrollTop() > 500)
I've simulated that nav pretty close in my fiddle, with some simplifications of course: https://jsfiddle.net/pttsky/0anpeLj0/
There are couple of key concepts:
There is actually only one nav to which we add .full class to indicate state change.
There is a container of nav, actual nav and its child li elements
Each of above listed has own CSS transitions and animations that change their positioning, opacity and backgrounds.
Talking deeper on changing nav from collapsed to full-width like on that site.
The container block slightly pulling upwards the nav. It becomes non-transparent, which gives an illusion that border-radius disappeared from the nav, but actually if we'd animated border-radius, that would be ugly.
.nav-container {
display: block;
position: fixed;
width: 100%;
z-index: 2;
top: 0;
padding: 25px 25px 15px;
-webkit-transition: .8s;
transition: .8s;
}
.full {
background: #fff;
padding-top: 15px;
}
The child elements, except MENU link, have max-width: 0 by default. When menu is hovered, or when it is in full-width state, elements have max-width: 200px, and MENU has reversed behaviour:
.nav-main .item {
display: block;
float: left;
max-width: 0;
opacity: 0;
-webkit-transition: .8s;
transition: .8s;
/* limit width */
overflow: hidden;
line-height: 3em;
}
.nav-main .toggle {
max-width: 200px;
opacity: 1;
-webkit-transition: .6s .4s;
transition: .6s .4s;
}
.full .nav-main .item {
max-width: 200px;
opacity: 1;
}
.full .nav-main .item + .item {
margin-left: 12vw;
}
.full .nav-main .toggle {
max-width: 0;
opacity: 0;
-webkit-transition: .1s;
transition: .1s;
}
When changing state, all items of nav seems like fade out then fade in. I've added the appropriate animation to the whole nav:
/* nav full-width */
#keyframes blink {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0;
}
}
.full .nav-main {
animation: blink .8s;
}
Alternatively to Device's answer, you could also use css transitions on one nav to get the same effect, instead of using two navs.
By switching the class on the nav you can trigger the transition and place the navigation in its correct place.
JS:
$(document).ready(function() {
var nav = $('.desktopnav');
$(window).scroll(function() {
var scrolltop = $(window).scrollTop();
if (scrolltop > 500 && !nav.hasClass('scrolled')) {
nav.addClass('scrolled');
}
else if (scrolltop <= 500 && nav.hasClass('scrolled')) {
nav.removeClass('scrolled');
}
});
});
CSS:
.desktopnav {
/* ... snipped, unchanged ... */
transition: all 0.2s ease-out;
}
.desktopnav>ul {
transition: all 0.2s ease-out;
}
.desktopnav>ul>.dropdown {
/* ... snipped, unchanged ... */
transition: all 0.2s ease-out;
}
/* ... snipped unchanged styles for the unscrolled menu ... */
.desktopnav.scrolled {
top: 0px;
right: auto;
left: 0px;
width: 100%;
}
.desktopnav.scrolled>ul {
margin-top:0px;
background: #fff;
}
.desktopnav.scrolled>ul>.dropdown {
border-radius: 0px;
}
.desktopnav.scrolled>ul>.dropdown .dropdown-content {
max-width: 1000px;
float: none;
display: inline-block;
vertical-align: middle;
margin-left: 19px;
}
https://jsfiddle.net/q80k0y7v/1/