framework
framework/gsap/flip-layout.md
medium
gsap-flip-layout
Flip 插件 — 列表重排 / 模式切换的稳态动画。Use when 写 TS 业务代码 / 评审涉及 `flip-layout` 的 PR。
flipgsap.fliplayout animation列表重排模式切换式切换的
paths
frontend/src/animations/**/*.tsfrontend/src/**/*.tsx
GSAP · Flip 插件
何时用
Flip 适用于"元素从位置 A 平滑过渡到位置 B"且 A → B 之间 DOM 结构变化的场景:
- 列表重排(如拖拽换位)
- 模式切换(如卡片放大到模态视图)
- Tab 切换时下划线滑动
- 网格 → 列表布局切换
安装注册
// src/main.tsx
import gsap from "gsap";
import { Flip } from "gsap/Flip";
gsap.registerPlugin(Flip);
标准用法
import gsap from "gsap";
import { Flip } from "gsap/Flip";
import { D, E } from "@/animations/tokens";
function reorderCards(scope: HTMLElement, newOrder: string[]) {
const cards = scope.querySelectorAll(".card");
// 1. 记录初始状态
const state = Flip.getState(cards);
// 2. 改变 DOM(如重排 / className 切换)
applyNewOrder(newOrder);
// 3. Flip 自动从旧状态过渡到新状态
Flip.from(state, {
duration: D.slow,
ease: E.inOut,
stagger: 0.02,
absolute: true, // 移动期间用 position: absolute 让元素脱离布局
});
}
React + dnd-kit 集成
import { DndContext, DragEndEvent } from "@dnd-kit/core";
import { SortableContext, arrayMove } from "@dnd-kit/sortable";
import { Flip } from "gsap/Flip";
function SortableSlides() {
const [slides, setSlides] = useState(initialSlides);
const containerRef = useRef<HTMLDivElement>(null);
const handleDragEnd = (e: DragEndEvent) => {
if (!e.over || e.active.id === e.over.id) return;
const oldIdx = slides.findIndex(s => s.id === e.active.id);
const newIdx = slides.findIndex(s => s.id === e.over!.id);
const state = Flip.getState(containerRef.current!.querySelectorAll(".slide"));
setSlides(arrayMove(slides, oldIdx, newIdx));
requestAnimationFrame(() => {
Flip.from(state, { duration: D.base, ease: E.out, stagger: 0.02, absolute: true });
});
};
return (
<DndContext onDragEnd={handleDragEnd}>
<SortableContext items={slides}>
<div ref={containerRef}>
{slides.map(s => <SortableSlide key={s.id} {...s} />)}
</div>
</SortableContext>
</DndContext>
);
}
用例
| 场景 |
|---|
| 拖拽换章节顺序 |
| 拖拽换幻灯片 |
| 控制台过滤切换布局 |
| 大纲转编辑器(hero 转场) |
自检
- [ ] 注册了 Flip 插件?
- [ ] 状态变化前
Flip.getState? - [ ] 状态变化后下一帧
Flip.from? - [ ] 用
absolute: true避免影响周围元素布局?