数值 · 06
错峰分布
Stagger distribution · stagger按索引或空间距离分配起始时间,让一组元素形成可读的节奏。
一组元素同时启动会像整片闪烁,固定顺序又未必适合网格。错峰函数先给每个元素一个距离,再把距离换成延迟。起点、网格、轴向和缓动会改变波纹从哪里来、如何扩散。
公式
dᵢ = distance(cellᵢ, origin) delayᵢ = ease(dᵢ/maxD) · amount or delayᵢ = ease(dᵢ/maxD) · maxD · each
参数
| 本页默认 | 成熟实现 | |
|---|---|---|
| each / amount | 0.08 s / 0.9 s | GSAP each or amounteach 是邻距,amount 是整组跨度;指定 amount 时它优先。 |
| from / grid / axis | start / 7×5 / both | GSAP center, edges, end, random, index; grid, axis同一组点换起点,观看顺序就会改变。 |
怎样做好
- 二维排布按空间距离错峰,别把每一行都当成同一条长列表。GSAP src/gsap-core.js:452–500
- 总时长要可控时用
amount;要保持相邻节奏时用each。GSAP src/gsap-core.js:488–500 - 给群组一个有意义的起点。
cardwall从中心向外错峰,配合卡片散开后的归位。www-motion/rewrite/cardwall/app.js:22
在我们的案例里
55 个案例使用错峰。cardwall 的卡片用 from:'center'、each:.028 向外归位,同一时间线又给标题字母单独设了错峰;延迟分布也在组织视觉层级。
出现在 55 个案例
01Momentum Slider02Drag Gallery03Formation04Cardwall05Riffle06Revolver07Rotunda09Lens Bloom10Frieze11Aperture12Folio15Hardback16Picasso Ecommerce17Assembly18Fathom19Emulsion20Portfolio Photographer21Reverie
+37 更多
22Boutique23Nova24Alive25Shape Trail26Membrane27Liquid Type29Pixels30Cipher32Cutout33Crema34Aster35Spring37Amour38Fluid Gallery39Marathon43Pantheon48Moments49Burst50Dimension51Morph52Meow53Relay54Lumen55Serene56Fleur57Atrium59Helix60Crate61Prism62Flashlight63Viewfinder64Sand65Arrange66Tunnel67Reel68Type69Loupe
参考实现 · basics.js
/**
* Delay for element i of n, GSAP/Motion style.
* each: seconds between neighbours · amount: total spread (wins over each)
* from: 'start' | 'center' | 'end' | 'edges' | 'random' | index · grid: [cols, rows] · ease: shapes the wave front
*/
export function stagger({ each = 0.05, amount, from = 'start', grid, axis, ease, seed = 1 } = {}) {
const cache = new Map();
return (i, n) => {
if (!cache.has(n)) {
const [cols, rows] = grid || [n, 1];
const origin = typeof from === 'number'
? [from % cols, Math.floor(from / cols)]
: from === 'end' ? [cols - 1, rows - 1]
: from === 'center' || from === 'edges' ? [(cols - 1) / 2, (rows - 1) / 2]
: [0, 0];
let d = Array.from({ length: n }, (_, k) => {
const dx = (k % cols) - origin[0], dy = Math.floor(k / cols) - origin[1];
return axis === 'x' ? Math.abs(dx) : axis === 'y' ? Math.abs(dy) : Math.hypot(dx, dy);
});
const max = Math.max(...d) || 1;
if (from === 'edges') d = d.map(v => max - v);
if (from === 'random') { const r = mulberry32(seed); d = d.map(() => r() * max); }
const unit = amount != null ? amount / max : each;
cache.set(n, d.map(v => (ease ? ease(v / max) * max : v) * unit));
}
return cache.get(n)[i];
};
}