Why does the drawer not animate on the way out? - javascript

I want my drawer to animate on the way in, and on the way out and when the animation ends, to turn to display: none but when the drawer is closed, it disappears and doesn't animate out.
const Drawer = ({ closeDrawer, isDrawerOpen }) => {
const [isAnimating, setIsAnimating] = useState()
let drawerClassName
if (isDrawerOpen) {
drawerClassName = "drawer-in"
} else if (!isDrawerOpen && isAnimating) {
drawerClassName = "drawer-animating"
} else if (!isDrawerOpen && !isAnimating) {
drawerClassName = "drawer-out"
}
return (
<>
<div
className={`drawer ${drawerClassName}`}
onAnimationStart={() => setIsAnimating(true)}
onAnimationEnd={() => setIsAnimating(false)}
></div>
<div onClick={closeDrawer}></div>
</>
)
}
CSS:
.drawer {
height: 100%;
width: 60%;
background-color: #fff;
position: absolute;
right: 0;
top: 0;
opacity: 1;
z-index: 3;
transform: translateX(100%);
}
.drawer-in {
animation: 0.7s drawerIn;
transform: translateX(0);
display: block;
}
.drawer-animating {
animation: 0.7s drawerOut;
display: block;
}
.drawer-out {
animation: 0.7s drawerOut;
display: none;
}
#keyframes drawerIn {
0% {
transform: translateX(100%);
}
1% {
transform: translateX(100%);
}
100% {
transform: translateX(0);
}
}
#keyframes drawerOut {
0% {
transform: translateX(0);
}
99% {
transform: translateX(100%);
}
100% {
transform: translateX(100%);
}
}

it's because when you set the drawer-out class to the element display: none; is activated immediately and you don't see the animation.
One solution is to run a setTimeout function within your JavaScript to wait for the animation to finish, then change the elements display property to none. This will allow your 'closing animation' to complete before removing the element. See my snippet below for working example.
Basically what's happening in my example snippet is I'm triggering the closing animation by adding a class associated with the animation. Then I'm setting a time out function that waits for the animation to complete (set time out in milliseconds to match your animation time within your CSS). Once the timeout is complete, the animation class is removed and the element's data attribute is set to closed which will trigger the display none. Hope this helps.
const menu = document.querySelector('.menu');
const menuToggle = document.querySelector('.menu_toggle');
menuToggle.checked=false
menuToggle.addEventListener('change',(e)=>{
menuToggle.disabled=true
let menuState = menu.dataset.menuState
if(menuState==='closed'){
menu.dataset.menuState='opened'
setTimeout(() => {
menuToggle.disabled=false
}, 500);
}else{
menu.classList.add('animate_close')
setTimeout(() => {
menu.classList.remove('animate_close')
menu.dataset.menuState='closed'
menuToggle.disabled=false
}, 500);
}
})
body {
background-color: rgb(235, 235, 235);
}
.menu {
background-color: black;
color: white;
width: fit-content;
}
.menu {
transition: all .5s ease-in-out;
}
.menu[data-menu-state="closed"] {
background-color: red;
display: none;
}
.menu[data-menu-state="opened"] {
animation: openMenu .5s ease-in-out;
transform: translateX(100%);
background-color: green;
}
.menu.animate_close{
background-color: rgb(0, 30, 128);
animation: closeMenu .5s ease-in-out;
opacity: 0;
}
#keyframes openMenu {
0% {
transform: translateX(0);
}
100% {
transform: translateX(100%);
}
}
#keyframes closeMenu {
0% {
transform: translateX(100%);
}
100% {
transform: translateX(0);
}
}
<body>
<label for="menu_toggle">Menu Toggle</label>
<input id="menu_toggle" type="checkbox" class="menu_toggle">
<div class="menu_container">
<div class="menu" data-menu-state="closed">
<ul>
<li>Item1</li>
<li>Item2</li>
<li>Item3</li>
</ul>
</div>
</div>
<script src="main.js"></script>
</body>

Related

Keep triggering animations on click

I have a section in my html with height=100vh. I want to show an animation of a text once user clicks anywhere on the window. Basically I want to keep triggering multiple animations over click event. I've achieved this using the following code:
const p1 = document.querySelector('.one p')
const p2 = document.querySelector('.two p')
const section1 = document.querySelector('.one')
const section2 = document.querySelector('.two')
window.addEventListener('click', () => {
p1.classList.add('animation')
if (p1.classList.contains('animation')) {
setTimeout(() => {
window.addEventListener('click', () => {
section1.classList.add('animation')
section2.classList.add('animation')
if (section1.classList.contains('animation')) {
setTimeout(() => {
window.addEventListener('click', () => {
p2.classList.add('animation')
}, {once:true})
}, 500)
}
}, {once:true})
}, 1000)
}
}, {once:true})
* {
margin: 0;
padding: 0;
}
body {
overflow: hidden;
height: 100vh;
}
section {
width: 100%;
height: 100vh;
color: white;
font-family: sans-serif;
font-size: 50px;
display: flex;
align-items: center;
justify-content: center;
}
.one {
background-color: indianred;
transition: 500ms ease-in;
}
.one.animation {
transform: translateY(-100vh);
}
.one p {
transform: translateX(-100vw);
transition: 1s ease-in;
}
.one p.animation {
transform: translateX(0);
}
.two {
background-color: grey;
transition: 500ms ease-in;
}
.two.animation {
transform: translateY(-100vh);
}
.two p {
transform: translateX(-100vw);
transition: 1s ease-in;
}
.two p.animation {
transform: translateX(0);
}
<body>
<section class="one">
<p>Section One</p>
</section>
<section class="two">
<p>Section Two</p>
</section>
</body>
But once all animations are done and I refresh the page, the applied animations remains and the page doesn't reload to it's initial look. I want to know why that is happening.
Also I would appreciate if I'm suggested with a different approach to achieve the requirements. Because the way I'm achieving it, I'm nesting the click event listener which doesn't seem to be efficient and a good practice.

CSS chain multiple animations from different classes

I am developing a ReactJs app, I need to animate a bus in a visual path.
The bus should make stops. So the bus first go from A -> B , then when the user click a button it goes from B -> C , ect..
I have already made the animations and the logic
.bus_0 {
animation: move 3s linear forwards;
}
.bus_1 {
animation: move2nd 3s linear forwards;
}
#keyframes move {
20% {
transform: translate(50px, 0px);
}
40% {
transform: translate(50px, 0px) rotate(-90deg);
}
80% {
transform: translate(50px, -130px) rotate(-90deg);
}
100% {
transform: translate(50px, -125px) rotate(0deg);
}
}
#keyframes move2nd {
20% {
transform: translate(100px, 0px);
}
40% {
transform: translate(100px, 0px) rotate(-90deg);
}
100% {
transform: translate(100px, -50px) rotate(-90deg);
}
}
Here I add classNames based on an index
let bus = document.getElementById('bus');
bus && bus.classList.add(`bus_${data.selectedIndex}`);
The problem is when the first animation starts, the bus reachs point B , but for the second animation , it starts from point A.
Example the bus first position (0, 0), after the first animation (100, 100), when the second animation plays it start from (0, 0) but i need it the start from the last position reached.
There must be a better way probably but that is what i got and i think it will solve your problem
So i collect boxes x and y coordinates when animation button clicks and after the animationend event, i collect both x and y coordinates again and calculate difference of them and add to box style.
const firstButton = document.querySelectorAll('button')[0];
const secondButton = document.querySelectorAll('button')[1];
const myBox = document.querySelector('div')
let rectBefore;
let rectAfter;
let positionBeforeX;
let positionBeforeY;
let positionAfterX;
let positionAfterY;
let differenceX;
let differenceY;
firstButton.addEventListener('click', () => {
rectBefore = myBox.getBoundingClientRect();
positionBeforeX = rectBefore.left;
positionBeforeY = rectBefore.top;
myBox.classList.toggle('first');
})
secondButton.addEventListener('click', () => {
rectBefore = myBox.getBoundingClientRect();
positionBeforeX = rectBefore.left;
positionBeforeY = rectBefore.top;
myBox.classList.toggle('second');
})
myBox.addEventListener('animationend', (event) =>{
rectAfter = myBox.getBoundingClientRect();
positionAfterX = rectAfter.left;
positionAfterY = rectAfter.top;
differenceX = positionAfterX - positionBeforeX;
differenceY = positionAfterY - positionBeforeY;
if(myBox.style.left !== ""){
myBox.style.left = `${parseInt(myBox.style.left.split('px')) + differenceX}px`;
myBox.style.top = `${parseInt(myBox.style.top.split('px')) + differenceY}px`;
}
else{
myBox.style.left = `${differenceX}px`;
myBox.style.top = `${differenceY}px`;
}
myBox.classList.remove(`${event.animationName}`);
})
*,
*::before,
*::after {
box-sizing: border-box;
}
body{
min-height: 100vh;
position: relative;
display: grid;
place-content: center;
}
button{
position: absolute;
background-color: greenyellow;
width: 5rem;
height: 5rem;
}
button:nth-of-type(1){
top:5rem;
right: 10rem;
margin-right: 1rem;
}
button:nth-of-type(2){
top:5rem;
right: 5rem;
}
.box{
position:relative;
width: 100px;
height: 100px;
background-color: blue;
}
.first {
animation: first 3.0s linear forwards;
}
.second {
animation: second 3.0s linear forwards;
}
#keyframes first {
20% {
transform: translate(50px, 0px);
}
40% {
transform: translate(50px, 0px) rotate(-90deg);
}
80% {
transform: translate(50px, -130px) rotate(-90deg);
}
100% {
transform: translate(50px, -125px) rotate(0deg);
}
}
#keyframes second {
20% {
transform: translate(100px, 0px);
}
40% {
transform: translate(100px, 0px) rotate(-90deg);
}
100% {
transform: translate(100px, -50px) rotate(-90deg);
}
}
<div class="box"></div>
<button>First Animation</button>
<button>Second Animation</button>

Image won't stay visible for hover effect

Hello and thank you in advance for reading my question.
GOAL: Set image so that once it's scrolled into view it transitions smoothly into a set position - but still reacts to :hover. Using #keyframes and a little JavaScript, I set the image to opacity: 0 and it's final opacity to opacity: .85. Then I added a hover effect in CSS to make it's opacity: 1
The issue is once it's finished with it's transition - it disappears - reverting to it's original opacity which is zero. I managed to make it freeze at .85 with animation-fill-mode: forwards, rather than animation-fill-mode: none, but then it won't respond to :hover
And here's a test snippet of the problem in action:
let observer_img = new IntersectionObserver(updates => {
updates.forEach(update => {
if (update.isIntersecting) {
update.target.classList.add('shift_frame_center_img');
} else {
update.target.classList.remove('shift_frame_center_img');
}
});
}, { threshold: 0 });
[...document.querySelectorAll('.features-img-wrapper img')].forEach(element => observer_img.observe(element));
body {
width: 100%;
height: 100vh;
background-color: lightgrey;
}
/* CHILD */
.features-img-wrapper img {
width: 10rem;
display: block;
margin-left: auto;
margin-right: auto;
margin-top: 8rem;
opacity: 0;
transition: all .5s;
}
/* APPEND-CHILD */
.shift_frame_center_img {
animation: center_img 1s 0.5s none;
}
/* CHILD ON HOVER */
.features-img-wrapper img:hover {
opacity: 1;
transform: scale(1.035);
}
/* KEYFRAMES */
#keyframes center_img {
0% {
transform: translateY(20rem);
}
100% {
transform: translateY(0);
opacity: .85;
}
}
<body>
<div class="features-img-wrapper">
<img src="https://synapse.it/wp-content/uploads/2020/12/test.png">
</div>
</body>
If I could get a hand with this that would be wonderful, I'm a bit of a beginner and have already spent a few hours on this, all feedback welcome. Thank you very much.
Solution 1
To understand why the hover effect was not working with the animation-fill-mode: forwards, read this answer.
You can fix that by adding !important property to the hover styles:
.features-img-wrapper img:hover {
opacity: 1 !important;
transform: scale(1.035) !important;
}
The problem, in this case, is that the transition will not work for hover.
Solution 2
You could remove the animation entirely and add the final state styles to the shift_frame_center_img class.
But you would still need to use the !important property because of the CSS Specificity.
let observer_img = new IntersectionObserver(updates => {
updates.forEach(update => {
if (update.isIntersecting) {
update.target.classList.add('shift_frame_center_img');
} else {
update.target.classList.remove('shift_frame_center_img');
}
});
}, { threshold: 0 });
[...document.querySelectorAll('.features-img-wrapper img')].forEach(element => observer_img.observe(element));
body {
width: 100%;
height: 100vh;
background-color: lightgrey;
}
/* CHILD */
.features-img-wrapper img {
width: 10rem;
transform: translateY(20rem);
display: block;
margin-left: auto;
margin-right: auto;
margin-top: 8rem;
opacity: 0;
transition: all .5s;
}
/* APPEND-CHILD */
.shift_frame_center_img {
transform: none !important;
opacity: .85 !important;
}
/* CHILD ON HOVER */
.features-img-wrapper img:hover {
opacity: 1 !important;
transform: scale(1.035) !important;
}
<body>
<div class="features-img-wrapper">
<img src="https://synapse.it/wp-content/uploads/2020/12/test.png">
</div>
</body>
This snippet removes the need for fill-mode forwards by setting the img to have opacity 1 as its initial state so it will revert to that at the end of the animation.
The animation itself is altered to take 1.5s rather than 1s with the first third simply setting the img opacity to 0 so it can't be seen. This gives the delay effect.
let observer_img = new IntersectionObserver(updates => {
updates.forEach(update => {
if (update.isIntersecting) {
update.target.classList.add('shift_frame_center_img');
} else {
update.target.classList.remove('shift_frame_center_img');
}
});
}, { threshold: 0 });
[...document.querySelectorAll('.features-img-wrapper img')].forEach(element => observer_img.observe(element));
body {
width: 100%;
height: 100vh;
background-color: lightgrey;
}
/* CHILD */
.features-img-wrapper img {
width: 10rem;
display: block;
margin-left: auto;
margin-right: auto;
margin-top: 8rem;
opacity: 0;
transition: all .5s;
opacity: 1;
}
/* APPEND-CHILD */
.features-img-wrapper img {
animation: center_img 1.5s 0s none;
}
/* CHILD ON HOVER */
.shift_frame_center_img:hover {
opacity: 1;
transform: translateY(0) scale(1.035);
}
/* KEYFRAMES */
#keyframes center_img {
0% {
transform: translateY(20rem) scale(1);
opacity: 0;
}
33.33% {
transform: translateY(20rem) scale(1);
opacity: 0;
}
100% {
transform: translateY(0) scale(1);
opacity: .85;
}
}
<body>
<div class="features-img-wrapper">
<img src="https://synapse.it/wp-content/uploads/2020/12/test.png">
</div>
</body>
Note: as each transform setting will reset anything that isn't included both tranlateY and scale are included in each setting.
Outside the SO snippet system it was possible to leave the animation settings untouched by chaining another animation to the front which ran for 0.5s and just set the img to opacity: 0. This did not work in the snippet system (it got into a loop of flashing on and off) hence the introduction of one but extended animation.

How to fire "animationend" event, when I can't set a CSS animation?

If I set the css animation to "element", it's fine. If the Css animation is not available, how does it function fire like a zero seconds animation in the ES5?
function run() { ... }
element.addEventListener('animationend', run);
Reply for
#Anurag Srivastava,
Am I wrong idea or do I have the following code wrong? Either way, the return value is "".
var el1 = document.getElementById("notAnimation");
console.log(el1.style.animation);
var el2 = document.getElementById("onAnimation");
console.log(el2.style.animation);
div {
padding: 10px;
margin: 20px;
}
#notAnimation {}
#onAnimation {
animation: scale 10s ease-in-out;
}
#keyframes scale {
0% {
transform: scale(1);
opacity: 1;
color: black;
}
50% {
transform: scale(0.95);
opacity: .4;
color: red;
}
100% {
transform: scale(1);
opacity: 1;
color: black;
}
}
<div id="notAnimation">
Not Animation
</div>
<div id="onAnimation">
Animation
</div>
You can check if element.style.WebkitAnimation and element.style.animation contain any value and execute run() if the value is ""
Edit Turns out that .style will return "" for any value. What you need is window.getComputedStyle() along with the property animationName. If it is none, there is no animation, else there is. Check the code below:
var el1 = document.getElementById("notAnimation");
console.log(window.getComputedStyle(el1)["animationName"])
var el2 = document.getElementById("onAnimation");
console.log(window.getComputedStyle(el2)["animationName"])
div {
padding: 10px;
margin: 20px;
}
#notAnimation {}
#onAnimation {
animation: scale 10s ease-in-out;
}
#keyframes scale {
0% {
transform: scale(1);
opacity: 1;
color: black;
}
50% {
transform: scale(0.95);
opacity: .4;
color: red;
}
100% {
transform: scale(1);
opacity: 1;
color: black;
}
}
<div id="notAnimation">
Not Animation
</div>
<div id="onAnimation">
Animation
</div>

Page elements flying in

My purpose is to modify my page to load elements by "flying in". I found this JSFiddle example, and I should now make it to happen on page load, without clicking anything. So it triggers on page load.
$(function() {
$("#add-sidebar-module").on("click", function() {
$("<div />", {
'class': "module",
text: "I'm new here."
}).prependTo("#sidebar");
});
$("#add-article").on("click", function() {
$("<div />", {
'class': "module",
html: "<h1>Title</h1><p>text text text.</p>"
}).prependTo("#main");
});
});
I'm also afraid that there is some kind of issues in what comes to the page loading and the animation. Tips and tricks to make sure it's as smooth as possible would be welcome!
http://jsfiddle.net/PJN6r/
<div class="animate-on-load">
This is animated
</div>
$('.animate-on-load').addClass('module');
As per your request.
I would not suggest triggering a click on page load. Just add the class on load.
This is more smooth effect: enter link description here
.module {
-webkit-animation: flyin 1s cubic-bezier(.62, -0.36, .4, 1.28);
-moz-animation: flyin 1s cubic-bezier(.62, -0.36, .4, 1.28);
-o-animation: flyin 1s cubic-bezier(.62, -0.36, .4, 1.28);
-ms-animation: flyin 1s cubic-bezier(.62, -0.36, .4, 1.28);
animation: flyin 1s cubic-bezier(.62, -0.36, .4, 1.28);
}
#-webkit-keyframes flyin {
from {
opacity: 0;
-webkit-transform: scale(1.4);
}
to {
-webkit-transform: scale(1);
opacity: 1;
}
}
#-moz-keyframes flyin {
from {
opacity: 0;
-moz-transform: scale(1.4);
}
to {
-moz-transform: scale(1);
opacity: 1;
}
}
#-o-keyframes flyin {
from {
opacity: 0;
-o-transform: scale(1.4);
}
to {
-o-transform: scale(1);
opacity: 1;
}
}
#-ms-keyframes flyin {
from {
opacity: 0;
-ms-transform: scale(1.4);
}
to {
-ms-transform: scale(1);
opacity: 1;
}
}
#keyframes flyin {
from {
opacity: 0;
transform: scale(1.4);
}
to {
transform: scale(1);
opacity: 1;
}
}
body {
padding: 50px;
}
.main, .sidebar {
padding: 1em;
}
.main {
float: left;
width: 60%;
}
.sidebar {
float: right;
width: 20%;
}
.module {
border: 0.5em solid #ccc;
background: #eee;
padding: 1.5em;
margin: 0 0 2em 0;
}
h1 {
margin-top: 0;
}
Check out this jQuery plugin. It animates any element you want when the page loads, so that you don't have to click anything.
Here is a demo of it.
To use this plugin, make sure you first include jQuery on any pages that use it, and include the JavaScript file as well.
Then wrap this tag around all elements you want to fly-in.
<div class="runway">
... fly-in elements go here ...
</div
Finally add either fly-in-right or fly-in-left class to the DOM element you wish to animate.
<img class="fly-in-left" src="..." />
And that should do it! When the element if visible on the page, it will animate a 'fly-in'!

Categories

Resources