使用 framer-motion 和 React.js 的动画滑动页面画廊
以下方法介绍了如何使用 framer-motion 和 ReactJS 创建动画滑动页面库。
先决条件:
- JavaScript(ES6)知识
- 熟悉 HTML 和 CSS。
- ReactJS 的基础知识。
创建 React 应用程序并安装模块:
第 1 步:使用以下命令创建一个 React 应用程序:
$ npx create-react-app page-gallery
第 2 步:创建项目文件夹后,即page-gallery ,使用以下命令移动到该文件夹。
$ cd page-gallery
第 3 步:添加项目期间需要的 npm 包。
$ npm install framer-motion @popmotion/popcorn
打开 src 文件夹并删除以下文件:
- 徽标.svg
- serviceWorker.js
- setupTests.js
- App.test.js(如果有)
- 索引.css。
创建一个名为PageSlider.js的文件。
项目结构:项目结构树应如下所示:
文件名: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 (
{pages.map((page) => (
setPage(page)}
isSelected={page === currentPage}
/>
))}
);
};
const Indicator = ({ isSelected, onClick }) => {
return (
{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.
)}
);
};
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 (
<>
>
);
};
export default App;
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 (
(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}
/>
);
};
export default PageSlider;
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;
}
文件名: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 (
(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}
/>
);
};
export default PageSlider;
文件名: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;
}
运行应用程序的步骤:从项目的根目录使用以下命令运行应用程序:
$ npm start
输出:现在打开浏览器并转到http://localhost:3000/ ,您将看到以下输出: