It is been over a week now, and I can't get it to work!, I am trying to make a transition between react pages components to have it scroll up and down for each page.however, on exit page the second page takes longer to be displayed and I can't figure out how to sync it so while the first page goes up, the second page start to goes up as well at the same time.
I am using AnimatePresence with exitBeforeEnter wrapping the app component.
Appreciate any help to the right directions.
Below are my variants for each component
const containerVariants = {
hidden: {
opacity: 0,
y: "-100vh",
},
visible: {
opacity: 1,
y: 0,
transition: {
type: "spring",
stiffness: 15,
damping: 10,
mass: 0.4,
staggerChildern: 0.4,
// opacity: { duration: 0.02 },
when: "beforeChildren",
},
},
exit: {
opacity: 0,
y: "-100vh",
zIndex: 0,
transition: {
type: "spring",
stiffness: 15,
opacity: { duration: 3 },
},
},
};
const Home = () => {
return (
<motion.div
variants={containerVariants}
initial="hidden"
animate="visible"
exit="exit"
>
< code goes here>
</motion.div>
You might already have it, but did you add the keys to pages inside <AnimatePresence? In my experience forgetting them caused issues with animating between pages/components.
Another issue I encountered was because of the key not being set on the immediate child of <AnimatePresence> (I had layout components between AnimatePresence and the pages).
Maybe you already have this all setup correctly, just to be sure :)
In my current project:
<AnimatePresence exitBeforeEnter>
<Component
{...pageProps}
key={router.route}
err={err}
/>
</AnimatePresence>
Related
I'm trying to add a CSS keyframe animation to my Material UI styled component but adding a keyframe animation seems to throw an error. Here's what my code looks like:
const PulsatingCircle = styled("div")(({
theme,
}) => ({
"#keyframes pulsate": {
from: {
opacity: 1,
transform: "scale(1)",
},
to: {
opacity: 0,
transform: "scale(2)",
},
},
background: theme.palette.primary.main,
borderRadius: "100%",
animation: "$plusate 1s infinite ease",
position: "absolute",
zIndex: -2,
}));
This code returns the following error:
TypeError: container.addRule(...).addRule is not a function
Array.onProcessStyle
src/optimized-frontend/node_modules/jss-plugin-nested/dist/jss-plugin-nested.esm.js:93
90 | }));
91 | } else if (isNestedConditional) {
92 | // Place conditional right after the parent rule to ensure right ordering.
> 93 | container.addRule(prop, {}, options) // Flow expects more options but they aren't required
| ^ 94 | // And flow doesn't know this will always be a StyleRule which has the addRule method
95 | // $FlowFixMe[incompatible-use]
96 | // $FlowFixMe[prop-missing]
Is there a solution to use #keyframes inside of a styled component like this?
Note: I'm using Material UI's built in styles API and not styled components.
You declared the animation with pulsate then called it with plusate... Fix that first! Then try removing the $ from the animation name.
I'm not sure which version of Material-UI you're using, but for MUI v5 this works:
const Keyframes = styled("div")({
"#keyframes pulsate": {
from: {
opacity: 1,
transform: "scale(1)"
},
to: {
opacity: 0,
transform: "scale(2)"
}
},
animation: "pulsate 1s infinite ease",
});
Above code in a working sandbox
Another example I found in the MUI docs
I made a progress bar component:
import React, {useState, useEffect} from "react";
var ProgressBar = ({percentage, duration}) => {
let [style, setStyle] = useState({
"position":"relative"
})
useEffect(()=>{
setStyle({
"top":"0px",
"position":"relative",
"height":"100%",
"backgroundColor": "lightblue",
"color": "lightgrey",
"left": "0px",
"transition": `width ${duration}s`,
"width": percentage+"%"
});
}, [])
var cntStyle = {
"display": "block",
"width": "150px",
"left":"0px",
"backgroundColor":"grey",
"height":"15px",
"position": "relative"
}
return (
<div className="progressbar-container"
style={cntStyle}>
<div className="progress" style={style}></div>
</div>
);
}
export default ProgressBar;
And use it like
<ProgressBar
percentage="80"
duration="5" />
I wonder why the transition does not work. It directly show 80% progress bar.
This doesn't work because there is no initial value of the width to transition from.
"CSS transitions allows you to change property values smoothly, over a given duration." read more here
This transition should work if you change the percentage from one value(0) to another(80).
A workaround for this would be to update the component prop with an interval or initialize the component style with the value 0 for the progress bar width
I want to reproduce that style in my component (the border bottom).
I am basically loading a rectangular image, then I want to rounded it at the bottom.
I think about :
border-bottom-right-radius and left, but it is not accurate with the custom shape of the screen.
transform scaleX, but it scale the image proportion (obviously)
create a white image with the custom shape and load it at the bottom of the screen, but I want to add a shadow at the bottom of the image... With a white image I could not do that...
Is there a way to do that in a proper style with React-Native ?
Thank you !
This is possible, but it's a little tricky. The idea is you need to create a "mask" of sorts that you can apply the shape to. After doing that you can put the image as a child of the mask element so that it will essentially mask the area you want. It's probably possible to do it with a dynamic size, but I didn't take the time to come up with that solution, i'll leave that to you ;)
Hopefully this'll get you going in the right direction though.
First lets setup the app structure
class App extends Component {
render() {
return (
<View style={styles.app}>
<Mask />
</View>
);
}
}
Pretty straightforward, just a basic app with a mask component. I made it a component so you can pass props to it in the future (like the image uri for instance).
Then the mask component
const logoUri = `http://66.media.tumblr.com/86b941b3445b80a518ea51208f48ab35/tumblr_ntpi99a6Pl1uounv1o1_500.png`;
const Mask = (props) => (
<View style={styles.maskContainer}>
<View style={styles.mask}>
<Image
source={{ uri: logoUri }}
style={styles.img}
/>
</View>
</View>
)
The maskContainer is a positioning element to help center the image.
The mask uses the oval style approach but to get the edges to not round like border radius, we have to scale it 2x
The img style needs to reverse the scaling so that the image itself is not warped :)
const styles = StyleSheet.create({
app: {
marginHorizontal: "auto",
maxWidth: 500,
backgroundColor: "#e0e0e0",
width: 700,
height: 700
},
mask: {
width: 200,
height: 470,
borderBottomLeftRadius: 100,
borderBottomRightRadius: 100,
overflow: "hidden",
transform: [{ scaleX: 2 }]
},
img: {
height: 470,
width: 299,
left: 25,
position: "absolute",
transform: [{ scaleX: 0.5 }, { translate: "-50%" }]
},
maskContainer: {
position: "absolute",
left: "50%",
transform: [{ translate: "-50%" }]
}
});
See it working on this fiddle!
#John Ruddell's answer is very helpful.
I am able to design this by creating a mask and applied through SVG path. So, you need to install 2 libraries.
react-native-svg (expo install react-native-svg)
react-native-masked-view (npm install --save #react-native-community/masked-view)
Following code respect the aspect ratio of image. So, you need the set the aspect ratio of your image and adjust the value of curveAdjustment in order to achieve your desired sharpness of curve.
import React from "react";
import {
Image,
View,
StyleSheet,
Text,
useWindowDimensions,
} from "react-native";
import MaskedView from "#react-native-community/masked-view";
import Svg, { Path } from "react-native-svg";
const logoUri = `http://66.media.tumblr.com/86b941b3445b80a518ea51208f48ab35/tumblr_ntpi99a6Pl1uounv1o1_500.png`;
function SplashScreen(props) {
const windowWidth = useWindowDimensions().width;
const imageAspectWidth = 375;
const imageAspectHeight = 332;
const curveAdjustment = 40;
const maskHeight = (imageAspectHeight / imageAspectWidth) * windowWidth;
const scaleFactor = imageAspectWidth / imageAspectHeight;
const scaledHeight = scaleFactor * maskHeight;
const controlPointX = windowWidth / 2.0;
const controlPointY = scaledHeight + curveAdjustment;
const curveCenterPointY = (controlPointY - maskHeight) / 2;
return (
<View style={styles.main}>
<MaskedView
style={[
styles.mask,
{
height: controlPointY - curveCenterPointY,
},
]}
maskElement={
<Svg height="100%" width="100%">
<Path
d={`M0 0 L${windowWidth} 0 L${windowWidth} ${maskHeight} Q${controlPointX} ${controlPointY} 0 ${maskHeight} Z`}
fill={"#fff"}
/>
</Svg>
}
>
<Image source={{ uri: logoUri }} style={styles.image} />
</MaskedView>
<Text>{"Tag line"}</Text>
</View>
);
}
const styles = StyleSheet.create({
image: {
flex: 1,
resizeMode: "stretch",
},
main: {
flex: 1,
},
mask: {
backgroundColor: "orange",
width: "100%",
},
});
export default SplashScreen;
Final result
Yep. clip-path property!
Just add:
clip-path: circle(69.3% at 50% 30%)
To your class and it will work.
if you want to create it yourself, there's a generator here:
https://bennettfeely.com/clippy/
I'm creating a skills chart in a React component, where each bar starts with a short width and then it expands to a specified width after 0.5 second. The width is related to the skill level, defined in the following array:
const skills = [
{ skillName: 'JavaScript', level: 10, color: 'bca538' },
{ skillName: 'HTML', level: 9, color: 'af4336' },
{ skillName: 'CSS', level: 9, color: '2f81b7' },
]
The chart is represented in the following code:
<div className="chart__bars">
{skills.map((skill, index) => {
const { skillName, level, color } = skill
const { scale } = this.state
return (
<div
className={'chart__bars__item'}
key={skillName}
style={{
background: `#${color}`,
height: `${(100 / skills.length) * (index + 1)}%`,
width: `${scale ? level * 10 : 30}%`,
zIndex: skills.length - index,
}}
>
<h4
style={{
opacity: `${scale ? 1 : 0}`,
}}
>
{skillName}
</h4>
</div>
)
})}
</div>
After the component is mounted, it triggers a state change after 0.5 second, which should then expand each bar (logic for this is inside the style property in the code above). Here's the initial state:
state = {
scale: false,
}
And here's where I change it:
componentDidMount() {
setInterval(() => {
this.setState({ scale: true })
}, 500)
}
It works fine on the browser, but not on mobile. Using the devtools I can see the width being updated, but it won't expand. If I uncheck the tick box for the width, and then check it again, then the width expands (which should happen automatically).
The working example is on my website: https://marcelcruz.io.
Any thoughts would be much appreciated!
Thanks.
I have a project with React and Material UI.
I need some guidance on how to animate a grid item size change. The item is by default sm={3}, when the user hovers the item, this changes to sm={6}.
Here is my code:
<Grid
item
xs={this.state.hoverItem ? 6 : 3}
spacing={24}
onMouseEnter={this.handleItemHover}
onMouseLeave={this.handleItemHoverLeave}
>
<Paper
elevation={this.state.hoverItem ? 5 : 1}
className={classNames(
classes.card,
this.state.hoverItem && classes.cardHover
)}
>
</Paper>
</Grid>
And this is how I am doing my JSS:
card: {
...theme.mixins.gutters(),
paddingTop: theme.spacing.unit * 2,
paddingBottom: theme.spacing.unit * 2,
transition: theme.transitions.create("width", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen
})
},
cardHover: {
transition: theme.transitions.create("width", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen
})
},
I thought this should animate it. However, the transitions are not doing anything.
Hi it was really an interesting problem of the day, I have tried to implement with simple transitions on hover. You can see here on codesandbox :
Animate Switching Grid Size
PS I have added your transition property as comment without giving width It worked great. You can report issue on Material-UI this is the right time.
Please let me know if you the issue still persists.