skip to Main Content

I have a list of items. When I first load the page, I want them to slide in sequentially in rapid succession and then shrink slightly when they arrive at their final destination — sort of like what would happen if you dropped a stack of pillows, or maybe a giant stack of sliced deli meat. (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)?

2

Answers


  1. The best way to implement is CSS3 animation.

    If you want any help about implementation in React and MUI, please comment.

    A simple Demo here, you may need to change parameter about translate3d and scaleY in animation as you like.

    <h1 class="callout-title_first animate__animated">Animate.css</h1>
    <h1 class="callout-title_bottom animate__animated">Animate.css</h1>
    <style>
      h1 {
        margin:0;
      }
    
      @keyframes slideInDown_first {
      0%{
        transform: translate3d(0,-100%,0);
      }
      33%{
        transform: translate3d(0,50%,0) ;
      }
      40%{
        transform-origin: bottom;;
        transform: translate3d(0,50%,0) scaleY(0.5);
      }
      to{
          transform: scaleY(0.5) translate3d(0,100%,0);
      }
    }
    
      @keyframes slideInDown_second {
      0%{
        transform: translate3d(0,-100%,0);
      }
      33%{
        transform: translate3d(0,0,0) ;
      }
      40%{
        transform-origin: bottom;;
        transform: translate3d(0,0,0) scaleY(0.5);
      }
      66%{
        transform: translate3d(0,-20%,0) scaleY(0.5);
      }
      to{
          transform: translateZ(0);
          transform: scaleY(0.5);
      }
    }
    .animate__slideInDown_first {
      animation-name: slideInDown_first
    }
    .animate__slideInDown_second {
      animation-name: slideInDown_second
    }
    .animate__animated {
        animation-duration: 3000ms;
        animation-fill-mode: both
    }
    </style>
    <script>
      document.querySelector('.callout-title_first').classList.add('animate__slideInDown_first')
      document.querySelector('.callout-title_bottom').classList.add('animate__slideInDown_second')
    </script>
    Login or Signup to reply.
  2. To do this, I would use the CSS animations :

    import { useState, useEffect } from 'react';
    import { Button, Stack } from '@mui/material';
    import { styled } from '@mui/system';
    
    const ITEM_HEIGHT = 50;
    
    const Wrapper = styled('div')({
      position: 'relative',
      overflow: 'hidden',
      height: ITEM_HEIGHT * 3,
    });
    
    const Item = styled('div')(({ theme }) => ({
      position: 'absolute',
      width: '100%',
      height: ITEM_HEIGHT,
      background: 'green',
      display: 'flex',
      justifyContent: 'center',
      alignItems: 'center',
      fontSize: 24,
      fontWeight: 'bold',
      color: theme.palette.common.white,
      animation: 'slideIn 0.3s ease-out forwards, squish 0.3s ease-out forwards',
      animationDelay: '0.1s',
      transformOrigin: 'center bottom',
    }));
    
    const SquishSlideStack = ({ children }) => {
      const [index, setIndex] = useState(-1);
    
      useEffect(() => {
        let timer;
        if (index < children.length - 1) {
          timer = setTimeout(() => {
            setIndex((i) => i + 1);
          }, 200);
        }
        return () => clearTimeout(timer);
      }, [index, children]);
    
      return (
        <Stack>
          <Wrapper>
            {children.map((child, i) => (
              <Item
                key={i}
                style={{
                  top: ITEM_HEIGHT * i,
                  animationDelay: `${i * 0.1}s`,
                  transform: `scale(${index === i ? '1,1.5' : '1,1'})`,
                }}
              >
                {child}
              </Item>
            ))}
          </Wrapper>
          <Button disabled={index >= children.length - 1} onClick={() => setIndex((i) => i + 1)}>
            Next
          </Button>
        </Stack>
      );
    };
    
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search