Reactjs: Wait for animation to finish - reactjs

inside my react app, users are able to enter data that is saved on the server. The data is saved immediatly, users don't have to press any "save" button. I want to display a short animation (similar to https://codepen.io/Sixclones/pen/VBdeXL) whenever I'm sending data to the server. After doing some research, I figured out that I should use react-transition-group (http://reactcommunity.org/react-transition-group/css-transition).
I made a component savingIcon:
const SavingIcon: React.SFC<SavingIconProps> = ({
className,
saving,
}) => {
return (
<div className={ classNames(className, "saving-icon") }>
<CSSTransition
timeout={ 200 }
classNames="saving"
in={ saving }
>
<div className="saving-balls">
<div className="saving-balls__item" />
<div className="saving-balls__item" />
<div className="saving-balls__item" />
</div>
</CSSTransition>
</div>
);
}
Whenever something is being saved, saving is set to true and I'd like to display the animation.
Inside saving-icon.scss I put the following CSS (I tried out several approaches, therefore there might be some unnecessary css):
#keyframes bouncing {
0% {
transform: translate3d(0, 10px, 0) scale(1.2, 0.85);
}
100% {
transform: translate3d(0, -20px, 0) scale(0.9, 1.1);
}
}
div.saving-icon {
position: sticky;
top: 15vh;
display: flex;
flex-direction: column;
justify-content: end;
height: 84vh;
$anim-drt: 0.4s;
$anim-ease: cubic-bezier(.6, .05, .15, .95);
.saving-enter {
div.saving-balls{
&__item {
background-color: $success-color;
transition: background-color 20ms;
}
}
}
.saving-enter-active {
div.saving-balls{
&__item {
background-color: $active-color;
transition: background-color 20ms;
}
&:nth-child(1) {
animation: bouncing $anim-drt alternate infinite $anim-ease;
transition: animation 1000ms;
}
&:nth-child(2) {
animation: bouncing $anim-drt $anim-drt/4 alternate infinite
$anim-ease backwards;
transition: animation 1000ms;
}
&:nth-child(3) {
animation: bouncing $anim-drt $anim-drt/2 alternate infinite
$anim-ease backwards;
transition: animation 1000ms;
}
}
}
.saving-exit {
div.saving-balls{
&__item {
background-color: $active-color;
transition: background-color 20ms;
}
&:nth-child(1) {
animation: bouncing $anim-drt alternate infinite $anim-ease;
transition: animation 1000ms;
}
&:nth-child(2) {
animation: bouncing $anim-drt $anim-drt/4 alternate infinite
$anim-ease backwards;
transition: animation 1000ms;
}
&:nth-child(3) {
animation: bouncing $anim-drt $anim-drt/2 alternate infinite
$anim-ease backwards;
transition: animation 1000ms;
}
}
}
.saving-exit-active {
div.saving-balls{
&__item {
background-color: $success-color;
transition: background-color 20ms;
transition: animation 1000ms;
}
}
}
div.saving-balls {
width: 3em;
height: 10vh;
z-index: 5;
display: flex;
justify-content: space-between;
align-items: center;
&__item {
width: 0.7em;
height: 0.7em;
border-radius: 50%;
background: $success-color;
}
}
}
My issue is the following:
Usually saving something only takes very short time; not enough time to actually run the animation once. My balls just become red for a split second and return being green (active and success color are some type or red and green). I would like to have the animation running a longer period of time, something around 2 seconds. I tried some hacks using effects, states and timeouts, but they didn't work well and I'd rather use a correct solution instead of some dirty hack.
I'm not very familiar with css transitions and animations, neither with react-transition-group. I hope for some existing easy way to play an animation for a certain minimum amount of time (if the connection is weak, the animation should show until the data is saved).
As an alternative, I accept different suggestions on how to tell the user that his input has been saved instead of an animation at the corner of the page. Currently, everything the user enters is saved, but he might not notice that this happened and search for a "save" button.

For anybody with a similar issue: I solved the issue by using useState and only changing the state value for saving if it indeed changed. I didn't use a transition group.
The working saving component:
const SavingIcon: React.SFC<SavingIconProps> = ({
className,
saving,
}) => {
const [ isSaving, setIsSaving ] = useState(false);
useEffect(() => {
if (isSaving !== saving) setIsSaving(saving);
}, [ saving ]);
return (
<div className={ classNames(className, "saving-icon", {
"saving-active": isSaving
}) }>
<div className="saving-balls">
<div className="saving-balls__item" />
<div className="saving-balls__item" />
<div className="saving-balls__item" />
</div>
</div>
);
}
scss:
#keyframes bouncing {
0% {
transform: translate3d(0, 10px, 0) scale(1.2, 0.85);
}
100% {
transform: translate3d(0, -20px, 0) scale(0.9, 1.1);
}
}
div.saving-icon {
position: fixed;
top: 86px;
right: 1.3vw;
$anim-drt: 0.4s; /* duration */
$anim-ease: cubic-bezier(.6, .05, .15, .95);
&.saving-active {
div.saving-balls {
div.saving-balls__item {
background-color: $active-color;
transition: background-color 20ms;
&:nth-child(1) {
animation: bouncing $anim-drt alternate infinite $anim-ease;
}
&:nth-child(2) {
animation: bouncing $anim-drt $anim-drt/4 alternate infinite
$anim-ease backwards;
}
&:nth-child(3) {
animation: bouncing $anim-drt $anim-drt/2 alternate infinite
$anim-ease backwards;
}
}
}
}
div.saving-balls {
width: 3em;
height: 30px;
z-index: 5;
display: flex;
justify-content: space-between;
align-items: center;
&__item {
width: 0.7em;
height: 0.7em;
border-radius: 50%;
background: $success-color;
}
}
}

Related

How do I use animation that spreads up and down when I make a drop-down menu?

I'm making a drop-down menu now. When I put my mouse on a specific part, the drop-down menu opens and when the mouse goes out of the browser, I want to make the drop-down menu disappear. I made a state value called isMouseOver to return true when the mouse goes up and to use jquery to return false when the mouse goes out of the browser. When the mouse goes up, I used animation to implement it as I wanted, but I want to implement animation that slowly disappears up when the mouse goes out of the browser, but this part doesn't work well. I put this part in an animation as well, but the animation overlapped and it was executed strangely. To sum up the question, how do I put the animation when isMouseOver becomes false and the display becomes none?
import { useState } from 'react';
import styled, {keyframes} from 'styled-components';
import $ from 'jquery';
// animation when mouseover
const dropdownAnimation = keyframes`
0% {
transform: translateY(-30%);
}
100% {
transform: translateY(0);
}
`
function HeaderRight () {
// Check mouseover
const [isMouseOver, setIsMouseOver] = useState(false)
// return true when mouse over
const ActMouseOver = () => {
setIsMouseOver(true)
}
// return false when mouse over
const ActMouseLeave = () => {
setIsMouseOver(false)
}
// return false when the mouse is out of the browser
$(document).mouseleave(function () {
ActMouseLeave();
});
return (
<HeaderRightWrap isMouseOver={isMouseOver} onMouseEnter={ActMouseOver}>
<ul className='HeaderRightWrapUl'>
<li className='HeaderRightWrapUlLi'>
<ul className='HeaderRightWrapUlLiUl'>
<li className='HeaderRightWrapUlLiUlLi'>
Hello
</li>
</ul>
</li>
</ul>
</HeaderRightWrap>
)
}
export default HeaderRight;
const HeaderRightWrap = styled.div`
position: relative;
box-sizing: border-box;
bottom: 40px;
.HeaderRightWrapUl {
height: 100%;
margin: 0 auto;
padding: 0;
list-style: none;
box-sizing: border-box;
display: block;
margin-inline-end: 0px;
margin-inline-start: 0px;
margin-block-start: 1em;
margin-block-end: 1em;
padding-inline-start: 40px;
}
.HeaderRightWrapUlLi {
z-index: 1000;
position: relative;
float: left;
width: auto;
height: 100%;
padding-right: 80px;
transition: padding .3s;
}
.HeaderRightWrapUlLiUl {
display: ${props=>props.isMouseOver===true?'auto':'none'};
position: absolute;
animation: ${dropdownAnimation} 0.3s ease;
}
.HeaderRightWrapUlLiUlLi {
margin-top: 0;
}
`

Mapped buttons that have opacity of 0 are showing, and are overlapping when resizing the window

I've successfully mapped over text and have styled it with transition effects when going from one slide to the next as you can see here:
Following a similar concept with buttons isn't working. There should only be one button active per slide like you see here:
I want the buttons to have the same effect as the text, but I'm getting behaviors like you see here:
As you can see, there is no transition effect on the button when clicking to the second slide, and it also appears in a lower spot.
And lastly, when resizing the window, buttons are overlapping like you see here:
Don't know what to try next.
Here's the ImageSlider component:
import { useState } from "react";
import { SliderData } from "../data";
import { categories } from "../data";
import ShopNowButtonActive from "./ShopNowButtonActive";
import { IoIosArrowBack } from "react-icons/io";
import { IoIosArrowForward } from "react-icons/io";
import "./ImageSlider.css";
import ShopNowButton from "./ShopNowButton";
const ImageSlider = ({ slides }) => {
const [current, setCurrent] = useState(0);
const length = slides.length;
const nextSlide = () => {
setCurrent(current === length - 1 ? 0 : current + 1);
};
const prevSlide = () => {
setCurrent(current === 0 ? length - 1 : current - 1);
};
return (
<div className="slider">
<IoIosArrowBack className="left-arrow" onClick={prevSlide} />
{SliderData.map((slide, index) => (
<div key={slide.id}>
<img
src={slide.img}
alt=""
className={index === current ? "slide active" : "slide"}
/>
<div className="info-container">
<div className={index === current ? "title active" : "title"}>
{slide.title}
</div>
<div className={index === current ? "desc active" : "desc"}>
{slide.desc}
</div>
{categories.map((item, index) =>
index === current ? (
<ShopNowButtonActive item={item} />
) : (
<ShopNowButton item={item} />
)
)}
</div>
</div>
))}
<IoIosArrowForward className="right-arrow" onClick={nextSlide} />
</div>
);
};
export default ImageSlider;
The css file:
.slider {
height: 90vh;
margin-bottom: 0.5rem;
}
.left-arrow {
position: absolute;
top: 45%;
left: 32px;
font-size: 2rem;
cursor: pointer;
opacity: 0.5;
z-index: 1;
}
.slide.active {
opacity: 1;
width: 100%;
height: 88%;
object-fit: cover;
object-position: center;
-webkit-clip-path: polygon(100% 0, 100% 80%, 50% 100%, 0 80%, 0% 0%);
clip-path: polygon(100% 0, 100% 80%, 50% 100%, 0 80%, 0% 0%);
}
.slide {
opacity: 0;
transition: 500ms opacity ease-in-out;
width: 100%;
height: 88%;
object-fit: cover;
object-position: center;
position: absolute;
-webkit-clip-path: polygon(100% 0, 100% 80%, 50% 100%, 0 80%, 0% 0%);
clip-path: polygon(100% 0, 100% 80%, 50% 100%, 0 80%, 0% 0%);
}
.info-container {
display: flex;
flex-direction: column;
width: 40%;
height: 100%;
position: absolute;
top: 45%;
right: 30px;
}
.title.active {
opacity: 1;
transition-delay: 700ms;
font-size: 4rem;
}
.title {
opacity: 0;
transition: 200ms opacity ease-in-out;
font-size: 4rem;
}
.desc.active {
opacity: 1;
padding-top: 1.5em;
transition-delay: 700ms;
font-size: 1.25rem;
font-weight: 500;
letter-spacing: 3px;
}
.desc {
opacity: 0;
padding-top: 1.5em;
transition: 200ms opacity ease-in-out;
font-size: 1.25rem;
font-weight: 500;
letter-spacing: 3px;
}
.right-arrow {
position: absolute;
top: 45%;
right: 32px;
font-size: 2rem;
cursor: pointer;
opacity: 0.5;
z-index: 1;
}
The ShopNowButtonActive component:
import React from "react";
import styled from "styled-components/macro";
import { Link } from "react-router-dom";
const ButtonActive = styled.button`
opacity: 1;
padding: 0.5rem;
margin-top: 2.5rem;
width: 8rem;
font-size: 20px;
background-color: transparent;
cursor: pointer;
transition-delay: 700ms;
`;
const ShopNowButtonActive = ({ item }) => {
return (
<Link to={`/products/${item.cat}`}>
<ButtonActive>SHOP NOW</ButtonActive>
</Link>
);
};
export default ShopNowButtonActive;
And finally, the ShopNowButton component:
import React from "react";
import styled from "styled-components/macro";
import { Link } from "react-router-dom";
const Button = styled.button`
opacity: 0;
/* display: none; */
padding: 0.5rem;
margin-top: 2.5rem;
width: 8rem;
font-size: 20px;
background-color: transparent;
cursor: pointer;
transition: 200ms opacity ease-in-out;
`;
const ShopNowButton = ({ item }) => {
return (
<Link to={`/products/${item.cat}`}>
<Button>SHOP NOW</Button>
</Link>
);
};
export default ShopNowButton;
(Sorry for the use of both an external css file and styled components.)
Any suggestions?
I have recreated the above scenario using some static data. I have modified some of the css. It is working as expected. I also observed that the only major difference between ShopNowButton and ShopNowButtonActive was opacity property which hides the element. The reason you are observing 2 buttons because they were all there in the dom actually.(They were not hiding properly due to which we are observing more shop now buttons and every time we click on next icon the corresponding button is being displayed. Basically all the buttons are there on the page itself.)
Please find the sandbox url below.
https://codesandbox.io/s/stackoverflow-6u05e7?file=/src/ImageSlider/ImageSlider.js

How to put a spinner inside of the button in React?

See current Spinner
Hello,
I like to put the current spinner inside the Login button, but it is located outside of the button. Any help? Thank you in advance!
Here are my codes.
[Login.js] try to load when user clicks the Login button.
...
<div className="auth-form-inputLine auth-form-submitBtn-container" id="auth-login-submitBtnContainer">
<button onClick={this.handleFormSubmit} className="auth-form-submitBtn">
{this.state.submitting ? <ButtonLoader /> : <h2 className="text">Login</h2>}
</button>
</div>
...
[ButtonLoader.js] There are moving three dots.
import React from "react";
import "./styles/ButtonLoader.css";
const ButtonLoader = () => {
return (
<div class="spinner">
<div class="bounce1"></div>
<div class="bounce2"></div>
<div class="bounce3"></div>
</div>
);
};
export default ButtonLoader;
[ButtonLoader.css]
.spinner {
margin: 100px auto 0;
width: 70px;
text-align: center;
}
.spinner > div {
width: 18px;
height: 18px;
background-color: #333;
border-radius: 100%;
display: inline-block;
-webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
}
.spinner .bounce1 {
-webkit-animation-delay: -0.32s;
animation-delay: -0.32s;
}
.spinner .bounce2 {
-webkit-animation-delay: -0.16s;
animation-delay: -0.16s;
}
#-webkit-keyframes sk-bouncedelay {
0%, 80%, 100% { -webkit-transform: scale(0) }
40% { -webkit-transform: scale(1.0) }
}
#keyframes sk-bouncedelay {
0%, 80%, 100% {
-webkit-transform: scale(0);
transform: scale(0);
} 40% {
-webkit-transform: scale(1.0);
transform: scale(1.0);
}
}

Leave transition not being applied for ReactCSSTransitionGroup

I have the following component:
<ReactCSSTransitionGroup
transitionName={transitionName}
transitionAppear={true}
transitionLeave={true}
>
{children}
</ReactCSSTransitionGroup>
And the following css classes:
.slide-appear {
max-height: 0;
}
.slide-appear.slide-appear-active {
max-height: 100vh;
overflow: visible;
transition: max-height 500ms ease-in;
}
.slide-leave {
max-height: 100vh;
}
.slide-leave.menu-leave-active {
overflow: hidden;
max-height: 0px;
transition: max-height 500ms ease;
}
The transitions are working for appear but not for leave.
According to the documentation, you disabled leave animation by using this prop:
transitionLeave={false}
Just remove it, problem solved.
Also as quick reminder, transitionAppear is used to enable animation after initial mount of the component, not to enable enter animation.

simple css animation not working on dynamic reactjs element

Check the snippet at codepen http://codepen.io/anon/pen/EZJjNO
Click on Add button, it will add another item but its appearing immediately without any fade effect.
JS:
class App extends React.Component {
constructor(props) {
super(props);
this.addItem = this.addItem.bind(this);
this.state = {
items: [1,2,3]
}
}
addItem() {
var items = this.state.items;
items.push(4);
this.setState({
items: items
})
}
render() {
return (
<div className="App">
{
this.state.items.map(function(i) {
return <div className="item fade">Testing</div>
})
}
<button onClick={this.addItem}>Add</button>
</div>
);
}
}
React.render(<App />, document.body);
CSS:
.App {
background-color: rgba(0,0,0, 0.5);
text-align: center;
height: 100vh;
}
div.item {
border: 1px solid #ccc;
padding: 10px;
background: #123456;
color: #fff;
opacity: 0;
transition: all 0.4s ease-out;
}
.fade {
opacity: 1 !important;
}
Since the fade class is added by default, you don't get the transition effect. If you open your browser's developer tools and remove the class, you'll see it fade away nicely.
There's a few ways to get what you want, but I'd just use a keyframe CSS animation like so:
.fade {
animation: 0.4s ease-out fadeIn 1;
}
#keyframes fadeIn {
0% {
opacity: 0;
visibility: hidden;
}
100% {
opacity: 1;
visibility: visible;
}
}
Here's a fork of your code pen showing how it works :)

Resources