How to add animations on every re-render of component - javascript

I have a simple component of my trivia-app on React. When I answer a question - my component re-renders the new question and I want to have an animation for the question block. But, I have only the one-time animation at the first render of this component. How to make animation on every re-render of the component?
Here is my sandbox example - https://codesandbox.io/s/youthful-ardinghelli-hn2zx?file=/src/App.js
And here is my code:
import { useState } from "react";
import "./styles.css";
const questions = ["1", "2", "3", "4", "5"];
const answers = ["asgas", "asgasg", "ashash", "hasdha"];
export default function App() {
const [currentQuestion, setCurrentQuestion] = useState(0);
return (
<>
<h2 className="question">{questions[currentQuestion]}</h2>
<div>
{answers.map((i) => (
<button
onClick={() => {
setCurrentQuestion((prevState) => prevState + 1);
}}
key={i}
>
{i}
</button>
))}
</div>
</>
);
}
.App {
font-family: sans-serif;
text-align: center;
}
.question {
background-color: blue;
animation: fromTop 0.5s linear;
}
#keyframes fromTop {
0% {
transform: translateY(-20%);
}
100% {
transform: translateY(0%);
}
}

Just add something data to <h2>-tag, then React can re-render this tag and animation started.
<h2 className="question" key={currentQuestion}>{questions[currentQuestion]}</h2>

You cannot use a useEffect(), because this will not animate as the component hasn't been "drawn" yet.
useEffect is executed after the render but before the "drawing" of the components, you have to use the hook useLayoutEffect. This hook executes after the drawing of the component, so you will have a lot more information about the layout like position, height, or weight.

Related

Changing body image with an onClick Event

I was wondering how would I approach to change a body background image with onClick Event. Should I be using useRef hook or. I would really appriciate the help
body {
background: url("http");
background-position: center;
background-size: cover;
overflow-y: hidden;
overflow-x: hidden;
min-width: 100%;
}
function App() {
return (
<div>
<h1 onClick={...}> click here to change background image </h1>
</div>
);
}
export default App;
You can simply use document.body.style.backgroundImage like this:
import { useState } from "react";
import "./styles.css";
export default function App() {
const [counter, setCounter] = useState(0);
const IMAGES = [
"https://cdn.pixabay.com/photo/2012/08/25/22/22/saturn-54999_1280.jpg",
"https://cdn.pixabay.com/photo/2020/07/06/01/33/sky-5375005_1280.jpg",
"https://cdn.pixabay.com/photo/2011/12/14/12/23/solar-system-11111_1280.jpg"
];
const changeBodyBgImage = () => {
document.body.style.backgroundImage = `url(${IMAGES[counter]})`;
setCounter(counter > 2 ? 0 : counter + 1);
};
return (
<div className="App">
<h1 onClick={changeBodyBgImage}>Hello CodeSandbox</h1>
</div>
);
}
Here is the demo: https://codesandbox.io/s/serene-brook-82prg?file=/src/App.js:0-665
Use document.body to set the background changes you need.
The event handler should look like this.
onClick={() => {
document.body.style.background = ...
}}

How to add classNames in conditional rendering in reactjs?

I have a list of data with images. I want to make image carousel. For this I have created card component and I want here to display 4 cards at a time and remaining should be hidden. Then i want to setTimeout of 5s to display remaining but only for at a time.
So far I have done this.
about.js
import './about.scss';
import data from '../../data/data';
import CardSection from './card';
class About extends React.Component{
constructor(props){
super(props);
this.state = {
properties: data.properties,
property: data.properties[0]
}
}
nextProperty = () => {
const newIndex = this.state.property.index+4;
this.setState({
property: data.properties[newIndex]
})
}
prevProperty = () => {
const newIndex = this.state.property.index-4;
this.setState({
property: data.properties[newIndex]
})
}
render() {
const {property, properties} = this.state;
return (
<div className="section about__wrapper">
<div>
<button
onClick={() => this.nextProperty()}
disabled={property.index === data.properties.length-1}
>Next</button>
<button
onClick={() => this.prevProperty()}
disabled={property.index === 0}
>Prev</button>
<Container className="card__container">
<div class="card__main" style={{
'transform': `translateX(-${property.index*(100/properties.length)}%)`
}}>
{
this.state.properties.map(property => (
<CardSection property={property}/>
))
}
</div>
</Container>
</div>
</div>
)
}
}
export default About
about.scss
.card__container{
overflow-x: hidden;
}
.card__main{
display: flex;
position: absolute;
transition: transform 300ms cubic-bezier(0.455, 0.03, 0.515, 0.955);
.card__wrapper {
padding: 20px;
flex: 1;
min-width: 300px;
}
}
card.js
import React from "react";
import { Card, CardImg, CardText, CardBody,
CardTitle, CardSubtitle, Button } from 'reactstrap';
class CardSection extends React.Component {
render() {
return (
<div className="card__wrapper">
<Card>
<CardImg top width="100%" src={this.props.property.picture} alt="Card image cap" />
<CardBody>
<CardTitle>{this.props.property.city}</CardTitle>
<CardSubtitle>{this.props.property.address}</CardSubtitle>
<CardText>Some quick example text to build on the card title and make up the bulk of the card's content.</CardText>
<Button>Button</Button>
</CardBody>
</Card>
</div>
);
}
}
export default CardSection;
I have added transition in them to change card onclick but i want them to auto change and hide the remaining card.
Right now it looks like this,
You can add items in componentDidMount method using setInterval
componentDidMount() {
this.interval = setInterval(() => this.setState({
properties:data.properties /* add your data*/ }), 4000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
You can have a property called showCardIds that holds an array of the Id of cards that need to be shown, and use that to set a Boolean property called hidden on the div of the card.
You could also do something like this as shown in the example below, this example also uses showCardIds as a state. It filters only for the property that needs to be rendered and filters out the rest.
Here is an example:
...
{
this.state.properties.filter((property, index) => showCardIds.includes(index)).map(property => (
<CardSection property={property}/>
))
}
...
That way only the ones that are present in the array of showCardIds would show up, there needs to be more logic to be written that would populate the ids in showCardIds
Hope this helps. The hidden property is supported from HTML5, and should work on most browsers, unless they are truly "ancient".

TransitionGroup and CssTransition: Exit transition not applied

I migrated from the old CSSTransitionGroup to the new react-transition-group CSSTransition and TransitionGroup.
I'm creating (hacking) an overlay loader and I'm trying to apply animation to the overlay when it appears and disappears.
Specifically when I pass an active=true props to the LoadingOverlayWrapper then a new CSSTransition is added to the TransitionGroup (the Fadecomponent) that wraps the overlay to show.
When active=false then the CSSTransition is removed from within the TransitionGroup (the direct child of TransitionGroupis null).
This is the relevant part of the code:
import React, {Children} from 'react'
import PropTypes from 'prop-types'
import {CSSTransition, TransitionGroup} from 'react-transition-group'
import LoadingOverlay from "./LoadingOverlay";
import styles from './Overlay.sass';
const FirstChild = props => Children.toArray(props.children)[0] || null;
const Fade = (props) => (
<CSSTransition
{...props}
timeout={500}
classNames={{
appear: styles.appear,
appearActive: styles.appearActive,
enter: styles.enter,
enterActive: styles.enterActive,
exit: styles.exit,
exitActive: styles.exitActive
}}
>
<FirstChild {...props} />
</CSSTransition>
);
class LoadingOverlayWrapper extends React.Component {
render() {
const {active} = this.props;
return (
<div>
<TransitionGroup>
{
active ?
(
<Fade key='transition_effect'>
<LoadingOverlay key='the_dimmer' {...this.props} />
</Fade>
)
:
null
}
</TransitionGroup>
{this.props.children}
</div>
)
}
}
And this is the relevant sass file (imported as css module):
.enter, .appear
opacity: 0.01
.appearActive, .enterActive
opacity: 1
transition: opacity .5s ease-in
.exit, .leave
opacity: 0.01
.exitActive, .leaveActive
opacity: 0
transition: opacity .5s ease-in
The enter (or appear, not sure here) transition works.
The problem is that when I remove the Fade component, being replace by null then the exit transition is not applied (or not visible) but I get no error, everything else works as intended.
I'm not sure how to debug or proceed here given I have little experience with React TransitionGroup.
I've been struggling with the same issue - the solution that worked for me is using the childFactory prop on the <TransitionGroup> like so:
<TransitionGroup
childFactory={child => React.cloneElement(child)}
>
{
active ?
(
<Fade key='transition_effect'>
<LoadingOverlay key='the_dimmer' {...this.props} />
</Fade>
)
:
null
}
</TransitionGroup>
import { CSSTransition } from 'react-transition-group';
<CSSTransition
in={toShow} // boolean value passed via state/props to either mount or unmount this component
timeout={300}
classNames='my-element' // IMP!
unmountOnExit
>
<ComponentToBeAnimated />
</CSSTransition>
NOTE: Make sure to apply below styles using the class property in CSS:
.my-element-enter {
opacity: 0;
transform: scale(0.9);
}
.my-element-enter-active {
opacity: 1;
transform: translateX(0);
transition: opacity 300ms, transform 300ms;
}
.my-element-exit {
opacity: 1;
}
.my-element-exit-active {
opacity: 0;
transform: scale(0.9);
transition: opacity 300ms, transform 300ms;
}

React smooth transition between different states in a component

I have a simple component like this:
var component = React.createClass({
render: function(){
if (this.props.isCollapsed){
return this.renderCollapsed();
}
return this.renderActive()
},
renderActive: function(){
return (
<div>
...
</div>
);
},
renderCollapsed: function(){
return (
<div>
...
</div>
);
},
});
Basically, when the property changes, the component will either show active state or collapse state.
What I am thinking is, when the property change happens, i.e. active->collapse, or the other way around, I want the old view "shrink" or "expand" smoothly to show the new view. For example, if it is active -> collapse, I want the active UI to shrink to the size of collapse UI, and show it smoothly.
I am not sure how to achieve this effect. Please share some ideas. Thanks!
Here is a minimal working example:
const collapsible = ({active, toggle}) =>
<div>
<button type="button" onClick={toggle}>Toggle</button>
<div className={'collapsible' + (active? ' active': '')}>
text
</div>
</div>
const component = React.createClass({
getInitialState() {
return {active: false}
},
toggle() {
this.setState({active: !this.state.active})
},
render() {
return collapsible({active: this.state.active, toggle: this.toggle})
}
})
ReactDOM.render(React.createElement(component), document.querySelector('#root'))
.collapsible {
height: 1.5rem;
transition: height 0.25s linear;
background: #333;
border-radius: 0.25rem
}
.collapsible.active {
height: 7rem
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script>
<div id="root"></div>
The view can be "shrink" or "expand" smoothly by CSS transition, which is triggered by changing CSS properties.
To control CSS properties with React, we can reflect state changes to property values or className in render().
In this example, .active class affects height value, and is controlled by state.active. The class is toggled by React in response to state changes, and triggers CSS transition.
For smoother transitions, See this article.
Rather than conditionally render two different end states of a
component, you could instead toggle the class on the same
component. You could have active and collapsed classes as follows:
For example:
.active{
-webkit-transition: -webkit-transform .5s linear; // transition of
// 0.5 of a second
height: 200px;
}
.collapsed{
height: 0px;
}
Check out this resource for examples
The standard way is to use CSSTransitionGroup from react-transition-group, which is quite easy. Wrap the component with the CSSTransitionGroup and set timeouts on enter and leave, like this:
<CSSTransitionGroup
transitionName="example"
transitionEnterTimeout={500}
transitionLeaveTimeout={300}>
{items}
</CSSTransitionGroup>
From the v1-stable docs:
"In this component, when a new item is added to CSSTransitionGroup it
will get the example-enter CSS class and the example-enter-active CSS
class added in the next tick."
Add styling for the CSS classes to get the correct animation.
There's also pretty good explanation in React docs, check it out.
There are third-party components for animation as well.
One more approach to this situation might be changing state after animation completes. The benefits of it is that you can apply not only transitions but whatever actions you want (js animations, smil, etc ..), main thing is not to forget to call an end callback;)
Here is working example CodePen
And here is the code example:
const runTransition = (node, {property = 'opacity', from, to, duration = 600, post = ''}, end) => {
const dif = to - from;
const start = Date.now();
const animate = ()=>{
const step = Date.now() - start;
if (step >= duration) {
node.style[property] = to + post;
return typeof end == 'function' && end();
}
const val =from + (dif * (step/duration));
node.style[property] = val + post;
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
}
class Comp extends React.Component {
constructor(props) {
super(props);
this.state = {
isCollapsed: false
}
this.onclick = (e)=>{
this.hide(e.currentTarget,()=>{
this.setState({isCollapsed: !this.state.isCollapsed})
});
};
this.refF = (n)=>{
n && this.show(n);
};
}
render() {
if (this.state.isCollapsed){
return this.renderCollapsed();
}
return this.renderActive()
}
renderCollapsed() {
return (
<div
key='b'
style={{opacity: 0}}
ref={this.refF}
className={`b`}
onClick={this.onclick}>
<h2>I'm Collapsed</h2>
</div>
)
}
renderActive() {
return (
<div
key='a'
style={{opacity: 0}}
ref={this.refF}
className={`a`}
onClick={this.onclick}>
<h2>I'm Active</h2>
</div>
)
}
show(node, cb) {
runTransition(node, {from: 0, to: 1}, cb);
}
hide(node, cb) {
runTransition(node, {from: 1, to: 0}, cb);
}
}
ReactDOM.render(<Comp />, document.getElementById('content'));
And for sure, for this approach to work your only opportunity is to relay on state, instead of props of Component, which you can always set in componentWillReceiveProps method if you have to deal with them.
Updated
Codepen link updated with more clear example which is showing benefits of this approach. Transition changed to javascript animation, without relying on transitionend event.
As you want to render two different component for each of active and collapsed, wrap them in a div that controls the height with the help of CSS.
render: function(){
var cls = this.props.isCollapsed() ? 'collapsed' : 'expanded';
return(
<div className={cls + ' wrapper'}>
{
this.props.isCollapsed() ?
this.renderCollapsed() :
this.renderActive()
}
</div>
);
}
and in your CSS:
.wrapper{
transition: transform .5s linear;
}
.expanded{
height: 200px;
}
.collapsed{
height: 20px;
}
You can add a class that describes the active state ie. .active and toggle that class when switching states.
The css should look something like this:
.your-component-name{
// inactive css styling here
}
.your-component-name.active {
// active css styling here
}
Here you have a Toggle react component using the Velocity-React library, which is great for giving animations to transitions in React uis:
import React, { Component } from 'react';
import { VelocityTransitionGroup } from 'velocity-react';
export default class ToggleContainer extends Component {
constructor () {
super();
this.renderContent = this.renderContent.bind(this);
}
renderContent () {
if (this.props.show) {
return (
<div className="toggle-container-container">
{this.props.children}
</div>
);
}
return null
}
render () {
return (
<div>
<h2 className="toggle-container-title" onClick={this.props.toggle}>{this.props.title}</h2>
<VelocityTransitionGroup component="div" enter="slideDown" leave="slideUp">
{this.renderContent()}
</VelocityTransitionGroup>
</div>
);
}
};
ToggleContainer.propTypes = {
show: React.PropTypes.bool,
title: React.PropTypes.string.isRequired,
toggle: React.PropTypes.func.isRequired,
};
Hope it helps!

How can I animate a react.js component onclick and detect the end of the animation

I want to have a react component flip over when a user clicks on the DOM element. I see some documentation about their animation mixin but it looks to be set up for "enter" and "leave" events. What is the best way to do this in response to some user input and be notified when the animation starts and completes? Currently I have a list item and I want it to flip over an show a few buttons like delete, edit, save. Perhaps I missed something in the docs.
animation mixin
http://facebook.github.io/react/docs/animation.html
Upon clicks you can update the state, add a class and record the animationend event.
class ClickMe extends React.Component {
constructor(props) {
super(props)
this.state = { fade: false }
}
render() {
const fade = this.state.fade
return (
<button
ref='button'
onClick={() => this.setState({ fade: true })}
onAnimationEnd={() => this.setState({ fade: false })}
className={fade ? 'fade' : ''}>
Click me!
</button>
)
}
}
See the plnkr: https://next.plnkr.co/edit/gbt0W4SQhnZILlmQ?open=Hello.js&deferRun=1&preview
Edit: Updated to reflect current React, which supports animationend events.
React uses synthetic events, which includes animation events. Documention found here: https://reactjs.org/docs/events.html#animation-events. I updated the accepted answer below:
class ClickMe extends React.Component {
state = {fade: false};
render () {
const {fade} = this.state;
return (
<button
onClick={() => this.setState({fade: true})}
onAnimationEnd={() => this.setState({fade: false})}
className={fade ? 'fade' : ''}>
Click me!
</button>
)
}
}
Here is the answer using pure Reactjs events without any JQueries, other libs, registrations or sms :)
The key point is to provide animation keyframe name as a function parameter
CSS
.Modal {
position: fixed;
top: 30%;
left: 25%;
transition: all 0.3s ease-out;
}
.ModalOpen {
animation: openModal 0.4s ease-out forwards;
}
.ModalClosed {
animation: closeModal 0.4s ease-out forwards;
}
#keyframes openModal {
0% { transform: translateY(-100%); }
100% { transform: translateY(0); }
}
#keyframes closeModal {
0% { transform: translateY(0); }
100% { transform: translateY(-100%);}
}
JS
const modal = ({
isShown, isMounted,
initiateUnmountAction, unmountAction
}) => {
const cssClasses = [
"Modal",
props.isShown ? "ModalOpen" : "ModalClosed"
];
return (
<Fragment>
{isMounted && <div className={cssClasses.join(' ')}
onAnimationEnd={event =>
{event.animationName == "closeModal" && unmountAction}
}>
<h1>A Modal</h1>
<button className="Button" onClick={initiateUnmountAction}>
Dismiss
</button>
</div>}
</Fragment>
);
};
How to do it with Hooks and prevState so as to not update state inside your event handlers.
import { useState } from 'react'
export default function Animation() {
const [fade, setFade] = useState(false)
const triggerFade = () => {
setFade(prevState => {
return !prevState
})
}
return (
<div
onAnimationEnd={triggerFade}
className={fade ? 'fadedClass' : 'visibleClass'}
>
Watch me fade
</div>
<button onClick={triggerFade}>Click Me</button>
)
}
Most popular and easy to use package I came across:
https://www.npmjs.com/package/react-transition-group
Install:
npm install react-transition-group
Usage:
import { CSSTransition } from 'react-transition-group';
<CSSTransition
in={toShow} // boolean value passed via state/props to either mount or unmount this component
timeout={300}
classNames='my-element' // IMP!
unmountOnExit
>
<ComponentToBeAnimated />
</CSSTransition>
NOTE: Make sure to apply below styles using the class property in CSS:
.my-element-enter {
opacity: 0;
transform: scale(0.9);
}
.my-element-enter-active {
opacity: 1;
transform: translateX(0);
transition: opacity 300ms, transform 300ms;
}
.my-element-exit {
opacity: 1;
}
.my-element-exit-active {
opacity: 0;
transform: scale(0.9);
transition: opacity 300ms, transform 300ms;
}
I've never used React, but if it uses CSS3 animations/transitions, you might be able to do something like this:
element.addEventListener( 'webkitTransitionEnd', function( event ) {
console.log( 'Complete');
}, false );
I successfully used this project in my react-hammer integration project there are some examples with hammer events and react animation.
It can also be done using CSS,
If you are using TailwindCSS, the answer would be, Add below Classes
hover:opacity-90 focus:opacity-100
else raw css
.class-name:hover{
opacity: 0.9;
}
.class-name:focus{
opacity: 1;
}
I was realise in that way, hope it's help someone.
CSS:
.mainStyle {
/*some-styles*/
height: 250px;
background-color: red;
}
.openAnimation {
animation: open-menu .4s linear forwards;
}
.closeAnimation {
animation: close-menu .4s linear forwards;
}
#keyframes open-menu {
0% {width: 0;} /*You can use animation what do You need*/
100% {width: 80%;}
}
#keyframes close-menu {
0% {width: 80%;}
100% {width: 0;}
}
JSX file
Import React, { useState } from React;
const AnimatedMenu = () => {
const [isOpen, setOpen] = useState(false);
const animationEndHandler = ({ animationName }) => {
if (animationName === 'open-menu') {
setOpen(true);
}
if (animationName === 'close-menu') {
setOpen(false);
}
};
return (
<>
<div
onAnimationEnd={(event) => animationEndHandler(event)}
className={isOpen ? 'mainStyles open' : 'mainStyles close'}
>
hide/show
</div>
<button onClick={() => setOpen(!isOpen)}>click me</button>
</>
);
};

Categories