How can I make a "squishy slide" animation in React / MUI? - css

I have a list of items. When I first load the page, I want them to slide in sequentially, one at a time, and then shrink slightly when they arrive at their final destination. (I've seen this animation in the past but I can't find an example and I have no idea what it's called; if anyone can find a link to an example, please post it.)
Here's my highly rudimentary attempt:
import {Button, Slide, Stack, StackProps} from '#mui/material'
import {Box} from '#mui/system'
interface ZoomStackProps extends PropsWithChildren<StackProps> {
timeout: number
}
export default function SquishSlideStack({children, timeout, ...stackProps}: ZoomStackProps) {
const [mountIndex, setMountIndex] = useState(0)
const [squozeIndex, setSquozeIndex] = useState(0)
function increment(n: number) {
if (n < React.Children.count(children) - 1) {
setMountIndex(n)
setTimeout(() => increment(n + 1), timeout)
}
}
useEffect(() => increment(1), [])
return (
<Stack {...stackProps}>
<Button onClick={() => setMountIndex(index => index + 1)}>Next</Button>
{React.Children.map(children, (child, i) =>
i > mountIndex ? (
null
) : (
<Slide
key={i}
in={true}
direction='up'
timeout={1000}
addEndListener={() => setSquozeIndex(i)}
>
<Box bgcolor='green'
width={600}
height={50}
sx={{
transform: i > squozeIndex ? 'scale(1, 1.5)' : 'scale(1, 1)',
transition: 'transform 2s ease'
}}
>
{child}
</Box>
</Slide>
)
)}
</Stack>
)
}
See Codesandbox example.
The sliding part here works, more or less, but only when I leave the squishing/scaling part off. Adding that breaks sliding and also doesn't scale correctly, for some reason.
What's the best way to achieve an animation like this in React (and hopefully in MUI, though that's not required)?

Related

Why is my react-lightbox not going full screen in next.js

I'm using next.js and I was hoping to see my gallery collection like this on click of one of my images which is not happening. Actually, it's like I have just used a normal component, because literally nothing is happening when I click one of my images. Please help.
// this is my app component
import SimpleReactLightbox from 'simple-react-lightbox'
const MyApp = ()=>{
return(
<SimpleReactLightbox>
<Component {...pageProps} />
</SimpleReactLightbox>
)
}
// this is my collection
import { CollectionStyledTypography } from './styles/collectionStyledComponents'
import { SRLWrapper } from 'simple-react-lightbox'
import Image from 'next/image'
const Collection = ({ imagesList = [] }) => {
return (
<SRLWrapper>
<div style={{ margin: '50px' }}>
{imagesList.map((image, index) => (
<CollectionStyledTypography component="div" key={index}>
<Image src={image.src} alt={image.alt} layout="fill" />
</CollectionStyledTypography>
))}
</div>
</SRLWrapper>
)
}
export default Collection
SRL is no longer being developed, and it won't work in React 18. You will have to downgrade to 17 to make it work. I am facing the same problem, nothing happens when i click on an image.

In React, how can I apply a CSS transition on state change, re-mount, or re-render?

Say I have a React functional component with some simple state:
import React, { useState } from 'react'
import { makeStyles } from "#material-ui/core"
export default function Basket() {
const [itemCount, setItemCount] = useState<number>(0)
return (
<div>
<Count count={itemCount} />
<button onClick={() => setItemCount(itemCount + 1)}>
Add One
</button>
</div>
)
}
function Count({count}: {count: number}) {
const classes = useStyles()
return (
<div className={classes.count}>
{count}
</div>
)
}
const useStyles = makeStyles({
count: {
backgroundColor: "yellow",
transition: "backgroundColor 2s ease" // ???
}
}
I want the Count component to apply a property whenever the count changes and then remove it again; say, turn on backgroundColor: yellow for 2 seconds and then gradually fade it over 1 second. What's the simplest way to achieve this?
Note that presumably this could be either triggered by a state change on the parent or by a re-rendering of the child. Alternatively, I could add the key property to <Count/> to force a re-mount of the child:
<Count
key={itemCount}
count={itemCount}
/>
Any of those would be acceptable; I'm looking for the simplest, cleanest solution, hopefully one that doesn't require additional state and is compatible with Material-UI styling APIs.
Just an idea.
const Component = () => {
useEffect(() => {
// will trigger on component mount
return () => {
// will trigger on component umount
}
}, [])
}
...
document.getElementById('transition').classList.add('example')
You can use useEffect along with useRef containing a reference to the element or directly getting it with document.getElementById and then update the transition class that way in component mount/unmount. Not sure if it'll work, I haven't tested it myself.

How to control the styles of many elements in React at one time?

I have a simple game, like this -
game
When you hover a square, it turns blue, if hover again, it turns white.
For this aim I just add styles for event.targer on hover
const handleMouseEnter = (e) => {
const hoveredDiv = e._targetInst.pendingProps.value;
e.target.className = 'blue'
if (hoveredSquares.includes(hoveredDiv)) {
dispatch(actions.removeFromHovered(hoveredDiv))
e.target.style.backgroundColor = 'white';
} else {
dispatch(actions.addToHovered(hoveredDiv))
e.target.style.backgroundColor = 'blue';
}
}
The problem is, when I change a game mode, for example, 10*10, I need to make white all squares.
It is like restart a game, but already hovered squares a still blue of course.
Below is my code for squares
const generateSquares = () => {
if (!currentMode || currentMode === 0) {
return
}
const array = Array.from(Array(currentMode), () => Array(currentMode).fill(currentMode))
return (
<Box style={{ marginTop: 20 }}>
{array.map((rowEl, rowIndex) => {
return (
<Box
key={rowIndex}
>
{rowEl.map((_, colIndex) => {
return (
<Box
value={`row ${rowIndex + 1} col ${colIndex + 1}`}
onMouseEnter={e => handleMouseEnter(e)}
key={colIndex}
></Box>
)
})}
</Box>
)
})}
</Box>
)
}
In Vanilla JS I would use querySelectorAll.
How to control the styles of many elements in React at one time? Preferable using hooks.
Thanks
You can create Base square component. After that each element has to have state.
const [isHovered, setIsHovered] = useState(false)
return <Square
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
className={isHovered ? blue : white} />
When you have to change game then you need to rerender all your components, and states will be refreshed.

Toggle flip cards in React

I'm trying to create a flip card effect where if I click on a card it flips over. However, if I then click on another card, I'd like the original card to flip back. This means having some sort of global toggle on all of the cards but one that gets overridden by the local toggle. I'm not sure of the most React-y way to do this - I've tried implementing it with the useState hook at both levels, but I'm not having much luck.
I'm using styled-components, with the 'flipped' prop determining the Y-transform.
Here's the flip card component, and you can see what I've tried so far:
const PortfolioItem = props => {
const [flipped, setFlipped] = useState(false)
return (
<PortfolioItemStyles onClick={() => setFlipped(!flipped)}>
// what I'm trying to say here is, if the individual card's 'flipped' is set to true,
use that, otherwise use props.flipped which will be set to false
<PortfolioItemInnerStyle flipped={flipped ? flipped : props.flipped}>
<PortfolioItemFront >
{props.image}
<PortfolioImageCover className="img-cover" />
<PortfolioItemHeader>{props.title}</PortfolioItemHeader>
</PortfolioItemFront>
<PortfolioItemBack>
<h1>Hello there</h1>
</PortfolioItemBack>
</PortfolioItemInnerStyle>
</PortfolioItemStyles>
)
}
function PortfolioStyles() {
const [ allFlipped, setAllFlipped ] = useState(false);
return (
<PortfolioContainer>
{portfolioItems.map(item => {
return <PortfolioItem image={item.image} flipped={allFlipped} title={item.title} onClick={() => setAllFlipped(false)} />
})}
</PortfolioContainer>
)
}
The logic I'm using is clearly faulty, but I was wondering what would be the 'best practice' way of doing this? In vanilla JS you'd use a single event handler and use event.target on it to make sure you were isolating the element, but I'm not sure how to handle this in React. Any help would be much appreciated.
I would personally manage the state only on the container component. Let's say you will store an index of the flipped card instead of a true/false status. The onClick would then change the current index and the flipped is computed by checking index === currentIndex. Something like this:
const PortfolioItem = props => {
return (
<PortfolioItemStyles>
<PortfolioItemInnerStyle flipped={props.flipped}>
<PortfolioItemFront >
{props.image}
<PortfolioImageCover className="img-cover" />
<PortfolioItemHeader>{props.title}</PortfolioItemHeader>
</PortfolioItemFront>
<PortfolioItemBack>
<h1>Hello there</h1>
</PortfolioItemBack>
</PortfolioItemInnerStyle>
</PortfolioItemStyles>
)
}
function PortfolioStyles() {
const [ currentFlippedIndex, setCurrentFlippedIndex ] = useState(-1);
return (
<PortfolioContainer>
{portfolioItems.map((item, index) => {
return <PortfolioItem image={item.image} flipped={index === currentFlippedIndex} title={item.title} onClick={() => setCurrentFlippedIndex(index)} />
})}
</PortfolioContainer>
)
}

Animated button with React Spring

I am coming from a React-pose background and like to try React-spring. I have a really simple case which I'd like to transfer to be used with React-spring.
I have the case written in a Codesanbox using React-pose, https://codesandbox.io/s/4wxzm60nk9
I've tried converting this myself, but I just end up confusing myself. Especially now when trying to do it with their hooks API. All help that I can get is super appriciated.
Thank you.
I just made an animated button yesterday, so I refactored it to change its size.
import React, {useState} from "react";
import { Spring, animated as a } from 'react-spring/renderprops';
const SpringButton = () => {
const [pressed, setPressed] = useState(false);
return (
<Spring native from={{scale: 1}} to={{scale: pressed? 0.8 : 1}}>
{({scale}) => (
<a.button
style={{
backgroundColor: 'red',
height: '100px',
width: '100px',
color: 'rgb(255, 255, 255)',
transform: scale.interpolate(scale => `scale(${scale})`)
}}
onMouseDown={() => setPressed(true)}
onClick={() => setPressed(false)}
onMouseLeave={() => setPressed(false)}
>
Click me
</a.button>
)}
</Spring>
);
}
Its not the hook interface yet, but I think it helps you to understand how it works. I t also uses the quicker native rendering. The event handling a bit different from react-pose. And you can tweek the config as well if you want the animation really springy.
import {config} from 'react-spring/renderprops';
<Spring config={config.wobbly} ...>
https://codesandbox.io/s/7zlrkv4kjj
Something like this probably: https://codesandbox.io/s/pyvo8mn5x0
function App() {
const [clicked, set] = useState(false)
const { scale } = useSpring({ scale: clicked ? 0.8 : 1 })
return (
<div className="App">
<animated.button
onMouseDown={() => set(true)}
onMouseUp={() => set(false)}
style={{
backgroundColor: 'red',
height: '100px',
width: '100px',
color: '#FFF',
transform: scale.interpolate(s => `scale(${s})`)
}}
children="Click me"
/>
</div>
)
}
You could also interpolate up-front if you like:
const props = useSpring({ transform: `scale(${clicked ? 0.8 : 1})` })
return <animated.button style={props} />
Unlike pose react-spring does not include gesture stuff, it choses to interface with 3rd party libs for that. For instance: https://github.com/react-spring/react-with-gesture

Resources