Components • 2nd October, 2021 • 3 mins read
How to create a zoom transition animation effect using React & Framer Motion?
Interruption in the user's experience while navigating between pages can often lead to them going off the website. We could improve on that by adding page transitions in our web application which could lead to an uninterrupted experience for the user.
This article aims to explain, how we could create one such page transition animation for a gallery web app using React & Framer Motion. So let's get started with it then! 😉
Project Initialisation
npx create-react-app gallery
Install Dependencies
- Framer Motion for transition animation
- Styled Components for styling needs
- Polished for style helper functions
- React Use for utility hooks
yarn add framer-motion styled-components polished react-use
Images Data Structure
All the images are stored in an array of the following data structure:
{"name": string,"location": string,"variant": string,"creator": {"name": string,"avatar": url_string}}
App Component
ImageGrid
component is called inside App component.
import { useState } from "react";// Externalimport styled from "styled-components";import { motion } from "framer-motion";// Componentsimport ImageGrid from "./components/image-grid";// Stylesimport { Container, Heading } from "./styles/globalStyles";const AppStyles = styled(motion.div)`display: flex;align-items: center;justify-content: center;background-color: #f8ebe2;`;const App = () => {// index of the image being shownconst [selectedImage, setSelectedImage] = useState(-1);return (<AppStyles><Container><Heading>Explore 🇮🇳</Heading><ImageGridselectedImage={selectedImage}setSelectedImage={setSelectedImage}/></Container></AppStyles>);};export default App;
Image Grid Component
This page shows all the images in a masonry grid layout.
import { useState, useEffect } from "react";// Externalimport { AnimateSharedLayout } from "framer-motion";import { useLockBodyScroll } from "react-use";// Componentsimport SinglePicture from "./single-picture";// Stylesimport { Grid } from "./styles";// Dataimport data from "../../data.json";const ImageGrid = ({ selectedImage, setSelectedImage }) => {// helps in preventing body from scrolling when an image page is being shownconst [isScrollLocked, setScrollLocked] = useState(false);useLockBodyScroll(isScrollLocked);useEffect(() => {if (selectedImage !== -1) {setScrollLocked(true);} else {setScrollLocked(false);}}, [selectedImage]);return (<AnimateSharedLayout><Grid>{data.images.map((data, index) => (<SinglePicturekey={`${data.name}-${index}`}isSelected={selectedImage === index}index={index}setSelectedImage={setSelectedImage}data={data}/>))}</Grid></AnimateSharedLayout>);};export default ImageGrid;
Single Picture
This page shows a single image with some image metadata.
// Stylesimport { Flex } from "../../styles/globalStyles";import {Image,SinglePictureContainer,Back,InfoCard,Name,PhotographerName,Avatar,Location,} from "./styles";// Assetsimport images from "../../images";import { CloseIcon } from "../../images/CustomIcons";// animation config and variantsconst spring = {type: "spring",stiffness: 500,damping: 30,};const backVariants = {initial: {opacity: 0,y: -20,},animate: { opacity: 1, y: 0 },};const cardVariants = {initial: {opacity: 0,y: 100,x: "-50%",},animate: { opacity: 1, y: 0, x: "-50%" },};const SinglePicture = ({isSelected,setSelectedImage,index,data: { creator, location, name, variant },}) => {const goBack = () => {setSelectedImage(-1);};return (<SinglePictureContainerisSelected={isSelected}layoutId={`card-container--index-${index}`}transition={spring}variant={variant}>{isSelected && (<BackonClick={goBack}initial="initial"animate="animate"exit="initial"transition={{ delay: 0.2, duration: 0.5 }}variants={backVariants}><CloseIcon /></Back>)}<Imagesrc={images[name]}alt={name}onClick={() => {setSelectedImage(index);}}isExpanded={isSelected}layoutId={`card-image--index-${index}`}/>{isSelected && (<InfoCardinitial="initial"animate="animate"exit="initial"transition={{ delay: 0.1, duration: 0.5 }}variants={cardVariants}><Location>{location}</Location><Name>{name}</Name><Flex><Avatarimage={!!creator.avatar? `${creator.avatar}?q=10&w=50`: images.avatarFallback}/><PhotographerName>{creator.name}</PhotographerName></Flex></InfoCard>)}</SinglePictureContainer>);};export default SinglePicture;
Conclusion
And there we have it, the gallery page transition animation is ready. The Code is available on Github. Would love to hear your valuable feedback in the comments down below.
See you guys 👋🏻 in the next article of this Component series!
Happy coding & Stay safe! ✨