I just started playing around with React Animations, and I seem to be stuck.
I have a series of Cards that appear (animate-in). Right now, they're all coming in at the same time (the animation plays at the same time). Is there a way to add an offset to the animation so that they play one after the other?
Animate = React.addons.CSSTransitionGroup
<Animate transitionName="cardAnim" transitionAppear={true}>
{
courses.map(function(element, index){
return (
<Card style={CardStyle} key={index}>
<CardMedia>
<img src={"img/courses/" + index + ".png"} />
</CardMedia>
<CardTitle>
{element}
</CardTitle>
</Card>
)
})
}
</Animate>
My aim: I want each Card to animate in separately, as in, the second card animates in after the first one has entered, the third animates after the second one is done, and so on.
Can anyone help? Thanks :)
All right, so I figured it out.
The key was the animation-fill-mode CSS property. I completely removed all React Animations and proceeded to figure it out using CSS only.
Here's the code, if anyone is interested:
CSS:
#keyframes flyInFromBottom {
0%{
opacity: 0;
transform: translateY(100vh);
}
100%{
opacity: 1;
}
}
JSX:
<Card style={{
animation: 'flyInFromBottom 0.3s ease ' + (index+1)*0.1 + 's',
float: 'left',
width: 'calc(50% - 4px)',
margin: '2px',
opacity: '0',
animationFillMode: 'forwards'
}}
key={index} onTouchTap={this.goToCourse}>
<CardMedia>
<img src={"img/courses/" + index + ".png"} />
</CardMedia>
<CardTitle>
{element}
</CardTitle>
</Card>
Essentially, I changed properties using CSS and persisted them using animation-fill-mode.
Related
I Want to reply a behavior I saw on a page the map below the section "How It works" title (I wanted to embed a video but it seems stack overflow don't allow it) i.e place one image over another in a specific location, the page I linked shows a map and a pointer that points to the emirates but I can't figure how to do this I achieve this in React, by using position CSS property to position the second image (pointer) relative to the first image (map) and then use the top, left, right, and bottom properties to position the pointer on the desired location on the map but I am having problems with the animation, this is rendering when the parent is rendered and not when the page is scrolled down, I am using the IntersectionObserver
my code is the following:
import React from 'react';
import React, {useEffect, useRef, useState} from 'react';
function MapWithPointer() {
const mapRef = useRef(null);
const pointerRef = useRef(null);
const [isPointerVisible, setIsPointerVisible] = useState(false);
useEffect(() => {
if(!isPointerVisible) return;
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
setIsPointerVisible(true);
}
});
if(mapRef.current) observer.observe(mapRef.current);
return () => observer.disconnect();
}, [isPointerVisible, mapRef]);
return (
<div ref={mapRef} style={{ position: "relative" }}>
<img src="https://emiride.com/wp-content/uploads/2022/11/map-1024x594.png" alt="map" />
<img
ref={pointerRef}
src="https://emiride.com/wp-content/uploads/2022/11/piont.png"
alt="pointer"
width={'30wh'}
className={`pointer ${isPointerVisible ? "is-visible" : ""}`}
style={{ position: "absolute", top: "310px", left: "640px" }}
/>
</div>
);
}
css:
#keyframes pointer-animation {
from {
transform: translateY(1000%);
}
to {
transform: translateY(0);
}
}
.pointer {
animation: pointer-animation 1s ease-in-out;
transform: translateY(100%);
}
.pointer.is-visible {
transform: translateY(0);
animation: pointer-animation 1s ease-in-out;
}
and int the parent ins rendering like this:
<MapComponent/> , the parent does not have useEffect hook.
how can I achieve my purpose?
I'm trying to create an animation of a spinning svg as a loader,
checking online I've seen some examples of doing it with Styled component, which is deprecated.
Been wondering if you guys have a suggestion?
I tried adding an '#keyframes spin' property to my SX but it didn't do anything, I.E:
<Box
sx={{
animation: '$test 1s linear infinite',
'#keyframes spin': {
from: {
transform: 'rotate(0dg)'
},
to: {
transform: 'rotate(360dg)'
}
}
}}
>
I tried creating an animation with SX property of MUI v5 with the same logic as Styled component
but failed :(
I wonder if we can achieve it without using a CSS file & a class...
With MUI5 it's works just fine when add keyframes on SX props
Here is my example
<Box sx={{
"#keyframes width-increase": {
"0%": {
width: "100px"
},
"100%": {
width: "300px"
}
},
width: "100px",
height: "50px",
backgroundColor: "red",
animation: "width-increase 1s ease infinite",
}}></Box>
Make sure you init keyframes before use.
Hope this will help you...
I have a React app that is using the CSSTransition component from react-transition-group: when the component appears, everything behaves as expected (a 0.5s transition from opacity: 0 to opacity: 1), however when the component exits, the transition is not applied and it just immediately disappears. Can anybody help me figure out why?
Render method in component:
render(){
const countries = geoUrl.objects.ne_50m_admin_0_countries.geometries;
const { handleEnter, handleList, list } = this.props;
return (
<CSSTransition
classNames="transition"
transitionAppearTimeout={50000}
timeout={500000}
key={ list }
in={ list } // this is a boolean value passed from the parent component, it is initially set to false but changes to true when this component is rendered
unmountOnExit
appear
>
<div className="overlay" id="list">
<div className="wrapper" ref={this.setWrapperRef}>
<aside className="list">
<a className="close" href="#home" onClick={handleList}>×</a>
<ul className="countryList">
{ countries.sort((a, b) => (a.properties.NAME > b.properties.NAME) ? 1 : -1).map(geo =>
geo.properties.COUNTRY ?
<li className="listItem" key={ `${geo.properties.ISO_A3}${geo.properties.name}` }><a href="#country" onClick={() => {
const { NAME, DISH, DESCRIPTION, PHOTO, RECIPE } = geo.properties;
handleEnter(NAME, DISH, DESCRIPTION, PHOTO, RECIPE);
}}>{ geo.properties.NAME }</a></li>
: null
)}
</ul>
</aside>
</div>
</div>
</CSSTransition>
);
}
CSS:
.transition-appear {
opacity: 0.01;
}
.transition-appear.transition-appear-active {
opacity: 1;
transition: opacity .5s ease-in;
}
.transition-enter {
opacity: 0;
}
.transition-enter-active {
opacity: 1;
transition: opacity .5s ease-in;
}
.transition-exit {
opacity: 1;
}
.transition-exit-active {
opacity: 0;
transition: opacity .5s ease-in;
}
I also struggled with the transition out, but have come up with a solution. I can't tell what your final result should look like in the UI, whether you want to transition in between conditionally rendered elements on the page, or just a transition when the component mounts and unmounts, but I think you want to do the former.
To transition between two or more elements on the same page, you will need to wrap your <CSSTransition> tags in <TransitionGroup> tags (and import it alongside CSSTransition). When you do this, you will need to provide a unique key property to the <CSSTransition> tag, it can't just be a boolean like it looks like you have. You also will need to slightly modify your CSS.
.transition-enter {
opacity: 0;
}
.transition-enter.transition-enter-active {
opacity: 1;
transition: opacity 0.5s ease-in;
}
.transition-enter-done {
opacity: 1;
}
.transition-exit {
opacity: 1;
}
.transition-exit.transition-exit-active {
opacity: 0;
transition: opacity 0.5s ease-in;
}
.transition-exit-done {
opacity: 0;
}
And the Transition tags:
import {CSSTransition, TransitionGroup} from "react-transition-group"
//...the rest of your code
return (
<TransitionGroup>
<CSSTransition
key={foo} //something unique to the element being transitioned
classNames="transition"
timeout={500}
>
<div className="overlay" id="list">
<div className="wrapper" ref={this.setWrapperRef}>
<aside className="list">
<a className="close" href="#home" onClick={handleList}>×</a>
<ul className="countryList">
{ countries.sort((a, b) => (a.properties.NAME > b.properties.NAME) ? 1 : -1).map(geo =>
geo.properties.COUNTRY ?
<li className="listItem" key={ `${geo.properties.ISO_A3}${geo.properties.name}` }><a href="#country" onClick={() => {
const { NAME, DISH, DESCRIPTION, PHOTO, RECIPE } = geo.properties;
handleEnter(NAME, DISH, DESCRIPTION, PHOTO, RECIPE);
}}>{ geo.properties.NAME }</a></li>
: null
)}
</ul>
</aside>
</div>
</div>
</CSSTransition>
</TransitionGroup>
)
Please note: You do not need appear, in, or unmountOnExit properties for this method, but you will have issues with duplicate elements appearing in the DOM during the transition, becuase react-transition-group actually clones the element and deletes the old one (which is why it needs a unique key). The only way to achieve a cross-fade transition is to take your elements out of the document flow with position absolute, so they overlap as one transitions in, and the other out.
I can't test your exact code, because there is not enough information, but I put together a very basic codesandbox that demonstrates the method with 2 conditionally rendered elements:
https://codesandbox.io/s/long-sea-9rtw9?file=/src/Test.js
I am using animate-css-grid and React library to build my portfolio in a grid design, I kind of achieved what I wanted here is the pen link.
But when I click any of the boxes, the text which should go with the box rendered instantaneously. I want it to render after the box get scaled up.
this is piece code which I am using in rendering the text.
class Card extends React.Component {
state = { expanded: false };
randomNumber = Math.floor(Math.random() * 5) + 1;
render() {
return (
<div
style={{ backgroundColor: this.props.color }}
class={`card card--${this.randomNumber} ${
this.state.expanded ? "card--expanded" : ""
}`}
onClick={() => {
this.setState({ expanded: !this.state.expanded });
}}
>{this.state.expanded ? <Text /> :
(<div>
{/* <div className="card__avatar" />
<div className="card__title" /> */}
<div className="card__description">
{this.props.subject}</div>
</div>)}
</div>
);
}
}
I am rendering the Text component at the time it gets the expanded state true.
I don't know how to delay it. The animate-css-grid library provides custom configuration but I am unable to understand how to use it. Can anybody who is familiar with CSS animations help me out here, please.
I added a className around what <Text /> returns and trigger an animation to reveal the text when the --card-expanded className is present. The delay of 500ms feels good to me, but it's, of course, adjustable.
.card-text {
opacity: 0;
}
.card--expanded .card-text {
animation: 1s show-text forwards 500ms;
}
#keyframes show-text {
to {
opacity: 1;
}
}
CodePen
I've written a React app, using CSS transitions. But those transitions does not work correctly in some of the components. In my app, only the components who are moving upwards works well, those who are moving downwards moves instantly without animation. (I want them both moves with animation.)
Here is the CSS I used there:
div.canvas {
position: absolute;
top: 90px;
left: 60px;
width: 640px;
height: 480px;
border: 1px solid #999;
background: white;
}
div.canvas-rect {
position: relative;
margin-left: 20px;
margin-top: 10px;
height: 20px;
background: green;
transition: all 1s linear;
-moz-transition: all 1s linear; /* Firefox 4 */
-webkit-transition: all 1s linear; /* Safari 和 Chrome */
-o-transition: all 1s linear; /* Opera */
}
UPDATED:
I also built a codepen.io project to show the problem. It has the complete code of this demo project.
I've tried to add a log entry to componentDidUpdate, componentDidMount and componentWillUnmount methods to show whether these component are re-created or updated, it shows that they are all updated (not re-created, or removed) every second.
Well, after I started a bounty because I also have this problem I finally found what seems to be the problem.
When you are using absolute position (or relative, as in your case), if you re-render the whole list every time, React will re-order the elements in the DOM (as you said, the elements are not being recreated, just updated). But this creates the problem with the transitions... apparently, if you move an element while the transition is running then you end up cutting the animation.
So, for cases in which you want to use position absolute, the key concept is to render the containers of your elements once (in this case, just divs) and only change the inner contents based on the new order. If you need to add more elements, just add them at the end.
I modified your codepen so that it reflects what I am saying. My example is very dumb because I just created 4 ad-hoc divs, but it illustrates the idea: create as many containers as you need, but DO NOT use a map that recreates them every time, or your transitions will be cut.
https://codepen.io/damianmr/pen/boEmmy?editors=0110
const ArrList = ({
arr
}) => {
return (
<div style={{position: 'relative'}}>
<div className={`element element-${arr[0]} index-${arr[0]}`}>{arr[0]}</div>
<div className={`element element-${arr[1]} index-${arr[1]}`}>{arr[1]}</div>
<div className={`element element-${arr[2]} index-${arr[2]}`}>{arr[2]}</div>
<div className={`element element-${arr[3]} index-${arr[3]}`}>{arr[3]}</div>
</div>
);
}
So, the problem is basically how you create a static list of containers and how you iterate through that list so that the first container renders the first element of your data, the second container the second element, etc.
Hope that it helps, this problem was driving me crazy too! :)
I know this wasn't the case, but since I got here also looking for React css transition does not work correctly, I just wanted to share:
If you create an element using arrow functions inside render, it won't get properly animated, since a new componente is always being created.
You should create a function outside and invoke it in 'render'.
You can trick React by using index as key. If you think about el, and index as starting position (index) and end position (el), the element has moved to the old end position by the end of the transition, and by when it's there, it's taken over by the new start position and (index) is switched to match the new setup. This is because when you set key in an element in react, the virtual DOM will always interpret it as it is the same element. And for the sake of it, you're right in setting index as the "id" in general.
I made a working example only by switching index/el (and setting element position to absolute).
const {combineReducers, createStore} = Redux;
const { Provider, connect } = ReactRedux;
const ArrList = ({
arr
}) => (
<div>{
arr.map((el, index)=>
<div
key={""+index}
className={`element element-${el}` + ` index-${el}`}
>
{el}
</div>) }
</div>
)
const mapStateToArrList = (state) => {
return {
arr: state.appReducer.arr
}
};
const App = connect(mapStateToArrList, null)(ArrList);
const initialState = {
arr: [1, 2, 3, 4]
}
const appReducer = (state = initialState, action) => {
switch(action.type) {
case "tick":
return {
...state,
arr: _.shuffle(state.arr)
}
default:
return state
}
}
const reducer = combineReducers({
appReducer
})
const store = createStore(reducer)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
const dispatcher = () => {
store.dispatch({
type: "tick"
})
setTimeout(dispatcher, 1000)
}
dispatcher()
.element {
position: absolute;
height: 20px;
background: green;
margin-left: 20px;
margin-top: 20px;
text-align: right;
color: white;
line-height: 20px;
transition: all 1s ease-in;
-moz-transition: all 1s ease-in; /* Firefox 4 */
-webkit-transition: all 1s ease-in; /* Safari 和 Chrome */
-o-transition: all 1s ease-in; /* Opera */
}
.element-1 {
width: 20px;
}
.element-2 {
width: 40px;
}
.element-3 {
width: 60px;
}
.element-4 {
width: 80px;
}
.index-1 {
top: 20px;
}
.index-2 {
top: 40px;
}
.index-3 {
top: 60px;
}
.index-4 {
top: 80px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.7.2/redux.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/5.0.6/react-redux.js"></script>
<div id="root"></div>
If you are removing the element from the virtual DOM, then the react will update its contents, so you won't see the animations. What you can do is either use react-transition-group OR tell your app to wait x ms before updating the dom once the event is called OR use visibility to toggle between hidden and showing instead of removing it completely from the DOM.
You did recreate DOM elements each time.
You should define collect key value.
I changed your key value '' + el to '' + index.
<div key={'' + index} className={'element element-' + el + ' index-' + index} >
Just change css properties only :)
I''ll just leave that here in case helps somebody but to me it was solved by moving from
export default function Contact(props) {...}
To:
const Contact = (props) => {...}
export default Contact