What are the steps to create a "gooey glide" animation using React and MUI?

I am looking to create a unique animation for my list of items on a web page. My goal is to have the items slide in one by one with rapid succession and then slightly shrink once they reach their final position, similar to how pillows might fall or like a stack of sliced deli meat (I remember seeing this animation before but can't find an example). If anyone knows where I can find a reference, please share it.

Below is my basic attempt at achieving this effect:


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>
    )
}

View the Codesandbox example here.

The sliding animation works as intended, but adding the scaling part breaks the sliding effect and doesn't scale correctly. How can I achieve this animation successfully in React (preferably using MUI)?

Answer №1

Utilizing CSS3 animation is the most effective approach.

If you require assistance with implementation in React and MUI, feel free to leave a comment.

A straightforward demonstration is available here, where you can adjust parameters related to translate3d and scaleY in the animation according to your preferences.

<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>

Answer №2

To achieve this task, I will incorporate 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>
  );
};

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Implement a transition effect for when elements change size using the `.resizable().css` method

I attempted to incorporate a deceleration effect when resizing an element back to its original size using the resizable method. The slowdown effect should only trigger during the click event (when the "Click-me" button is pressed), not while manipulating ...

Updating the background color using typescript

Before transitioning to angular development, I had experience working with vanilla Javascript. I encountered a challenge when trying to modify the css properties of specific elements using Typescript. Unfortunately, the traditional approach used in Javascr ...

Receiving an error of "Object is not a function" when trying to pass an array into a

My React application includes an array of ski days, each with information about different ski trips: allSkiDays:[ { resort: "Squaw Valley", date: new Date("12/24/2018"), powder: true, backcountry: false }, ...

Is Typescript the Ultimate Replacement for propTypes in React Development?

After diving into Typescript for the first time and exploring related articles, it appears that when using Typescript with React, propTypes definitions may no longer be necessary. However, upon examining some of the most popular React Component Libraries: ...

Managing empty props in React applications

I am facing an issue where I need to display an image fetched from an API on app start, but when I try to render it in React, the application crashes with the error message: TypeError: Cannot read property 'icon' of undefined. Although the icon ...

What's the best way to modify the React state following a successful tRPCv10 request?

Looking to adapt the new tRPC version 10 for a basic shopping list CRUD Nextjs app. Managed to set up the tRPC endpoint successfully with "get all" and "create" handlers, both working fine when tested from the front end. However, facing an issue updating t ...

Purging data when no input is detected in React.js

I need to figure out a reliable way to detect when my input field has been cleared so that I can clear the associated data. Currently, I am using the logic if (searchTerm.length === 0) to check for a cleared input. However, I have also experimented with i ...

Exploring object properties within arrays and nested objects using ReactJS

Within the react component PokemonInfo, I am looking to extract the stats.base_stat value from the JSON obtained from https://pokeapi.co/api/v2/pokemon/1/. The issue lies in the fact that base_stat is nested inside an array called stats. My assumption is t ...

Customizing styles in Material UI by utilizing the Root component and the ClassName attribute

Exploring material UI for the first time has been quite enlightening. One interesting discovery I made is that styles in material UI can be customized by using the rule names of classes. For instance, if there's an element like MenuItem where I only ...

Background image that covers the entire page

I am using background-size:cover to ensure that the entire background positioning area is covered by the background image. However, I've noticed that setting it to "cover" sometimes cuts off parts of my background image. Is there another way to achiev ...

replace the tsconfig.json file with the one provided in the package

While working on my React app and installing a third-party package using TypeScript, I encountered an error message that said: Class constructor Name cannot be invoked without 'new' I attempted to declare a variable with 'new', but tha ...

The makeStyles function is unable to interact with dynamic CSS variables

const customStyles = makeStyles({ box: { color: "var(--custom-text) !important", }, }) The CSS variable var(--custom-text) is dynamically changing within the application based on a user toggle. However, the component utilizing this custo ...

I'm having trouble getting my button to float correctly on the right side

Here I am facing a unique situation where I am attempting to align my button input[type="submit"] to the right side. I have allocated space for it and therefore trying to float the button to the right so that it can be positioned next to my input text. U ...

Switching the WordPress dropdown menu from a hover function to a click function

Is it possible to modify the WordPress menu dropdown behavior from hover to onclick? I want the menu to appear when an item is clicked, but WordPress currently requires hovering. Below is what I've tried so far, but it has not been successful. Jquery ...

Tips for customizing the appearance of a highlighted row in MUI5

I'm still learning the ropes with MaterialUI and have recently started using V5. While I've come across code samples for this in v4, I'm a bit unsure on how to achieve the same result in mui5. Specifically, I have a data grid where I would l ...

Testing React Hooks in TypeScript with Jest and @testing-library/react-hooks

I have a unique custom hook designed to manage the toggling of a product id using a boolean value and toggle function as returns. As I attempt to write a unit test for it following a non-typescripted example, I encountered type-mismatch errors that I' ...

In Wordpress, I am using Php to display posts. How can I create a new list item after every 3 posts, with each subsequent post displayed inside the new list item?

I am new to using PHP and feeling a bit confused with the code below, which is a team slider. In the <ul> tag, there is one <li> containing 3 nested <div>s (each representing a person). Can someone review my code and help me improve it? I ...

What happens when browsers encounter unfamiliar at-rules?

CSS at-rules have been part of CSS since the days of CSS2. As CSS evolves with new specifications like @supports, it's interesting to see how different browsers handle unsupported rules. Do major browsers simply ignore unrecognized rules, or do they f ...

What is the best way to send information from child components to their parent in React

I'm facing a scenario where I need to increase the parent value based on actions taken in the children components. Parent Component: getInitialState :function(){ return {counter:0} }, render(){ <CallChild value={this.state.counter}/> ...

Error encountered while implementing onMutate function in React Query for Optimistic Updates

export const usePostApi = () => useMutation(['key'], (data: FormData) => api.postFilesImages({ requestBody: data })); Query Definition const { mutateAsync } = usePostApi(); const {data} = await mutateAsync(formData, { onMutate: ...