apply fadeIn/fadeOut effect at the same time - javascript

I have one boolean to control two components (fullPage/halfPage) into one div. when boolean is true fullPage component applied and false halfPage component applied. how can I apply the fadeIn, fadeOut effect on that div. so when fullPage classname applies, full page fade in and half page fade out. when halfPage class name applies, full page fade out and half page fade In?
codesandox.io
// css
.App {
font-family: sans-serif;
text-align: center;
}
.fullPage {
background-color: aquamarine;
animation: fadeIn 2s linear;
width: 490px;
}
.halfPage {
background-color: bisque;
animation: fadeIn 2s linear;
width: 290px;
}
#keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
#keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
// App.tsx
import "./styles.css";
import { useState } from "react";
import classNames from "classnames";
export const HalfPage = () => {
return (
...
)}
export const FullPage = () => {
return (
...
);
};
export default function App() {
const [cnt, setCnt] = useState(0);
const isFullPage = cnt % 2 === 0;
const appClass = classNames({
fullPage: isFullPage,
halfPage: !isFullPage
});
const handleOnClick = () => {
setCnt(cnt + 1);
};
return (
<div className="App">
<button onClick={handleOnClick}>Change background</button>
<div className={appClass}>{isFullPage ? <FullPage /> : <HalfPage />}</div>
</div>
);
}

You can achieve the fade in / fade out effect in both elements, but it requires some refactoring in your HTML code as well as some more CSS to make it work. All we do is wrapping these page elements in divs and place them in a parent element whose class name is inner-container, in order to keep them in the same position as they should be.
So, your new styles.css would look like this (I have pointed out the changes):
.App {
font-family: sans-serif;
text-align: center;
}
/* Added opacity, position and transition time in both elements. */
.fullPage {
background-color: aquamarine;
animation: fadeIn 2s linear;
width: 490px;
opacity: 0;
transition: 0.4s;
position: absolute;
}
.halfPage {
background-color: bisque;
animation: fadeIn 2s linear;
width: 290px;
opacity: 0;
transition: 0.4s;
position: absolute;
}
/* Added flag class for active element and class for inner container. */
.active {
opacity: 1;
}
.inner-container {
position: relative;
}
/* Keyframes are not necessary. */
/* #keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
#keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
} */
Your return function in App.tsx would look like this:
return (
<div className="App">
<button onClick={handleOnClick}>Change background</button>
<div className="inner-container">
<div className={isFullPage ? "fullPage active" : "fullPage"}>
<FullPage />
</div>
<div className={isFullPage ? "halfPage" : "halfPage active"}>
<HalfPage />
</div>
</div>
</div>
);

Related

animation with react and useRef hook? is there another way?

I want to make smth like clock with react.I hope to do it with help of ref, but it doesn't work sometimes. Is there another solution of this task, or what i do wrong?
import React, { useRef } from 'react';
import classes from "./Synchroniser.module.css";
const Synchroniser = () => {
const point:any = useRef(null);
var k = 0;
if (point.current !== null) {
setInterval(()=>{
if(k<360){
k++;
console.log(k);
}else{k=0}
point.current.style.transform = `rotate(${k}deg)`;
},100);
}else{
console.log(0)
}
return(
<div>
<h1 ref={point}>RLY?</h1>
</div>
);
};
export default Synchroniser
Go to bottom for the pure CSS solution
React Solution
This solves your problem but the k variable adds twice as much as it needs to, I don't know why. For the k variable to persist between states and not reset the clock every rerender you have to use it with useRef too.
const point = useRef(null);
const k = useRef(0);
useEffect(() => {
if (point.current !== null) {
setInterval(() => {
if (k.current < 360) {
k.current = k.current + 1;
} else {
k.current = 0;
}
point.current.style.transform = `rotate(${k.current}deg)`;
}, 1000);
} else {
console.log("0");
}
},[]);
CSS Solution CodeSandbox Link
JSX
<div className="clock">
<div className="spin"></div>
</div>
CSS
.clock {
height: 250px;
width: 250px;
border-radius: 9999px;
display: flex;
justify-content: center;
align-items: center;
background: #eeeeee;
}
.spin {
width: 90%;
height: 2px;
position: relative;
background: white;
-webkit-animation:spin 60s linear infinite;
-moz-animation:spin 60s linear infinite;
animation:spin 60s linear infinite;
}
.spin::after,
.spin::before {
height: 2px;
content: " ";
position: absolute;
top: 0;
width: 50%;
}
.spin::after {
right: 0;
background-color: black;
}
.spin::before {
left: 0;
background-color: #eeeeee;
}
#-moz-keyframes spin {
100% {
-moz-transform: rotate(360deg);
}
}
#-webkit-keyframes spin {
100% {
-webkit-transform: rotate(360deg);
}
}
#keyframes spin {
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}

Keyframes animation for a nav bar won't retrigger at a different position

I've been trying to develop a CSS keyframes animation for a navigation bar.
You can see in the code snippet how the animation works - the red line is animated when the user clicks an element of the nav bar. The first element of the nav bar is active by default (the red line is under this element). When an element is clicked, the JS takes the properties of the animation element, as well as the properties of the element that was clicked. These properties are incorporated into new keyframes that are inserted into the single keyframes rule.
When the second element is clicked, the animation runs successfully from element 1 --> 2. The animation also runs successfully from element 1 --> 3.
But after the animation plays from element 1 --> 2, it won't play from element 2 --> 3. The animationend event does not trigger (I checked this). As of now, I'm only concerned with the animation going forwards.
After researching, I tried several methods to fix this. Removing and reattaching the animation class does not work, even with a DOM reflow being triggered. Changing the animation-play-state from 'running' to 'paused' does not work either. Other solutions, such as changing the animation-name to 'none' and then back, only generate more problems, like the position of the animation element being reset upon the ending of the animation. I truly do not know how to fix this.
I would prefer to make a flexible keyframes animation, such as this, rather than brute-forcing it. A brute force scenario would include making 6 different keyframes rules, and I want the code to be applicable to any number of elements in the navigation bar. Adding keyframes rules for every addition of an element would require exponentially more code each addition.
Thanks.
~ Code for demo ~
var keyframes = findKeyframesRule('movey');
$(document).ready(() => {
$('div.one').click(() => {
if (!($('div.one').hasClass('active'))) {
/* unfinished */
}
})
$('div.two').click(() => {
if (!($('div.two').hasClass('active'))) {
/* transfer active class */
$('div.active').removeClass('active');
$('div.two').addClass('active');
var left = ( parseInt($('div.absolute').css('left')) / $(window).width() ) * 100;
/* reset keyframes before animation */
clearKeyframes();
/* add new keyframes for when div.two is clicked */
keyframes.appendRule("0% { width: 15%; left: " + left + "%;}");
keyframes.appendRule("49.99% { width: 30%; left: " + left + "%; right: 70%;}");
keyframes.appendRule("50% { width: 30%; left: unset; right: 70%;}");
keyframes.appendRule("100% { width: 15%; right: 70%;}");
/* first animation - add animation class */
if (!($('div.absolute').hasClass('animateMovey'))) {
$('div.absolute').addClass('animateMovey');
/* animations after first - remove and reattach animation class with new keyframes */
} else {
$('div.absolute').removeClass('animateMovey');
$('div.absolute').addClass('animateMovey');
}
/* ensure animation occurs */
$('div.animateMovey').on('animationend', () => {
console.log('Animation ended');
})
}
})
$('div.three').click(() => {
if (!($('div.three').hasClass('active'))) {
$('div.active').removeClass('active');
$('div.three').addClass('active');
var left = ( parseInt($('div.absolute').css('left')) / $(window).width() ) * 100;
var width = 45 - left;
clearKeyframes();
keyframes.appendRule("0% { width: 15%; left: " + left + "%;}");
keyframes.appendRule("49.99% { width: " + width + "%; left: " + left + "%; right: 55%;}");
keyframes.appendRule("50% { width: " + width + "%; left: unset; right: 55%;}");
keyframes.appendRule("100% { width: 15%; right: 55%;")
if (!($('div.absolute').hasClass('animateMovey'))) {
$('div.absolute').addClass('animateMovey');
} else {
$('div.absolute').removeClass('animateMovey');
$('div.absolute').addClass('animateMovey');
}
$('div.animateMovey').on('animationend', () => {
console.log('Animation ended');
})
}
})
})
function findKeyframesRule(rule) {
var ss = document.styleSheets;
for (var i = 0; i < ss.length; ++i) {
for (var j = 0; j < ss[i].cssRules.length; ++j) {
if (ss[i].cssRules[j].type == window.CSSRule.KEYFRAMES_RULE && ss[i].cssRules[j].name == rule)
return ss[i].cssRules[j];
}
}
return null;
}
function clearKeyframes() {
for (var i = 0; i <= 3; ++i) {
if (keyframes[0]) {
var keyToRemove = keyframes[0].keyText;
keyframes.deleteRule(keyToRemove);
}
}
}
body {
margin: 0;
}
div.nav {
position: relative;
display: block;
overflow: hidden;
width: 100%;
}
div.nav div {
float: left;
width: 15%;
height: 75px;
}
div.nav div:hover {
opacity: 0.5;
}
div.one {
background-color: #7a7a7a;
}
div.two {
background-color: #9e9e9e;
}
div.three {
background-color: #bdbdbd;
}
.active {
box-shadow: inset 3px 5px 6px #000;
}
div.animateMovey {
animation-name: movey;
animation-duration: 0.6s;
animation-fill-mode: forwards;
animation-timing-function: ease-in-out;
}
div.relative {
position: relative;
width: 100%;
height: 20px;
}
div.absolute {
position: absolute;
background-color: #ff8c69;
width: 15%;
height: 100%;
}
#keyframes movey {
100% { }
}
<div>
<div class="nav">
<div class="one active"></div>
<div class="two"></div>
<div class="three"></div>
</div>
<div class="relative">
<div class="absolute"></div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
Interesting question. I'm not sure why the event is not re-triggering in this case, but will suggest a few changes to your approach:
Aim to animate transform and opacity instead of width and left, right
(https://developers.google.com/web/fundamentals/design-and-ux/animations/animations-and-performance)
One way to do this is to use a separate red element under each box, and slide it left or right using transform
Use animation-delay to create the lengthening and shortening effect
Try to reuse the animation logic, so it will work regardless of the number of items.
The challenging part of this effect is managing the opacity of each line. I've used animationEnd to help with that, and it appears to work fine.
Additional comments in the example code. It could be improved by handling clicks while animation is active, consolidating animation functions etc. You could also vary the animation duration depending on the number of items.
let boxes = null;
let lines = null;
let fromIndex = 0;
let toIndex = 0;
const ANIMATION_DURATION = 0.1; // seconds
const animation = {
animating: false,
lines: [],
direction: "right",
inOrOut: "in"
};
function getEls() {
boxes = [...document.querySelectorAll(".box")];
lines = [...document.querySelectorAll(".line")];
}
function setAnimationDuration() {
lines.forEach((line) => {
line.style.animationDuration = `${ANIMATION_DURATION}s`;
});
}
function addEvents() {
boxes.forEach((box, index) => {
box.addEventListener("click", () => {
// User has clicked the currently active box
if (fromIndex === index) return;
// Line is currently animating
if (animation.animating) return;
toIndex = index;
updateActiveBox();
handleLineAnimation();
});
});
document.addEventListener("animationend", (e) => {
// Maintain opacity on lines that animate in
if (animation.inOrOut === "in") {
e.target.style.opacity = 1;
}
});
}
function updateActiveBox() {
boxes[fromIndex].classList.remove("active");
boxes[toIndex].classList.add("active");
}
function updateActiveLine(line) {
lines[fromIndex].classList.remove("active");
line.classList.add("active");
}
function handleLineAnimation() {
animation.animating = true;
animation.lines = [];
if (toIndex > fromIndex) {
animation.direction = "right";
for (let i = fromIndex; i <= toIndex; i++) {
animation.lines.push(lines[i]);
}
} else {
animation.direction = "left";
for (let i = fromIndex; i >= toIndex; i--) {
animation.lines.push(lines[i]);
}
}
animate();
}
function animate() {
const wait = (animation.lines.length - 1) * ANIMATION_DURATION * 1000;
animation.inOrOut = "in";
animateIn();
setTimeout(() => {
resetLine();
updateActiveLine(lines[toIndex]);
animation.inOrOut = "out";
animateOut();
setTimeout(() => {
resetLine();
onAnimationComplete();
}, wait);
}, wait);
}
function animateIn() {
const {
direction,
lines
} = animation;
lines.forEach((line, index) => {
// index = 0 is currently active, no need to animate in
if (index > 0) {
line.classList.add(`animate-in-${direction}`);
line.style.animationDelay = `${(index - 1) * ANIMATION_DURATION}s`;
}
});
}
function animateOut() {
const {
direction,
lines
} = animation;
lines.forEach((line, index) => {
// lines.length - 1 is new active, don't animate out
if (index < lines.length - 1) {
line.classList.remove(`animate-in-${direction}`);
line.classList.add(`animate-out-${direction}`);
line.style.animationDelay = `${index * ANIMATION_DURATION}s`;
}
});
}
function resetLine() {
const {
direction,
lines,
inOrOut
} = animation;
lines.forEach((line) => {
line.classList.remove(`animate-${inOrOut}-${direction}`);
line.style.animationDelay = null;
// After animating out, remove inline opacity
if (inOrOut === "out") {
if (!line.classList.contains("active")) {
line.style.opacity = "";
}
}
});
}
function onAnimationComplete() {
animation.animating = false;
fromIndex = toIndex;
}
function init() {
getEls();
setAnimationDuration();
addEvents();
}
function reset() {
fromIndex = 0;
init();
lines.forEach((line, index) => {
line.classList.remove('active');
line.style.opacity = "";
boxes[index].classList.remove('active');
});
boxes[0].classList.add("active");
lines[0].classList.add("active");
}
init();
// DEBUG
document.getElementById("debug").addEventListener("change", (e) => {
document.querySelector("nav").classList.toggle("debug-on");
});
document.getElementById("add").addEventListener("click", (e) => {
const div = document.createElement("div");
div.classList.add("box");
div.innerHTML = '<div class="new"></div><span class="line"></span>';
document.querySelector("nav").appendChild(div);
reset();
});
document.getElementById("remove").addEventListener("click", (e) => {
const indexToRemove = boxes.length - 1;
if (indexToRemove > 0) {
const box = boxes[indexToRemove];
box.parentNode.removeChild(box);
reset();
}
});
nav {
display: flex;
flex-wrap: wrap;
overflow: hidden;
}
.debug-on .line {
border: 1px solid;
box-sizing: border-box;
opacity: 0.2;
}
.box {
display: flex;
flex-direction: column;
position: relative;
float: left;
flex: 0 0 15%;
/* Allows the line to slide left or right with opacity: 1 */
overflow: hidden;
}
.box>div {
cursor: pointer;
height: 75px;
}
.one {
background-color: #7a7a7a;
}
.two {
background-color: #9e9e9e;
}
.three {
background-color: #bdbdbd;
}
.new {
background-color: pink;
border: 1px solid;
box-sizing: border-box;
}
.line {
background-color: #ff8c69;
height: 20px;
opacity: 0;
pointer-events: none;
width: 100%;
animation-fill-mode: forwards;
animation-timing-function: linear;
}
.active>div {
box-shadow: inset 3px 5px 6px #000;
}
.box:hover div {
opacity: 0.5;
}
.line.active {
opacity: 1;
}
.line.show {
opacity: 1;
}
.animate-in-right {
animation-name: SLIDE_IN_RIGHT;
}
.animate-out-right {
animation-name: SLIDE_OUT_RIGHT;
}
.animate-in-left {
animation-name: SLIDE_IN_LEFT;
}
.animate-out-left {
animation-name: SLIDE_OUT_LEFT;
}
#keyframes SLIDE_IN_RIGHT {
from {
opacity: 1;
transform: translateX(-100%);
}
to {
opacity: 1;
transform: translateX(0);
}
}
#keyframes SLIDE_OUT_RIGHT {
from {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 1;
transform: translateX(100%);
}
}
#keyframes SLIDE_IN_LEFT {
from {
opacity: 1;
transform: translateX(100%);
}
to {
opacity: 1;
transform: translateX(0);
}
}
#keyframes SLIDE_OUT_LEFT {
from {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 1;
transform: translateX(-100%);
}
}
/* for demo only */
.debug {
background: #eee;
padding: 1rem;
display: inline-flex;
flex-direction: column;
font: 14px/1 sans-serif;
position: fixed;
bottom: 0;
right: 0;
}
.debug button {
margin-top: 1rem;
padding: .25rem;
}
<nav>
<div class="box active">
<div class="one"></div>
<span class="line active"></span>
</div>
<div class="box">
<div class="two"></div>
<span class="line"></span>
</div>
<div class="box">
<div class="three"></div>
<span class="line"></span>
</div>
</nav>
<br><br>
<div class="debug">
<label for="debug">Debug Lines <input type="checkbox" id="debug">
</label>
<button id="add">Add cell</button>
<button id="remove">Delete cell</button>
</div>

How dynamically replace keyframes value in css animation while using boolean in state?

I created slide animation for my drop out menu, now I need to change the keyframes to make this menu not just disappear but smoothly hide in the same way it appears on the screen.
The issue is that I'm missing something or using the incorrect syntax for some of my approaches.
link to the repo:
https://github.com/kkdima/barva
For styling, I'm using styled-components, because it has these ThemeProvider features and variables options.
I was trying to fix this with the ThemeProvider but no success so far. It gives messages that something wrong with the props.
Also, I have 2 components in the same .js file, one of them is sharing state with another.
this.state = { menuTriggered: false };
this state is the reference to which keyframe should be animated, either bgAnimationOpen or bgAnimationClose.
In both of them, I have elements (Background and Ul) which need to be changed for the smooth backward animation.
The next code example has needed keyframes in the definition of Background div. The rest are stored in the separate KeyFrames.js file which I import.
Pardon me for such a long code, I just have styles inside my js files.
import Link from "next/link";
import React, { Component } from "react";
import styled, { ThemeProvider } from "styled-components";
import Logo from "./Logo";
import { menuAnimationOpen } from "../styles/KeyFrames";
import { menuToClose } from "../styles/KeyFrames";
import { closeToMenu } from "../styles/KeyFrames";
import { MenuToCloseBackwards } from "../styles/KeyFrames";
import { bgAnimationOpen } from "../styles/KeyFrames";
import { bgAnimationClose } from "../styles/KeyFrames";
class Menu extends Component {
constructor(props) {
super(props);
}
render() {
const { menuTriggered } = this.props;
return (
<div>
<ThemeProvider theme={theme}>
<Background />
</ThemeProvider>
<UL className="menuOpens">
<StyledLink href="/">
<Li>
<A>Home</A>
</Li>
</StyledLink>
<StyledLink href="/projects">
<Li>
<A>Projects</A>
</Li>
</StyledLink>
<StyledLink href="/about">
<Li>
<A>About</A>
</Li>
</StyledLink>
<StyledLink href="/contact">
<Li>
<A>Contact</A>
</Li>
</StyledLink>
</UL>
</div>
);
}
}
class SlideNav extends Component {
constructor(props) {
super(props);
this.state = { menuTriggered: false };
}
toggleMenu = () => {
this.setState({ menuTriggered: !this.state.menuTriggered });
};
render() {
const { menuTriggered } = this.state;
return (
<MenuWrapper className="menu">
<LogoAndMenuButton>
<Logo />
<MenuTextWrapper>
<Input type="checkbox" onClick={this.toggleMenu} />
<Span className="text-Menu">
<p>Menu</p>
</Span>
<Span className="text-Close">
<p>Close</p>
</Span>
</MenuTextWrapper>
</LogoAndMenuButton>
{menuTriggered ? (
<Menu
menuTriggered={this.state.menuTriggered}
>
{/* {this.props.children} */}
</Menu>
) : (
<div />
)}
</MenuWrapper>
);
}
}
export default SlideNav;
const StyledLink = styled(Link)``;
const MenuWrapper = styled.div`
width: 100vw;
box-sizing: border-box;
padding-left: 40;
background: grey;
`;
const LogoAndMenuButton = styled.div`
display: flex;
justify-content: space-between;
flex-direction: row;
margin: 0;
width: 100%;
`;
// MENU/CLOSE BUTTON STYLES:
// MENU/CLOSE BUTTON STYLES:
// MENU/CLOSE BUTTON STYLES:
const MenuTextWrapper = styled.div`
background: none;
border: none;
overflow: hidden;
width: 65px;
height: 26px;
padding: 0;
z-index: 1;
margin: 40px 40px;
/* Menu to Close text animation 1st span */
input[type="checkbox"]:checked ~ .text-Menu {
animation: ${menuToClose} 300ms ease-in-out;
animation-fill-mode: forwards;
}
/* Menu to Close text animation 2nd span */
input[type="checkbox"]:checked ~ .text-Close {
color: #fff;
animation: ${closeToMenu} 300ms ease-in-out;
animation-fill-mode: forwards;
}
/* Close to Menu text animation 2nd span */
input[type="checkbox"]:not(:checked) ~ .text-Menu {
animation: ${MenuToCloseBackwards} 300ms ease-in-out;
animation-fill-mode: forwards;
}
`;
const Input = styled.input`
position: absolute;
width: 65px;
height: 26px;
z-index: 2;
cursor: pointer;
margin: 0;
opacity: 0;
`;
const Span = styled.span`
justify-content: center;
display: flex;
color: black;
padding-bottom: 10px;
z-index: -1;
p {
font-size: 1.2rem;
margin: 0;
}
`;
//END OF MENU/CLOSE BUTTON STYLES;
//END OF MENU/CLOSE BUTTON STYLES;
//END OF MENU/CLOSE BUTTON STYLES;
const menuOpen = bgAnimationOpen, menuClose = bgAnimationClose;
const Background = styled.div`
position: absolute;
top: 0;
background-color: #222222;
box-sizing: border-box;
height: 100vh;
width: 100vw;
#keyframes bgAnimationOpen {
from {
transform: translateY(-900px);
}
to {
visibility: block;
}
}
#keyframes bgAnimationClose {
from {
transform: translateY(900px);
}
to {
visibility: block;
}
}
animation: ${bgAnimationOpen} 0.8s
cubic-bezier(0.215, 0.61, 0.355, 1);
animation-fill-mode: forwards;
`;
Background.defaultProps = {
theme: {
open: "bgAnimationOpen"
}
};
const theme = {
close: "bgAnimationClose",
open: "bgAnimationOpen"
};
// UL NAVIGATION STYLES:
// UL NAVIGATION STYLES:
// UL NAVIGATION STYLES:
const UL = styled.ul`
position: absolute;
top: 140px;
font-size: 32px;
font-weight: bold;
max-width: 200px;
margin: 0px 0px 0px 40px;
padding: 0px;
li:nth-of-type(1) {
animation: ${menuAnimationOpen} 0.8s 0.5s cubic-bezier(0.35, 0.25, 0, 1.28);
animation-fill-mode: forwards;
}
li:nth-of-type(2) {
animation: ${menuAnimationOpen} 0.8s 0.6s cubic-bezier(0.35, 0.25, 0, 1.22);
animation-fill-mode: forwards;
}
li:nth-of-type(3) {
animation: ${menuAnimationOpen} 0.8s 0.7s cubic-bezier(0.35, 0.25, 0, 1.15);
animation-fill-mode: forwards;
}
li:nth-of-type(4) {
animation: ${menuAnimationOpen} 0.8s 0.8s cubic-bezier(0.35, 0.25, 0, 1.1);
animation-fill-mode: forwards;
}
`;
const Li = styled.li`
opacity: 0;
list-style: none;
color: pink;
text-align: left;
line-height: 45px;
transition: transform 400ms ease-in;
&:hover {
transform: scale(1.1);
}
`;
const A = styled.a`
cursor: pointer;
text-decoration: none;
`;
The needed outcome is working animation back and force just as on the attached gif. Much love if you can help with any clue to resolving this!
PS. It's slightly out of topic but maybe somebody knows how to fix the gif in the logo background? In the end of the cycle or in the very beggining it dissapears and creates delay in couple sec where there's no gif background. How to keep it in the loop all the time with no dissapearence? even though it's on the loop by default
animation: gifs 60s infinite running;

Nested function inside for loop function | add/remove class | keyframes animation

hi I have a for loop that is counting from 1 till infinity and I would like to change a background of a div and colour of the 'p'every time number is updated. It works when I make loop once (means for a first change but it does not rum animation for a second time. (numbers keep updating)
<div class="section" id="box">
<p id="demo">23375</p>
</div>
my CSS
.section {
background-color: blue;
margin: 1rem;
}
.pargraph-active,
.colorTransition {
animation: colorTransition 2s ease-in-out;
}
#keyframes colorTransition {
0% {
background-color: blue;
50% {
color: white
background-color: red;
}
100% {
background-color: blue;
}
}
and JS
let box = document.querySelector('#box');
let demo = document.querySelector('#demo');
function runAnimation() {
box.classList.add('colorTransition');
demo.classList.add('pargraph-active');
console.log(`Animation started`);
}
function initialState() {
box.classList.remove('colorTransition');
demo.classList.remove('pargraph-active');
console.log(`Initial State`);
}
(function theLoop(i) {
setTimeout(
function() {
demo.innerHTML = i;
if (i++) {
theLoop(i);
}
runAnimation();
},
2000,
initialState()
);
})(1);
You have some error in #keyframe syntax, you are missing } and also ; after color.
I fixed it and it work now, but i suggest that you only use css instead .
something like this would do
animation: colorTransition 2s ease-in-out infinite;
let box = document.querySelector('#box');
let demo = document.querySelector('#demo');
function runAnimation() {
box.classList.add('colorTransition');
demo.classList.add('pargraph-active');
console.log(`Animation started`);
}
function initialState() {
box.classList.remove('colorTransition');
demo.classList.remove('pargraph-active');
console.log(`Initial State`);
}
(function theLoop(i) {
setTimeout(
function() {
demo.innerHTML = i;
if (i++) {
theLoop(i);
}
runAnimation();
},
2000,
initialState()
);
})(1);
.section {
margin: 1rem;
background-color: blue;
}
.pargraph-active,
.colorTransition {
animation: colorTransition 2s ease-in-out;
}
#keyframes colorTransition {
0% {
background-color: blue;
}
50% {
color: white;
background-color: red;
}
100% {
background-color: blue;
}
}
<div class="section " id="box">
<p id="demo">23375</p>
</div>

ReactJS - Prevent initial animation of modal with react hooks

I built simple Modal component which will slide from bottom when opened. Animations are working fine when Modal trigger button clicked and backdrop clicked. But i am seeing slide-down animation at initial render of page. How can i prevent initial animation ?? I am specifically looking how to solve with react hooks.
Modal.js
import React, { useRef, useEffect } from 'react';
import { createPortal } from 'react-dom';
import './Modal.css';
const Modal = ({ isOpen, onClose, children }) => {
const modalEl = useRef(null);
const handleCoverClick = (e) => {
if (e.target.hasAttribute('modal')) {
onClose();
}
}
useEffect(() => {
const handleAnimationEnd = (event) => {
if (!isOpen) {
event.target.classList.remove('show');
event.target.classList.add('hide');
} else {
event.target.classList.remove('hide');
event.target.classList.add('show');
}
};
modalEl.current.addEventListener('animationend', handleAnimationEnd);
return () => modalEl.current.removeEventListener('animationend', handleAnimationEnd);
}, [isOpen]);
return createPortal(
<>
<div className={`ModalCover ${isOpen ? 'show' : 'hide'}`} onClick={handleCoverClick} modal="true"></div>
<div className={`ModalContainer ${isOpen ? 'slide-up' : 'slide-down'}`} ref={modalEl}>
{children}
</div>
</>,
document.body);
};
export default Modal;
Modal.css
.show {
display: block;
}
.hide {
display: none;
}
.slide-up {
transform: translateY(0%);
animation: slide-up 0.5s forwards;
}
.slide-down {
transform: translateY(100%);
animation: slide-down 0.5s forwards;
}
#keyframes slide-up {
0% { transform: translateY(100%); }
100% { transform: translateY(0%); }
}
#keyframes slide-down {
0% { transform: translateY(0%); }
100% { transform: translateY(100%); }
}
.ModalCover {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 10;
background-color: rgba(0, 0, 0, 0.15);
}
.ModalContainer {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 400px;
margin-top: calc(100vh - 400px);
z-index: 20;
}
demo (codesandbox) : https://codesandbox.io/s/l7x5p4k82m
Thanks!
A simpler way is to do this with classNames since direct DOM access is discouraged with DOM. modalEl.current ref is assigned after initial render, it can be used as a flag that a component was mounted:
<div className={`
ModalContainer
${isOpen ? 'slide-up' : 'slide-down'}
${!modalEl.current && 'hide'}
`} ref={modalEl}>
Applying hide class on component mount in useEffect may result in briefly shown modal animation.

Categories