<AnimatePresence>
是使用在元件 unmount 時的離場動畫,在 React 的生命週期裡 unmount 就直接從畫面上消失了,如果要做離場動畫必須東補西補,利用布林值判斷動畫結束與消失的時機,motion API 提供了 <AnimatePresence>
允許元件消失之前做動畫。
本節資源:
程式碼
React 缺少了在 unmount 之前能夠做完動畫的生命週期,所以當元件 unmount 就直接消失於畫面中,非常之突然。為了解決這個問題,可以設兩道閘門,一個是確認動畫結束,一個是真正 unmount,把元件從畫面消失。
// CSS
.outsider{
width: 100px;
height: 100px;
background: #fa0;
line-height: 100px;
text-align: center;
cursor: pointer;
}
// 當動畫啟動追加上去
.outsider[data-hidding="true"]{
opacity: 0;
transition: all 1s ease-out;
}
// JS
import React, { useState } from "react";
import "./style.css";
export default function ComponentFadeOut() {
const [hidding, setHidding] = useState(false); // 動畫確認開始
const [hidden, setHidden] = useState(false); // 動畫結束做 unmount
return (
<>
{!hidden && (
<div
className="outsider"
{/* 動畫的啟動 */}
data-hidding={hidding}
onClick={() => {
setHidding(true);
// 隔 1.5 秒消失
setTimeout(() => {
setHidden(true);
}, 1500);
}}
>
邊緣人
</div>
)}
</>
);
}
在 motion API 可以使用 <AnimatePresence>
解決這個問題,讓真正 unmount 的時間延遲 (defer),讓畫面做完動畫。
import { AnimatePresence, motion } from "framer-motion";
import React, { useState } from "react";
import "./style.css";
export default function AnimatePresenceComponent() {
// 透過 hidden 判斷要不要消失就好
const [hidden, setHidden] = useState(false);
return (
<>
<h3>AnimatePresence</h3>
{/* 加入 AnimatePresence */}
<AnimatePresence>
{!hidden && (
<motion.div
className="outsider"
onClick={(e) => {
setHidden(true);
}}
transition={{
duration: 1.5
}}
{/* exit 離場動畫編排 */}
exit={{
opacity: 0,
}}
>
邊緣人 2 號
</motion.div>
)}
</AnimatePresence>
</>
);
}
AnimatePresence 主要目的有兩個 :
當 motion 元件要消失在渲染樹中要搭配 exit
props,會在 DOM 將節點移除之前觸發動畫 :
import { motion, AnimatePresence } from "framer-motion"
export const MyComponent = ({ isVisible }) => (
// 將會移除 render tree 的 motion 元件用 AnimatePresence 包裹起來
<AnimatePresence>
{isVisible && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }} // 添加 exit props
/>
)}
</AnimatePresence>
)
<AnimatePresence>
會偵測子元件會從渲染樹移除,不過要注意的是子元素最外層 必須為加上唯一的 key,讓 React 知道哪一個要操作,跟一般陣列渲染一樣,如果有 刪除/新增/交換順序 一定要加上唯一的 key,而且避免使用 index 發生非預期的事情 。
另外 exit
也跟其他基本的 props (initial 、 animate) 一樣,可以透過 variants 使用字串標籤,也可以個別設定 transition 數值。
motion 元件在 mount 之後會執行 initial
與 animate
的動畫,如果 initial = false
其初始狀態就是從 animate 開始。 <AnimatePresence>
的操作是把 key 換掉,也就是又重新出現的 motion 元件依然會從 initial
開始,如果只想要在第一次 render 取消 initial
怎麼辦 ?
在
AnimatePresence
上加入initial=false
<AnimatePresence initial={false}>
...
</AnimatePresence >
簡單來說,在第一次跳過 initial
動畫。
W3shcool 範例 : How To Create a Slideshow
完成效果 :
主要離場動畫的部分
// 動畫 variants 的部份
const imageVariants = {
enter: (dir) => ({
x: 500 * -dir, // 如果是往右 (+),下一張進入時要反方向
opacity: 0,
}),
center: {
/* 由於每一張圖片都是使用 position 都疊在一起,
要改變 zindex 避免如果有點擊互動點不到元素 */
zIndex: 1,
opacity: 1,
x: 0,
transition: {
// 切開 initial 跟 animate 的間隔,下一篇會提到用 "mode" 來改善
delay: 0.5,
},
},
exit: (dir) => {
return {
zIndex: 0,
x: 500 * dir,
opacity: 0,
};
},
};
// JS
const [[dir, picNumber], setPic] = useState([0, 0]); // [方向,第幾張]
// 取消第一次子元素的 initial 與傳入 custom 使子元件即使消失能抓到值
<AnimatePresence custom={dir} initial={false}>
// 記得要有 key !
<div className="imageBox" key={picNumber}>
<motion.img
variants={imageVariants}
initial={"enter"}
animate={"center"}
exit={"exit"}
custom={dir}
src={carouselPic[picNumber]}
alt={picNumber}
/>
</div>
</AnimatePresence>
底下的小球列表是用到前面文章提到的 layoutId
與 LayoutGroup
,主要拿來共享元件與區域分組用。
<LayoutGroup>
<div className="dot-group">
{carouselPic.map((_, index) => (
<motion.span
className="dot"
key={index}
onClick={() => {
if (picNumber === index) return;
// 判斷下一個數字與方向
setPic([picNumber > index ? -1 : 1, index]);
}}
{picNumber === index && (
<motion.span
layout
layoutId="dot-indicator"
className="dot-indicator"
variants={indicatorVariants}
animate="trigger"
/>
)}
</motion.span>
))}
</div>
</LayoutGroup>
為 <AnimatePresence>
加上 initial = false
是因為不用在一進入頁面就看到活動的效果,在上面 Gif 可以看到初始化後一開始就呈現 animate
動畫了,如果沒有關掉就會從 initial 的 opacity : 0
(因為 dir 初始是 0 不影響) 開始。
另外要注意的是,在 <AnimatePresence>
如果沒有補上 custom
會抓不到值,因為當 motion 元件消失其伴隨的 custom 也會消失,避免在 exit 執行時抓不到動態值,可以在 <AnimatePresence>
補上去。
目前已知 Bug ,只要互動超級無敵快,它就會 Bang 不見,在這個例子中按太快那一張圖片就不會顯示,但是再點一下就會回來了,所以不要太手癢 XD。
可以看到它的 initial 到 animate 卡住了,只剩 exit。
AnimatePresence
: 處理 motion 元件離場動畫,直到完整跑完才執行 unmount。exit
: 設定離場的動畫序列,跟其他的 initial 與 animate 一樣,也可以利用 variants 字串標籤,或者細節設定 transition。AnimatePresence
的概念還有一些,是有關 Custom Component 使用上的問題。明天也會加上 react-router 實作 Page Transition 。關於範例的部分我假日會努力弄完的Q