我不知道如何<AnimateSharedLayout />
通过 framer-motion 使用组件为路由设置动画。基本上在这里的代码中,我想显示一个图像列表,当点击它们时,我想导航到/images/[image_id]
并显示它。我在本地没有图像,所以我必须获取它们。这里有一些问题:
- 第一次在索引页面上获取相同的元素,然后在详细信息页面上重新获取(如果有办法将它带到详细信息页面会很好)
- 更改路线前,页面自动滚动到顶部,破坏动画;
- 如果用户在慢速网络上,动画会滞后甚至无法启动;
- 返回上一条路线不会重播动画(我可能必须缓存 api 调用以避免重新获取它们?)
这是代码_app.tsx
import '../styles/globals.css';
import 'bulma/css/bulma.css';
import { AppProps } from "next/app";
import { AnimateSharedLayout, AnimatePresence } from 'framer-motion';
const MyApp: React.FC<AppProps> = ({ Component, pageProps, router }) => {
return (
<AnimateSharedLayout>
<AnimatePresence>
<Component {...pageProps} key={router.route} />
</AnimatePresence>
</AnimateSharedLayout>
);
}
export default MyApp;
这里是pages/index.tsx
import { motion } from "framer-motion";
import { useEffect, useState } from "react";
import Picture, { PictureProps } from "./components/Picture";
export const base = 'https://jsonplaceholder.typicode.com';
const Home: React.FC = () => {
const [pics, setPics] = useState<PictureProps[]>([]);
useEffect(() => {
fetch(`${base}/photos?_start=0&_limit=15`)
.then(res => res.json())
.then(res => setPics(res as PictureProps[]))
.catch(err => console.log("fetching error", err));
}, []);
return (
<div className="container pt-4">
<div className="columns is-multiline">
{
pics.map(pic => (
<div key={pic.id} className="column is-one-third-desktop is-half-tablet is-full-mobile">
<Picture {...pic} />
</div>
))
}
</div>
</div>
);
}
export default Home;
这里是pages/images/[image].tsx
import { motion } from "framer-motion";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { PictureProps } from "../components/Picture";
import { base } from '../index';
const Image: React.FC = () => {
const router = useRouter();
const { image } = router.query;
const [pic, setPic] = useState<PictureProps>({} as PictureProps);
useEffect(() => {
image && fetch(`${base}/photos/${image}`)
.then(res => res.json())
.then(res => setPic(res))
.catch(err => console.log(err));
}, [image]);
return (
<motion.div>
{
image &&
<motion.figure layoutId={`img-${image}`} className='image'>
<img src={pic.url} alt={pic.title} />
</motion.figure>
}
</motion.div>
);
}
export default Image;
这是components/Picture.tsx
import { motion, Variants } from "framer-motion";
import Link from "next/link";
export type PictureProps = {
albumId: number,
id: number,
title: string,
url: string,
thumbnailUrl: string,
};
const cardVariants: Variants = {
unloaded: {
y: -100,
opacity: 0,
transition: {
when: "afterChildren",
},
},
loaded: {
y: 0,
opacity: 1,
transition: {
when: "beforeChildren",
},
}
};
const textVariants: Variants = {
unloaded: {
scale: .7,
opacity: 0,
},
loaded: {
scale: 1,
opacity: 1,
},
};
const Picture: React.FC<PictureProps> = ({
id, title, url,
albumId, thumbnailUrl
}) => {
console.log(`img-${id}`);
return (
<motion.div className='card' variants={cardVariants} initial="unloaded" animate="loaded" exit="exit">
<div className="card-image">
<motion.figure
className="image"
layoutId={`img-${id}`}
>
<img src={url} alt={title} />
</motion.figure>
</div>
<motion.div className="card-content">
<div className="media">
<div className="media-left">
<Link href={`images/${id}`}>
<a>
<figure className="image is-50by50">
<img src={thumbnailUrl} alt="thumbnail" />
</figure>
</a>
</Link>
</div>
<motion.div variants={textVariants} className="media-content">
<p className="title is-6">{title}</p>
</motion.div>
</div>
<motion.div variants={textVariants} className='content'>
Some random text Some random text
Some random text Some random text
<br />
<strong>Album id</strong>: <em>{albumId}</em>
</motion.div>
</motion.div>
</motion.div>
);
}
export default Picture;