The component should satisfy the following:
I'm stuck in the third step, where the CSS is not working as expected.
The sandbox is here: https://codesandbox.io/s/gracious-kapitsa-cxtsn6
This is the CSS properties for the expected sliding transition:
const styleSlideRtoL = {
position: "relative",
left: "150px",
transition: "transform 0.3s ease-in-out",
transform: "translateX(-150px)",
color: "blue",
};
They are used in the list elements as follows:
const div1 = <div style={styleSlideRtoL}>Lorem Ipsum 1</div>;
const div2 = <div style={styleSlideRtoL}>Lorem Ipsum 2</div>;
const div3 = <div style={styleSlideRtoL}>Lorem Ipsum 3</div>;
const div4 = <div style={styleSlideRtoL}>Lorem Ipsum 4</div>;
const div5 = <div style={styleSlideRtoL}>Lorem Ipsum 5</div>;
const divs = [div1, div2, div3, div4, div5];
With the above code, there is no transition effects observed.
However, if I use a different style (no sliding effect) for the 1st, 3rd, and 5th div, then the 2nd and 4th div will get the sliding effect as expected.
const styleNoSlide = {
// position: "relative",
// left: "150px",
// transition: "transform 0.3s ease-in-out",
// transform: "translateX(-150px)",
color: "orange",
}
const div1 = <div style={styleNoSlide}>Lorem Ipsum 1</div>;
const div2 = <div style={styleSlideRtoL}>Lorem Ipsum 2</div>;
const div3 = <div style={styleNoSlide}>Lorem Ipsum 3</div>;
const div4 = <div style={styleSlideRtoL}>Lorem Ipsum 4</div>;
const div5 = <div style={styleNoSlide}>Lorem Ipsum 5</div>;
Questions:
styleSlideRtoL
for all the divs?Firstly, simply mounting a DOM element that has the transition
CSS property will not cause the "enter" animation to happen. For transition
to animate something an element has to first be loaded with one set of CSS properties, then change to a new set of CSS properties. Inserting a <div>
with the CSS properties in their final state won't cause any animation to happen.
So then why does the latter example half-work? When you are switching between these divs by updating the selectedIndex
state, React is using its reconciliation process, which tries hard to minimise the amount of DOM operations to change from the previous DOM state to the next one. When you change from the first to the second <div>
, React is actually just patching the existing <div>
by changing its attributes to the new passed styles.
Suddenly, this satisfies the requirements of transition
that a DOM node's CSS properties must change and not just be inserted with the final state.
You can demonstrate this by forcing React's algorithm to not reuse the same base <div>
element by giving each of them a unique key:
const div1 = <div key={1} style={styleNoSlide}>Lorem Ipsum 1</div>;
const div2 = <div key={2} style={styleSlideRtoL}>Lorem Ipsum 2</div>;
const div3 = <div key={3} style={styleNoSlide}>Lorem Ipsum 3</div>;
const div4 = <div key={4} style={styleSlideRtoL}>Lorem Ipsum 4</div>;
const div5 = <div key={5} style={styleNoSlide}>Lorem Ipsum 5</div>;
You will notice now that React is treating them as unique elements, the animation is completely gone again.
The easiest way you could achieve what you actually want is to instead of swapping between divs, render all of the divs all of the time, but make it so only one is actually visible (the rest will overflow out of view). Then adjust the transform
position in response to clicking the buttons.
import "./styles.css";
import { useState } from "react";
export default function App() {
const [selectedIndex, setSelectedIndex] = useState(0);
const styleSlideWrapper = {
width: "150px",
overflow: "hidden",
border: "3px solid magenta",
borderRadius: "6px",
whiteSpace: "nowrap"
};
const styleSlideRtoL = {
position: "relative",
color: "blue",
display: "inline-block",
width: "100%",
transition: "transform 0.3s ease-in-out",
transform: `translateX(-${100 * selectedIndex}%)`
};
const div1 = <div style={styleSlideRtoL}>Lorem Ipsum 1</div>;
const div2 = <div style={styleSlideRtoL}>Lorem Ipsum 2</div>;
const div3 = <div style={styleSlideRtoL}>Lorem Ipsum 3</div>;
const div4 = <div style={styleSlideRtoL}>Lorem Ipsum 4</div>;
const div5 = <div style={styleSlideRtoL}>Lorem Ipsum 5</div>;
const divs = [div1, div2, div3, div4, div5];
const handleBack = () => {
if (selectedIndex > 0) {
setSelectedIndex(selectedIndex - 1);
}
};
const handleNext = () => {
if (selectedIndex < divs.length - 1) {
setSelectedIndex(selectedIndex + 1);
}
};
return (
<>
<h1>Click a button to change item.</h1>
<div style={styleSlideWrapper}>{divs}</div>
<button onClick={() => handleBack()} disabled={selectedIndex === 0}>
Prev
</button>
<button
onClick={() => handleNext()}
disabled={selectedIndex === divs.length - 1}
>
Next
</button>
</>
);
}
Working sandbox.
Note key
isn't needed here because we are rendering all of the divs and not switching between them so the problem with React patching a single div never occurs anyway.
The key reason for doing this though is because you want to show the previous one exiting as well. As well as that, the elements are mounted when the page loads, and then the transform is adjusted after which satisfies the earlier outline of how transition
works.
Worth noting many great animation libraries make life easier like framer-motion
for example.