I am trying to set up animated transitions on route change within my React / Redux app. I have been using examples on SO and the official tutorials as a guide but have so far only been able to get the fade in effect to work on the first load. When changing routes, the components are displayed without any animation in or out.
Am I missing something obvious- and is this the best path to follow when trying to animate the content in / out?
/*
** Loop through ACF components to layout page
*/
blocks = () => {
if (this.props.pages.pages[0]) {
return this.props.pages.pages[0].acf.components &&
this.props.pages.pages[0].acf.components.map((block, index) => {
let Component = componentMapping[block.acf_fc_layout];
return <Component key={index} data={block} id={index} />;
});
}
};
/*
** Render page on state change
*/
render() {
return (
<div className="App">
<NavContainer />
<ReactCSSTransitionGroup
transitionName="example"
transitionAppear={true}
transitionAppearTimeout={500}
transitionEnterTimeout={500}
transitionLeaveTimeout={300}
>
{this.blocks()}
</ReactCSSTransitionGroup>
</div>
);
}
The css I have included is as follows :
.example-enter {
opacity: 0.01;
}
.example-enter.example-enter-active {
opacity: 1;
transition: opacity 500ms ease-in;
}
.example-leave {
opacity: 1;
}
.example-appear {
opacity: 0.01;
}
.example-appear.example-appear-active {
opacity: 1;
transition: opacity .5s ease-in;
}
.example-leave.example-leave-active {
opacity: 0.01;
transition: opacity 300ms ease-in;
}
I appreciate any help that anybody could give. Thanks in advance!
Let's go through some troubleshooting steps to get to the bottom of this, but first, let me give you some basic tips on how the ReactCSSTransitionGroup component works.
<ReactCSSTransitionGroup/> needs to be present on the page with it's children being added and removed after mount. e.g. You have a list of employees that you are filtering based on an input box. As the filter controls what's being added and removed those items are coming and going and being animated. The <ReactCSSTransitionGroup/> must exist first and the component must be mounted.
With React Router, you're mounting and unmounting components as you change routes. If the <ReactCSSTransitionGroup/> is inside each component that is being mounted and unmounted the transition will not fire. I think this is what you are experiencing. I would suggest trying out this NPM package to help you take care of animations with route changes.
The issue with your animations only firing once is a separate issue than above. I'm concerned about this part:
return this.props.pages.pages[0].acf.components &&
this.props.pages.pages[0].acf.components.map((block, index) => {
let Component = componentMapping[block.acf_fc_layout];
return <Component key={index} data={block} id={index} />;
});
Returning a variable with the && operator can give you funny results. Try doing some explicit if statements. Or honestly just change your code to this:
if (this.props.pages.pages[0]) {
return this.props.pages.pages[0].acf.components.map((block, index) => {
let Component = componentMapping[block.acf_fc_layout];
return <Component key={index} data={block} id={index} />;
});
}
Another thought I had was to see what happens when you omit that component creation (componentMapping[block.afc_fc_layout]) and just return a <div>. See what happens and see if that's what's causing you trouble.
Related
I have a Vue component that is an image slider, and I use transform: translateX to cycle through the images. I based my work off of Luis Velasquez's tutorial here: https://dev.to/luvejo/how-to-build-a-carousel-from-scratch-using-vue-js-4ki0
(needed to make a couple of adjustments, but it follows it pretty closely)
All works fairly well, except I get a strange flicker after the sliding animation completes, and I cannot figure out why.
In my component I have:
<button
:class="btnClass"
#click="prev"
#keyup.enter="prev"
#keyup.space="prev"/>
<button
:class="btnClass"
#click="next"
#keyup.enter="next"
#keyup.space="next" />
<div class="slider-wrap">
<div class="slider-cards">
<div ref="slidewrap"
v-hammer:swipe.horizontal="handleSwipe"
class="cards"
>
<div class="inner" :style="innerStyles" ref="inner">
<SliderCard v-for="(item, index) in slides"
:key="`${item.id}-${index}`"
:item="item"
/>
</div>
</div>
</div>
</div>
The navigation of those is handled via the following methods (just including the "next" navigation one to spare some redundancy here:
next () {
// translate the inner div left, push first element to last index, and slide
// inner div back to original position
if (this.transitioning) {
return;
}
this.transitioning = true;
const elementToMoveToEnd = this.slides[2];
this.moveLeft();
this.afterTransition(() => {
this.resetTranslate();
this.slides.splice(0, 1);
this.slides.push(elementToMoveToEnd);
this.transitioning = false;
});
},
resetTranslate() {
/// NOTE: this.navigationStep is just a static px value set up elsewhere in my component, based on screenwidth -- it just sets how much the slider should translate based on the card widths.
this.innerStyles = {
transition: 'none',
transform: `translateX(-${this.navigationStep})`
};
},
moveLeft() {
this.innerStyles = {
transform: `translateX(-${this.navigationStep})
translateX(-${this.navigationStep})`
};
},
afterTransition (callback) {
const listener = () => {
// fire the callback (reorganizing slides, resetting the translation),
// and remove listener immediately after
callback();
this.$refs.inner.removeEventListener('transitionend', listener);
};
this.$refs.inner.addEventListener('transitionend', listener);
},
In my CSS, I control the translation animation via:
.inner {
transition: transform 0.2s linear;
white-space: nowrap;
-webkit-transform-style: preserve-3d;
}
(the preserve-3d was a desperate attempt to fix the flickering).
My slides move, but there is a flash immediately after they move -- I suspect it has something to do with competing translations/transitions in the moveLeft and resetTranslation methods, but I've messed with those, and nothing has improved the flashing. If anyone has any ideas, I'd really appreciate it!
I've been stuck trying to get some divs to simply fade in when they become visible on the screen. Maybe I'm missing something, but here's the code.
I'm using the modules React-Spring V9 and React-Visibility-Sensor.
Parent Component's Render:
{
ArrayOfText.map(text => (
<VisibilitySensor key={text}>
{({ isVisible }) => (
<MyItem isVisible={isVisible} text={text} />
)}
</VisibilitySensor>
))
}
Child Component:
export default function MyItem({ text, isVisible }) {
const animatedStyle = useSpring({
config: { ...config.gentle },
to: {
opacity: isVisible ? 1 : 0
}
});
return (
<animated.div style={animatedStyle} className='large-header-text'>
{text}
</animated.div>
);
}
This works in that divs will appear on screen with a slight delay after they come into view. The problem I'm having is there's no animation. It's just opacity 0, then wait ~1 second, then instantly opacity: 1.
If anyone has run into this issue before please let me know how you solved it! Thank you.
I discovered the problem. I doubt this will help anyone else, but the reason it wasn't animating was because I was trying to have text fade in that had the following styles applied to it:
background: -webkit-linear-gradient(60deg, #1369BF, #B07D2E, #8FD714);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
This gives the text a gradient color, but I should have known this would prevent something like animating opacity.
What I want to do is, I've an Image component which shows some loading state as placeholder colour (via css background-colour) till image loads then swap it with the actual image.
Css
.show-img {
opacity: 1;
transition: all 0.3s ease-in;
}
.hide-img {
background-color:#eee;
opacity: 0;
transition: all 0.3s ease-in;
}
Using state
const Image = () => {
const [loading, setLoading] = useState(false);
const onImgLoad = () => {
setLoading(true);
};
return (
<img
src="https://i.picsum.photos/id/498/200/300.jpg"
className={loading ? 'hide-img' : 'show-img'}
onLoad={onImgLoad}
/>
);
};
Using refs
const Image = () => {
const ref = useRef();
const onImgLoad = (e) => {
//if img is loaded
if (e.target.src && ref.current) {
ref.current?.classList?.remove('hide-img');
ref.current?.classList?.add('show-img');
}
};
return (
<img
ref={ref}
src="https://i.picsum.photos/id/498/200/300.jpg"
className="hide-img"
onLoad={onImgLoad}
/>
);
};
I want to know which one of the approach is more performant and why? I was thinking about avoiding re-rendering due to state update for such a basic task (maybe).
PS: I've this Image component inside the carousel and there are multiple carousels on the page.
Thank you,
Theoretically speaking, I would argue that using state is more performant as the state is handled by react and its virtual DOM which is way faster than updating the DOM directly with useRef. Additionally, using state warranties the component re-rendering but optimised by react, which means only when its needed. I used the following articles to reach to this conclusion.
https://medium.com/swlh/useref-explained-76c1151658e8
https://blog.logrocket.com/usestate-vs-useref/
Nonetheless you may have to implement a profiler to check if this is true, as for the final user it may not look as the fastest solution, due to other circumstances outside react, like the amount of images, the connection speed and whether the images are in a CDN or not, just to mention some.
I been trying to make a Masonry gallery with a sequential fade-in effect so that the pictures fade in one by one. And there is also a shuffle feature which will randomize the images and they fade in again after being shuffled.
here is the demo and the code:
https://tuo1t.csb.app/
https://codesandbox.io/s/objective-swartz-tuo1t
When first visiting the page, the animation is correct. However once we click on the shuffle button, something weird happened: There are often some pictures don't fade-in sequentially after the image before them faded in, there is even no fade-in animation on them, they just show up out of order.
The way I achieved this animation is by adding a delay transition based on the index of the image, and use ref to track images.
first I initialize the ref
let refs = {};
for (let i = 0; i < images.length; i++) {
refs[i] = useRef(null);
}
and I render the gallery
<Mansory gap={"1em"} minWidth={minWidth}>
{imgs.map((img, i) => {
return (
<PicContainer
index={img.index}
selected={isSelected}
key={img.index}
>
<Enlarger
src={img.url}
index={img.index}
setIsSelected={setIsSelected}
onLoad={() => {
refs[i].current.toggleOpacity(1); <--- start with zero opacity images till those are loaded
}}
ref={refs[i]}
realIndex={i}
/>
</PicContainer>
);
})}
</Mansory>
for the every image component
class ZoomImg extends React.Component {
state = { zoomed: false, opacity: 0 };
toggleOpacity = o => {
console.log("here");
this.setState({ opacity: o }); <-- a setter function to change the opacity state via refs:
};
render() {
const {
realIndex,
index,
src,
enlargedSrc,
setIsSelected,
onLoad
} = this.props;
return (
<div style={{ margin: "0.25rem" }} onLoad={onLoad}>
<Image
style={{
opacity: this.state.opacity,
transition: "opacity 0.5s cubic-bezier(0.25,0.46,0.45,0.94)",
transitionDelay: `${realIndex * 0.1}s` <--- add a delay transition based on the index of the image.
}}
zoomed={this.state.zoomed}
src={src}
enlargedSrc={enlargedSrc}
onClick={() => {
this.setState({ zoomed: true });
setIsSelected(index);
}}
onRequestClose={() => {
this.setState({ zoomed: false });
setIsSelected(null);
}}
renderLoading={
<div
style={{
position: "absolute",
top: "50%",
color: "white",
left: "50%",
transform: "translateY(-50%} translateX(-50%)"
}}
>
Loading!
</div>
}
/>
</div>
);
}
}
I used console.log("here"); in the setter function, which will be called for changing the opacity state via refs. There are 16 images, so initially it is called 16 times. But when I clicked on the shuffle button, you can see that it is called fewer than 16 times because some of the pictures show up directly without fading in.
I been struggling with this problem for days and really hope someone can give me some hints.
The problem is that your are adding only some new images in the shuffle method, one approach is to apply 0 opacity to all refs first, then wait a few ms to add 1 opacity again, like here.
But, I would recommend a better approach for animation, I love shifty and its Tweenable module.
early days with Facebook ReactJS. Simple CSS fade-in transition. It works as expected with ReactJS v0.5.1. It doesn't with v11.1, v12.0, v12.1. Here's the CSS and JSX:
CSS
.example-enter {
opacity: 0.01;
transition: opacity .5s ease-in;
}
.example-enter.example-enter-active {
opacity: 1;
}
.example-leave {
opacity: 1;
transition: opacity .5s ease-in;
}
.example-leave.example-leave-active {
opacity: 0.01;
}
JSX for ReactJS v12.1
/**#jsx React.DOM*/
var ReactTransitionGroup = React.addons.CSSTransitionGroup;
var HelloWorld = React.createClass({
render: function() {
return (
<ReactTransitionGroup transitionName="example">
<h1>Hello world</h1>
</ReactTransitionGroup>
);
}
});
React.render(<HelloWorld />, document.body);
Here's the list of Codepens:
v0.5.1 http://codepen.io/lanceschi/pen/ByjGPW
v0.11.1 http://codepen.io/lanceschi/pen/LEGXgP
v0.12.0 http://codepen.io/lanceschi/pen/ByjGOR
v0.12.1 http://codepen.io/lanceschi/pen/YPwROy
Any help appreciated.
Cheers,
Luca
It looks like CSSTransitionGroup used to animate on initial mount, but it doesn't any more as of React v0.8.0 or so. See https://github.com/facebook/react/issues/1304 for a bit more info.
One solution is to simply mount the <h1> after <HelloWorld> is mounted, like so:
/**#jsx React.DOM*/
var ReactTransitionGroup = React.addons.CSSTransitionGroup;
var HelloWorld = React.createClass({
getInitialState: function() {
return { mounted: false };
},
componentDidMount: function() {
this.setState({ mounted: true });
},
render: function() {
var child = this.state.mounted ?
<h1>Hello world</h1> :
null;
return (
<ReactTransitionGroup transitionName="example">
{child}
</ReactTransitionGroup>
);
}
});
React.render(<HelloWorld />, document.body);
Live example: http://codepen.io/peterjmag/pen/wBMRPX
Note that CSSTransitionGroup is intended for transitioning child components as they're dynamically added, removed, and replaced, not necessarily for animating them on initial render. Play around with this TodoList Codepen (adapted from this example in the React docs) to see what I mean. The list items fade in and out as they're added and removed, but they don't fade in on the initial render.
EDIT: A new "appear" transition phase has been introduced recently to allow for animation-on-mount effects. See https://github.com/facebook/react/pull/2512 for details. (The commit has already been merged into master, so I imagine it'll be released with v0.12.2.) Theoretically, you could then do something like this to make the <h1> fade in on mount:
JS
...
<ReactTransitionGroup transitionName="example" transitionAppear={true}>
<h1>Hello world</h1>
</ReactTransitionGroup>
...
CSS
.example-appear {
opacity: 0.01;
transition: opacity .5s ease-in;
}
.example-appear.example-appear-active {
opacity: 1;
}
I looked into the issue a little deeper. With the current version of ReactJS it seems not possible to make an initial CSS transition. More info and thoughts here.
Most probably things are gonna change with v0.13.x. You can have a look at the source code which features a transitionAppear prop.
EDIT: I downloaded from GitHub the latest ReactJS (v0.13.0 - alpha) and built it. Everything now works accordingly if you make use of transitionAppear prop (is to be set true explicitly). Here below you'll find the updated CSS and JSX as well as the live example:
CSS:
.example-appear {
opacity: 0.01;
transition: opacity 0.5s ease-in;
}
.example-appear.example-appear-active {
opacity: 1;
}
JSX for ReactJS v0.13.0 - alpha:
/**#jsx React.DOM*/
var ReactTransitionGroup = React.addons.CSSTransitionGroup;
var HelloWorld = React.createClass({
render: function() {
return (
<ReactTransitionGroup transitionName="example" transitionAppear={true}>
<h1>Hello world</h1>
</ReactTransitionGroup>
);
}
});
React.render(<HelloWorld />, document.body);
Live example: http://codepen.io/lanceschi/pen/NPxoGV
Cheers,
L
appear
Normally a component is not transitioned if it is shown when the <Transition> component mounts. If you want to transition on the first mount set appear to true, and the component will transition in as soon as the <Transition> mounts.
from react-transition-group Docs
Example of usage
JSX:
<TransitionGroup>
<CSSTransition classNames="fade" appear={true} >
<h1>Hello world!</h1>
</CSSTransition>
</TransitionGroup>
CSS:
.fade-appear {
opacity: 0.01;
z-index: 1;
}
.fade-appear.fade-appear-active {
opacity: 1;
transition: opacity 300ms ease-in;
}
As of:
React v16.6.3
react-transition-group v2.5.1