指南 · 05
進場、離場與版面 保留離場的子元素直到動態結束,並為位置與尺寸的變化加上動畫,甚至跨越元件。
Animated.Keep Animated.Keep 會保留有 key 的直接子元素,直到它們的 depart 姿態結束。seed={false} 讓首次渲染時已存在的子元素直接從其 pose 開始,wait 讓進場的子元素等到離場結束,onDeparted 則在最後一個離場後觸發。
JavaScript 已關閉。下方的程式碼就是完整的範例。
App.jsx import { useState } from 'react' ;
import { Animated } from '@kkwaenggwari/animated' ;
import './styles.css' ;
const departures = {
up : { opacity : 0 , transform : [{ translateY : -24 }] },
side : { opacity : 0 , transform : [{ translateX : 120 }] },
shrink : { opacity : 0 , transform : [{ scale : 0.6 }] },
};
let next = 4 ;
export default function App ({ depart = 'side' , wait = false } ) {
const [items, setItems] = useState ([1 , 2 , 3 ]);
const add = ( ) => setItems ((list ) => [...list, next++]);
const remove = (id ) => setItems ((list ) => list.filter ((item ) => item !== id));
return (
<Animated.Scene tempo ={{ spring: { stiffness: 320 , damping: 30 } }}>
<div className ="stage" >
<button type ="button" className ="add" onClick ={add} >
Add message
</button >
<Animated.Keep wait ={wait} >
{items.map((id) => (
<Animated.div
key ={id}
move
className ="message"
seed ={{ opacity: 0 , transform: [{ translateY: 16 }] }}
pose ={{ opacity: 1 , transform: [{ translateY: 0 }] }}
depart ={departures[depart]}
tempo ={{ spring: { stiffness: 320 , damping: 26 } }}
>
<span > Message #{id}</span >
<button type ="button" onClick ={() => remove(id)}>
Dismiss
</button >
</Animated.div >
))}
</Animated.Keep >
</div >
</Animated.Scene >
);
}styles.css .stage {
display : grid;
align-content : start;
gap : 10px ;
min-height : 320px ;
max-width : 420px ;
padding : 28px ;
}
button {
font : inherit;
cursor : pointer;
}
.add {
justify-self : start;
min-height : 44px ;
margin-bottom : 8px ;
padding : 0 20px ;
border : 0 ;
border-radius : 999px ;
background : #2854ff ;
color : #fff ;
font-weight : 650 ;
}
.message {
display : flex;
align-items : center;
justify-content : space-between;
gap : 12px ;
padding : 10px 10px 10px 18px ;
border : 1px solid #e4e6ef ;
border-radius : 14px ;
background : #fff ;
}
.message button {
min-height : 36px ;
padding : 0 14px ;
border : 1px solid #e4e6ef ;
border-radius : 999px ;
background : #f5f6fb ;
color : #16181d ;
}Animated.Scene 與 move Animated.Scene 會測量已提交的版面。具有 move 的元素會為其位置與尺寸的變化加上動畫;move="pill" 這樣的字串會在 Scene 中連結具有相同識別的元素,即使一個被卸載、另一個被掛載。
這些元素版面上的 translate 與 scale 由 Scene 掌管,所以請把你自己的 transform 放在巢狀元素上。
JavaScript 已關閉。下方的程式碼就是完整的範例。
App.jsx import { useState } from 'react' ;
import { Animated } from '@kkwaenggwari/animated' ;
import './styles.css' ;
const tabs = ['All' , 'Motion' , 'Layout' , 'Input' ];
const colors = ['#2854ff' , '#8fb0ff' , '#5c7cff' , '#16181d' , '#4a6bff' , '#7d9bff' ];
export default function App ({ columns = 3 , stiffness = 300 , damping = 28 } ) {
const [tab, setTab] = useState ('All' );
const [tiles, setTiles] = useState (colors);
const shuffle = ( ) => setTiles ((list ) => [...list.slice (1 ), list[0 ]]);
return (
<Animated.Scene tempo ={{ spring: { stiffness , damping } }}>
<div className ="stage" >
<nav className ="tabs" >
{tabs.map((name) => (
<button key ={name} type ="button" onClick ={() => setTab(name)}>
{name === tab && <Animated.span className ="pill" move ="pill" /> }
<span className ="label" > {name}</span >
</button >
))}
</nav >
<div className ="tiles" style ={{ gridTemplateColumns: `repeat (${columns }, 1fr )` }}>
{tiles.map((color) => (
<Animated.div key ={color} move className ="tile" style ={{ background: color }} />
))}
</div >
<button type ="button" className ="shuffle" onClick ={shuffle} >
Rotate tiles
</button >
</div >
</Animated.Scene >
);
}styles.css .stage {
display : grid;
gap : 18px ;
padding : 28px ;
}
button {
font : inherit;
cursor : pointer;
}
.tabs {
display : flex;
flex-wrap : wrap;
gap : 4px ;
justify-self : start;
padding : 4px ;
border-radius : 999px ;
background : #f5f6fb ;
}
.tabs button {
position : relative;
min-height : 40px ;
padding : 0 18px ;
border : 0 ;
background : none;
color : #16181d ;
}
.pill {
position : absolute;
inset : 0 ;
border-radius : 999px ;
background : #2854ff ;
}
.label {
position : relative;
}
.tabs button :has (.pill ) .label {
color : #fff ;
}
.tiles {
display : grid;
gap : 10px ;
max-width : 420px ;
}
.tile {
height : 64px ;
border-radius : 14px ;
}
.shuffle {
justify-self : start;
min-height : 44px ;
padding : 0 20px ;
border : 1px solid #e4e6ef ;
border-radius : 999px ;
background : #fff ;
}用 Hook 控制出現與消失 如果你偏好自己控制掛載,useAnimatedPresence(visible, { enter, exit }) 會回傳 { mounted, animatedValue }。
Toast.jsx 複製程式碼 const { mounted, animatedValue } = useAnimatedPresence (open, {
enter : (value ) => spring (value, { toValue : 1 }),
exit : (value ) => timing (value, { toValue : 0 , duration : 180 }),
});
return mounted ? <div style ={{ opacity: animatedValue }}> Saved</div > : null ;在 React 之外 createLayoutScene 為任何渲染器或手動的 DOM 修改提供相同的擷取/提交協調。
outside-react.js 複製程式碼 const scene = createLayoutScene ({ tempo : { spring : { stiffness : 320 , damping : 30 } } });
const card = scene.attach (element, { key : 'card' });
scene.capture ();
list.prepend (element);
scene.commit ();