Framer Motion 可以非常轻松地创建出美观的动画,但如果你想为不同的屏幕尺寸设置不同的动画效果该怎么办?使用 CSS 动画,你可以直接使用媒体查询,但你知道吗,通过利用 Window.matchMedia API 或使用 CSS 变量,我们可以为 Framer Motion 和 React Spring 等 JavaScript 动画库编写响应式动画?
在 JavaScript 中使用媒体查询
在 React 中使用自定义 hook 和 window.matchMedia API 是在 Framer Motion 中制作响应式动画的最简单方法。许多 UI 框架(例如 Material UI 和 Chakra UI)已经公开了这样的 hook,但如果你想自己编写一个,可以参考以下内容:
export function useMediaQuery(query) {
const [matches, setMatches] = useState(false);
useEffect(() => {
const media = window.matchMedia(query);
if (media.matches !== matches) {
setMatches(media.matches);
}
const listener = () => {
setMatches(media.matches);
};
if (typeof media.addEventListener === "function") {
media.addEventListener("change", listener);
} else {
media.addListener(listener);
}
return () => {
if (typeof media.removeEventListener === "function") {
media.removeEventListener("change", listener);
} else {
media.removeListener(listenerList);
}
};
}, [matches, query]);
return matches;
}
并非所有媒体查询 hook 都能与服务器端渲染配合使用,而只在客户端更新值。如果你觉得这一点很重要,请考虑使用下面描述的 CSS 变量选项。
它获取一个媒体查询字符串(就像你在 css 中编写的那样),如果查询与当前屏幕匹配,则返回 true
。如果屏幕大小调整,则该值将更新。在代码中像这样使用它
const isSmall = useMediaQuery("(min-width: 480px)");
一个好主意是设置自定义钩子,使其与您已经在 css 中使用的媒体查询相匹配,这样您就不必记住像素值(是 479 还是 480?):
export const useIsSmall = () => useMediaQuery('(min-width: 480px)');
export const useIsMedium = () => useMediaQuery('(min-width: 768px)');
/* etc.. */
通过Variants实现响应式动画
现在我们已经设置好了钩子,让我们通过使用变体来根据媒体查询有条件地进行更改,从而将所有内容组合在一起。
import { motion } from 'framer-motion'
import { useIsSmall } from 'src/hooks/utils'
const Component = () => {
const isSmall = useIsSmall() /* or useMediaQuery('(min-width: 480px)'); */
const variants = isSmall
? {
animate: {
opacity: 1,
scale: 1,
y: 0,
},
exit: {
opacity: 1,
scale: 1,
y: 500,
},
}
: {
animate: {
opacity: 1,
scale: 1,
y: 0,
},
exit: {
opacity: 0,
scale: 0.9,
y: -10,
},
};
return (
<motion.div initial="exit" animate="animate" exit="exit">Animated</motion.div>
);
}
你也可以内联使用变量<motion.div animate={isSmall ? { y: 500} : { y: 1000}} />
,但我发现大多数情况下,将其与变体一起使用是最简洁的方法。
使用 CSS Variants 实现响应式 Framer Motion
使用 Framer Motion 制作响应式动画的另一种方法是使用 CSS 变量,正如 Sam Selikoff 在 这个精彩的视频 中使用 Tailwind CSS 所展示的那样。这比使用媒体查询钩子稍微复杂一些,但具有与服务器端渲染最佳配合的优势。
诀窍在于将 CSS 变量用作 varient 的值,然后使用媒体查询在断点处更新变量。以下是前面示例的重构:
const Component = () => {
const variants = {
animate: {
opacity: 'var("--opacity-animate")',
scale: 'var("--scale-animate")',
y: 'var("--y-animate")'
},
exit: {
opacity: 'var("--opacity-exit")',
scale: 'var("--scale-exit")',
y: 'var("--y-exit")',
},
}
return (
<motion.div
initial="exit"
animate="animate"
exit="exit"
className="component"
>
Animated
</motion.div>
);
}
然后在你的 CSS 中使用媒体查询覆盖变量。请注意,你只需要指定在断点处更改的值,其余的值将从初始值继承。
.component {
--opacity-animate: 1;
--scale-animate: 1;
--y-animate: "0px";
--opacity-exit: 0;
--scale-exit: 0.9;
--y-exit: "-10px";
@media (min-width: 480px) {
--opacity-exit: 1;
--scale-exit: 1;
--y-exit: "500px";
}
}