Animated sliding page gallery using framer-motion and React.js

The following approach covers how to create an animated sliding page gallery using framer-motion and ReactJS.
Prerequisites:
- Knowledge of JavaScript (ES6)
- Knowledge of HTML and CSS.
- Basic knowledge of ReactJS.
Creating React Application And Installing Module:
Step 1: Create a React application using the following command:
$ npx create-react-app page-gallery
Step 2: After creating your project folder i.e. page-gallery, move to it using the following command.
$ cd page-gallery
Step 3: Add the npm packages you will need during the project.
$ npm install framer-motion @popmotion/popcorn
Open the src folder and delete the following files:
- logo.svg
- serviceWorker.js
- setupTests.js
- App.test.js (if any)
- index.css.
Create a file named PageSlider.js.
Project structure: The project structure tree should look like this:
Project structure
Filename: App.js
Javascript
import React from "react";import { useState } from "react";import { motion, AnimateSharedLayout } from "framer-motion";import PageSlider from "./PageSlider";import "./styles.css";const Pagination = ({ currentPage, setPage }) => { // Wrap all the pagination Indicators // with AnimateSharedPresence // so we can detect when Indicators // with a layoutId are removed/added return ( <AnimateSharedLayout> <div className="Indicators"> {pages.map((page) => ( <Indicator key={page} onClick={() => setPage(page)} isSelected={page === currentPage} /> ))} </div> </AnimateSharedLayout> );};const Indicator = ({ isSelected, onClick }) => { return ( <div className="Indicator-container" onClick={onClick}> <div className="Indicator"> {isSelected && ( // By setting layoutId, when this component // is removed and a new one is added elsewhere, // the new component will animate out from the old one. <motion.div className="Indicator-highlight" layoutId="highlight" /> )} </div> </div> );};const pages = [0, 1, 2, 3, 4];const App = () => { /* We keep track of the pagination direction as well as * current page, this way we can dynamically generate different * animations depending on the direction of travel */ const [[currentPage, direction], setCurrentPage] = useState([0, 0]); function setPage(newPage, newDirection) { if (!newDirection) newDirection = newPage - currentPage; setCurrentPage([newPage, newDirection]); } return ( <> <PageSlider currentPage={currentPage} direction={direction} setPage={setPage} /> <Pagination currentPage={currentPage} setPage={setPage} /> </> );};export default App; |
Filename: PageSlider.js
Javascript
import React from "react";import { useRef } from "react";import { motion, AnimatePresence } from "framer-motion";import { wrap } from "@popmotion/popcorn";// Variants in framer-motion define visual states// that a rendered motion component can be in at// any given time.const xOffset = 100;const variants = { enter: (direction) => ({ x: direction > 0 ? xOffset : -xOffset, opacity: 0 }), active: { x: 0, opacity: 1, transition: { delay: 0.2 } }, exit: (direction) => ({ x: direction > 0 ? -xOffset : xOffset, opacity: 0 })};const pages = [0, 1, 2, 3, 4];const PageSlider = ({ currentPage, setPage, direction }) => { /* Add and remove pages from the array to checkout how the gestures and pagination animations are fully data and layout-driven. */ const hasPaginated = useRef(false); function detectPaginationGesture(e, { offset }) { if (hasPaginated.current) return; let newPage = currentPage; const threshold = xOffset / 2; if (offset.x < -threshold) { // If user is dragging left, go forward a page newPage = currentPage + 1; } else if (offset.x > threshold) { // Else if the user is dragging right, // go backwards a page newPage = currentPage - 1; } if (newPage !== currentPage) { hasPaginated.current = true; // Wrap the page index to within the // permitted page range newPage = wrap(0, pages.length, newPage); setPage(newPage, offset.x < 0 ? 1 : -1); } } return ( <div className="slider-container"> <AnimatePresence // This will be used for components to resolve // exit variants. It's necessary as removed // components won't rerender with // the latest state (as they've been removed) custom={direction}> <motion.div key={currentPage} className="slide" data-page={currentPage} variants={variants} initial="enter" animate="active" exit="exit" drag="x" onDrag={detectPaginationGesture} onDragStart={() => (hasPaginated.current = false)} onDragEnd={() => (hasPaginated.current = true)} // Snap the component back to the center // if it hasn't paginated dragConstraints={{ left: 0, right: 0, top: 0, bottom: 0 }} // This will be used for components to resolve all // other variants, in this case initial and animate. custom={direction} /> </AnimatePresence> </div> );};export default PageSlider; |
Filename: App.css
CSS
body { display: flex; justify-content: center; align-items: center; min-height: 100vh; overflow: hidden; background: #09a960;}* { box-sizing: border-box;}.App { font-family: sans-serif; text-align: center;}.slider-container { position: relative; width: 600px; height: 600px;}.slide { border-radius: 5px; background: white; position: absolute; top: 0; left: 0; bottom: 0; right: 0;}/* position of indicator container */.Indicators { display: flex; justify-content: center; margin-top: 30px;}.Indicator-container { padding: 20px; cursor: pointer;}.Indicator { width: 10px; height: 10px; background: #fcfcfc; border-radius: 50%; position: relative;}.Indicator-highlight { top: -2px; left: -2px; background: #09f; border-radius: 50%; width: 14px; height: 14px; position: absolute;} |
Step to Run Application: Run the application using the following command from the root directory of the project:
$ npm start
Output: Now open your browser and go to http://localhost:3000/, you will see the following output:



