In my React app, I am trying to fade a div in, wait a bit, and fade it back out. Everything is working great except the fade out.
My SCSS looks like this:
$orange-color: #DD7C15;
$white-color: #FFFFFF;
$black-color: #222222;
.App {
font-family: sans-serif;
text-align: center;
}
.message-banner {
position: fixed;
bottom: 0;
left: 0;
z-index: 100000;
width: 100vw;
color: $orange-color;
font-size: 4em;
text-align: center;
background-color: $white-color;
border: 2px solid $black-color;
opacity: 0.9;
animation: fadeIn 2s ease-in;
&.hide {
opacity: 0;
animation: fadeOut 2s ease-out;
}
}
#keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 0.9;
}
}
#keyframes fadeOut {
0% {
opacity: 0.9;
}
100% {
opacity: 0;
}
}
And my relevant React code:
const showBanner = () => {
setMessageBannerText("My sweet awesome banner!!");
setTimeout(() => {
setMessageBannerText("");
}, 3000);
};
const bannerClasses =
messageBannerText === "" ? "message-banner hide" : "message-banner";
I've created a sandbox showing what I am talking about.
https://codesandbox.io/s/brave-grass-q1y6j
Issue :
The animation is working fine but you are removing the content setMessageBannerText(""); while the animation, so it's not visible
Solution :
So instead of making content blank, you should maintain the state for animation
1) Solution :
const steps = {
0: "", // <--- blank coz message-banner has animation itself
1: "message-banner",
2: "message-banner hide"
};
export default function App() {
const messageBannerText = "My sweet awesome banner!!";
const [animationStep, setAnimationStep] = useState(0);
const showBanner = () => {
setAnimationStep(1);
setTimeout(() => {
// setMessageBannerText(""); // <---- issue
setAnimationStep(2);
}, 3000);
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={showBanner}>Show Banner</button>
<MessageBanner text={messageBannerText} classes={steps[animationStep]} />
</div>
);
}
WORKING DEMO :
2) Solution : ( with css changes, but you still need to follow above changes )
.message-banner {
position: fixed;
bottom: 0;
left: 0;
z-index: 100000;
width: 100vw;
color: $orange-color;
font-size: 4em;
text-align: center;
background-color: $white-color;
border: 2px solid $black-color;
opacity: 0;
&.animate {
opacity: 0;
animation: fadeInOut 5s ease-out;
}
}
// single animation for fade in and fade out
#keyframes fadeInOut {
0% {
opacity: 0;
}
30% {
opacity: 0.9;
}
70% {
opacity: 0.9;
}
100% {
opacity: 0;
}
}
const [show, setShow] = useState(false);
const showBanner = () => {
if (!show) { // <--- just for safe side, if animation is going on, then ignore state change
setShow(true);
setTimeout(() => {
setShow(false);
}, 5000);
}
};
const bannerClasses = show ? "message-banner animate" : "message-banner";
WORKING DEMO :
Hey I have edited your sandbox to achieve the result you desire:-
Changes:-
1) Added show and hide classes.
2) Introduced a boolean state for transition rather than depending on text because your message-banner div doesn't have its own height or width. We will simply let the text stay but hide the div away from the user.
3) Instead of animation, used transition since you're simply toggling between two states and you want to stick with those for rest of your application. With animation, you will have to do some more tricks for them to stick. Plus animation is useful for more complex scenario.
Related
I am trying to create an animation to a div with fixed position. basically I want that when 5 seconds pass a class is added to this div and an animation is made, but for some reason, it is not happening.
What am I doing wrong?
import { useRef, useEffect } from "react";
export default function Modal() {
const modal = useRef(null);
useEffect(() => {
setTimeout(() => {
modal.current.classList.add("modalShow");
}, 5000);
}, []);
return <div id="modal" ref={modal} className="modalContainer "></div>;
}
.modalContainer {
position: fixed;
height: 100%;
width: 100%;
background: red;
animation: all 5s ease-out;
transform: translateY(100%);
}
.modalShow {
transform: translateY(0%);
}
.modalHide {
transform: translateY(100%);
}
this is my live code:
Live Code
thanks!
You are not using #keyframes.
Change:
animation: all 5s ease-out;
to:
transition: all 5s ease-out;
The code below triggers the popup in react, which enters when a user clicks a new order button in the interface. There is a button that whne clicked, the popup exits, but without animating
export const NewOrder = () => {
const [selected, setSelected] = useState(false);
const newOrder = () => {
setSelected(!selected);
};
return (
<Fragment>
<div className='dashboard-main-neworder' onClick={newOrder}>
<img alt='New Order' src={addorder}></img>
<span>New Order</span>
</div>
{selected && <NewOrderForm />}
</Fragment>
);
};
This is the animation
#keyframes popup {
0% {
top: -100%;
opacity: 0;
}
70% {
top: 60%;
}
100% {
top: 50%;
opacity: 1;
}
}
assignment in class is here
.popup {
width: 55rem;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 999;
background: #ffffff;
border-radius: 1rem;
transition: all 0.2s ease-in-out;
animation: popup ease 0.5s;
That's kinda hard to do because when you unmount a component using react it just removes it from the tree, my suggestion would be using something like react-transition-group
I change my className of one div for every 3 seconds(with state change, using setInterval).
And each classes has different background-image.
I want to fade in and out that background images whenever they change, with transition css. I saw some examples for more simple cases, but I have more than two elements to change/ and change pictures with state change.
How can I do that?
I uploaded my code on: https://stackblitz.com/edit/react-ubxffz
But I cannot upload images on this page so temporarily replaced it to background-color in this page.
this is Image Slide component.
const imgUrls = [
1,2,3
];
class ImageSlide extends Component {
render() {
const { url } = this.props;
const Text=...
return (
<div>
<div className={`pic${url}`}>
<p className="p1_1">{Text.p1_1}</p>
</div>
</div>
);
}
this is App component, which calls ImageSlide.
class App extends Component {
constructor (props) {
super(props);
currentImageIndex: 0,
};
}
// ...
componentDidMount() {
this.interval = setInterval(() => {
this.nextSlide(); //this function change the index state.
}, 3000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
// ...
<ImageSlide url={imgUrls[this.state.currentImageIndex]} />
this is css for each class, setting background image.
.pic1 {
background-image: url('images/img_01.png');
}
.pic2 {
background-image: url('images/img_02.png');
}
.pic3 {
background-image: url('images/img_03.png');
}
It works like this: To fade backgrounds: you need to have two elements with different background-images that are stacked on top of each other and wich then are cross faded
Working code in stackblitz.
Working code without framework:
const imgUrls = [
1,2,3
];
let currentIndex = 0;
const lastIndex = imgUrls.length - 1;
const nextSlide = () => {
currentIndex++;
currentIndex = currentIndex % (lastIndex + 1)
// #See https://css-tricks.com/restart-css-animation/
const elm = document.getElementById('root')
.querySelector('[class^="pic"],[class*=" pix"]');
elm.className = `pic${currentIndex+1}`
const newone = elm.cloneNode(true);
elm.parentNode.replaceChild(newone, elm);
}
interval = setInterval(() => {
console.log()
nextSlide(); //this function change the index state.
}, 3000);
#root {
position: relative;
width: 640px;
height: 480px;
}
#root .front,
#root .back {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
#root .front {
z-index: 2;
opacity: 0;
}
#root .back {
z-index: 1;
opacity: 1;
}
#root [class^="pic"] .front,
#root [class*=" pic"] .front {
-webkit-animation: in 3s 0s;
animation: in 3s 0s;
}
#root .pic1 .front,
#root .pic2 .back {
background-image: url("https://picsum.photos/640/480?image=1");
}
#root .pic1.init .back {
background-image: none;
}
#root .pic2 .front,
#root .pic3 .back {
background-image: url("https://picsum.photos/640/480?image=2");
}
#root .pic3 .front,
#root .pic1 .back {
background-image: url("https://picsum.photos/640/480?image=3");
}
#-webkit-keyframes in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
#keyframes in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
<div id="root">
<div class="pic1 init">
<div class="front"></div>
<div class="back"></div>
</div>
</div>
Use animation to each class as below:
See working code
화이팅!!
.pic1 {
background-image: url('images/img_01.jpg');
animation: fade 3s infinite;
}
.pic2 {
background-image: url('images/img_02.jpg');
animation: fade 3s infinite;
}
.pic3 {
background-image: url('images/img_03.jpg');
animation: fade 3s infinite;
}
#keyframes fade {
0%,100% { opacity: 0 }
50% { opacity: 1 }
}
I found a solution with react hooks
import React, { useState, useEffect } from "react";
const heroImage = ["hero1.svg", "hero2.svg"];
export default function Home() {
const [activeIndex, setactiveIndex] = useState(0);
useEffect(() => {
setInterval(() => {
currentImageIndex === 0 ? setactiveIndex(1) : setactiveIndex(0);
}, 3000);
});
return (
<div>
<img
className={styles.featureImage}
src={"img/" + heroImage[activeIndex]}
alt={"imageTitle"}
/>
</div>
);
}
And CSS will look something like this
.featureImage {
height: 600px;
width: 600px;
animation: fade 3s infinite;
}
This should be so simple, but I've been breaking my head over this for days. I'm trying to animate my page transitions. The problem is the docs SUCK. I've followed them over and over and tried every which way, but can't get it to work.
What I want to do is slide my pages gracefully either right or left, and fade the one that is unmounting gracefully out behind it. Simple right? I am NOT using React Router for my pages.
I've tried a variety of solutions for this, but the problem seems to be on the unmounting. When the page is replaced, the existing page gets unmounted before it can transition out. I'm posting my attempt with react-transition-group, though at this point, I'll accept any other solution that works. I'm not sure react-transition-group is being actively maintained actually, because there are numerous other postings for help with 0 responses.
So on my app container I want to put something like this:
<App>
<PageSlider>
<Page key={uniqueId} /> <==this will be "swapped" through (Redux) props
</PageSlider>
So, from what I've read, I have to use a TransitionGroup container as my PageSlider for this, so that it will manage the entering and exiting of my page. So here goes:
class PageSlider extends Component {
constructor(props) {
super(props);
}
render() {
return (
<TransitionGroup
component="div"
id="page-slider"
childFactory={child => React.cloneElement(child,
{classNames: `page-${this.props.fromDir}`, timeout: 500}
)}
>
{this.props.children}
</TransitionGroup>
);
}
}
I also read I need to do a "child Factory" to enable the exiting stuff. There was absolutely no example of this I could find in the docs. Since the pages will come from different directions, I will pass to this the direction from which I want to slide the page, which will tell the page what class it gets.
Now, as for the page itself, I have wrapped it in a CSSTransition like so. There were no good examples in the docs of how this all gets passed down, so I'm really confused what to do here:
class Page extends Component {
constructor(props) {
super(props);
}
render() {
return (
<CSSTransition> <==????????
{this.props.children} Do props get passed down?
</CSSTransition> Which ones?
); Does "in" get taken care of?
}
}
And just to finish the styles will be applied in CSS something like this:
.page {
display: flex;
flex-direction: column;
height: 100%;
position: absolute;
top: 0;
bottom: 0;
-webkit-transition: all 500ms ease-in-out;
transition: all 500ms ;
}
//go from this
.page-right-enter {
-webkit-transform: translate3d(100%, 0, 0);
transform: translate3d(100%, 0, 0);
}
//to this
.page-right-enter-active {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
//exiting go from this
.page-right-exit {
opacity: 1;
}
//to this
.page-right-exit-active {
opacity: 0;
}
All of these components will be connected through Redux so they know when a new page has been triggered and which direction has been called.
Can someone PLEEEEASE help me on this? I've literally spent days and tried every library out there. I'm not wedded to react-transition-group! Any library that works on the unmount I'll try. Why is this not easier?
OK. Well, I struggled with this for WAAAAY too long. I finally dumped react-transition-group, and went pure CSS. Here's my solution.
PageSlider.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
require('./transitions.scss');
const BlankPage = (props) => <div className="placeholder"></div>;
class PageSlider extends Component {
constructor(props) {
super(props);
this.state = {
nextRoute: props.page,
pages: {
A: {key: 'A', component: BlankPage, className: 'placeholder'},
B: {key: 'B', component: BlankPage, className: 'placeholder'},
},
currentPage: 'A'
};
}
componentDidMount() {
//start initial animation of incoming
let B = {key: 'b', component: this.state.nextRoute, className: 'slideFromRight'}; //new one
let A = Object.assign({}, this.state.pages.A, {className: 'slideOutLeft'}); //exiting
this.setState({pages: {A: A, B: B}, currentPage: 'B'});
}
componentWillReceiveProps(nextProps) {
if (nextProps.page != this.state.nextRoute) {
this.transition(nextProps.page, nextProps.fromDir);
}
}
transition = (Page, fromDir) => {
if (this.state.nextRoute != Page) {
let leavingClass, enteringClass;
let pages = Object.assign({}, this.state.pages);
const current = this.state.currentPage;
const next = (current == 'A' ? 'B' : 'A');
if (fromDir == "right") {
enteringClass = 'slideFromRight';
leavingClass = 'slideOutLeft';
} else {
enteringClass = 'slideFromLeft';
leavingClass = 'slideOutRight';
}
pages[next] = {key: 'unique', component: Page, className: enteringClass};
pages[current].className = leavingClass;
this.setState({pages: pages, nextRoute: Page, currentPage: next});
}
}
render() {
return (
<div id="container" style={{
position: 'relative',
minHeight: '100vh',
overflow: 'hidden'
}}>
{React.createElement('div', {key: 'A', className: this.state.pages.A.className}, <this.state.pages.A.component />)}
{React.createElement('div', {key: 'B', className: this.state.pages.B.className} , <this.state.pages.B.component />)}
</div>
);
}
}
PageSlider.propTypes = {
page: PropTypes.func.isRequired,
fromDir: PropTypes.string.isRequired
};
export default PageSlider;
transition.scss
.placeholder {
position: absolute;
left: 0;
width: 100vw;
height: 100vh;
background: transparent;
-webkit-animation: slideoutleft 0.5s forwards;
-webkit-animation-delay: 10;
animation: slideoutleft 0.5s forwards;
animation-delay: 10;
}
.slideFromLeft {
position: absolute;
left: -100vw;
width: 100vw;
height: 100vh;
-webkit-animation: slidein 0.5s forwards;
-webkit-animation-delay: 10;
animation: slidein 0.5s forwards;
animation-delay: 10;
}
.slideFromRight {
position: absolute;
left: 100vw;
width: 100vw;
height: 100vh;
-webkit-animation: slidein 0.5s forwards;
-webkit-animation-delay: 10;
animation: slidein 0.5s forwards;
animation-delay: 10;;
}
.slideOutLeft {
position: absolute;
left: 0;
width: 100vw;
height: 100vh;
-webkit-animation: slideoutleft 0.5s forwards;
-webkit-animation-delay: 10;
animation: slideoutleft 0.5s forwards;
animation-delay: 10;
}
.slideOutRight {
position: absolute;
left: 0;
width: 100vw;
height: 100vh;
-webkit-animation: slideoutright 0.5s forwards;
-webkit-animation-delay: 10;
animation: slideoutright 0.5s forwards;
animation-delay: 10;
}
#-webkit-keyframes slidein {
100% { left: 0; }
}
#keyframes slidein {
100% { left: 0; }
}
#-webkit-keyframes slideoutleft {
100% { left: -100vw; opacity: 0 }
}
#keyframes slideoutleft {
100% { left: -100vw; opacity: 0}
}
#-webkit-keyframes slideoutright {
100% { left: 100vw; opacity: 0}
}
#keyframes slideoutright {
100% { left: 100vw; opacity: 0}
}
Passing in the next component, which is my react Page component, called like so:
app.js
<div id="app">
<PageSlider page={this.state.nextRoute} fromDir={this.state.fromDir}/>
</div>
I do have a running code sample (http://jsfiddle.net/Fuunq/) which shows a slide down animation using angular.js (1.2.0). But there are two issues that I don't know how to solve:
a) When clicking on the 'Add item' link, the animation first moves down the 'Add item' link and then slides in the new item from the top. How can this be changed, that the 'Add item' link slides down together with the new appearing item?
b) How can I prevent that the items do fade in when the page gets loaded for the first time?
HTML
<div ng-controller="MyCtrl">
<div ng-repeat="item in items" class="animation-slide-down">
<div class="item">
<div>Name: {{item.name}}</div>
<div>Color: {{item.color}}</div>
</div>
</div>
Add item
</div>
CSS
.item {
margin-top: 5px;
padding: 15px;
background-color: #34ac54;
border: 1px solid black;
}
#-webkit-keyframes slide_down {
0% {
-webkit-transform: translateY(-100%);
opacity: 0;
z-index: -100;
}
99% {
z-index: -100;
}
100% {
-webkit-transform: translateY(0);
opacity: 1;
z-index: 0;
}
}
#keyframes slide_down {
0% {
transform: translateY(-100%);
opacity: 0;
z-index: -100;
}
99% {
z-index: -100;
}
100% {
transform: translateY(0);
opacity: 1;
z-index: 0;
}
}
.animation-slide-down.ng-enter {
-webkit-animation: slide_down 3s ease-in;
animation: slide_down 4s ease-in;
}
JS
var myApp = angular.module('myApp', ['ngAnimate']);
function MyCtrl($scope) {
$scope.name = 'foo';
$scope.items = [
{name: 'Item 1', color: 'blue'},
{name: 'Item 2', color: 'red'}
]
$scope.addItem = function() {
$scope.items.push({name: 'Another item', color: 'black'})
}
}
Thanks for your help!
a) as far as i know this can only be achieved with a negative margin-top transitions to your target value 5px. There is one problem with this: you have to know the exact height of one item.
#-webkit-keyframes slide_down {
0% {
margin-top: -68px;
opacity: 0;
z-index: -100;
}
99% {
z-index: -100;
}
100% {
margin-top:5px;
opacity: 1;
z-index: 0;
}
}
b) you can use the $animate service. The service has an enabled method, so you may disable or enable the animations at any time. here is your controller code that disables the animation on start and after the start enables the animation. the trick is the $timeout service. with that you defer activating the animations until the next digest cycle happens:
function MyCtrl($scope, $animate, $timeout) {
$animate.enabled(false);
$scope.name = 'foo';
$scope.items = [
{name: 'Item 1', color: 'blue'},
{name: 'Item 2', color: 'red'}
]
$scope.addItem = function() {
$scope.items.push({name: 'Another item', color: 'black'})
}
$timeout(function(){
$animate.enabled(true);
});
}
here is the working example:
http://jsfiddle.net/CKF47/