Animations
FLIP-animates transform on reflow instead of teleporting elements, and fades newly-mounted/removed items in/out — both entirely via inline styles, so this works with zero required CSS.
Options
MasonryOptions.animate / transitionDuration / transitionEasing
animate
boolean · default: true
Turns the animation described below on or off entirely. When false, every position change applies instantly, with no transition and no fade.
transitionDuration
number (ms) · default: 250
Ignored when animate is off.
transitionEasing
string · default: 'cubic-bezier(0.2, 0, 0, 1)'
Any valid CSS transition-timing-function value. Ignored when animate is off.
Example:
const engine = createMasonryEngine(container, {
animate: true, // the default — explicit here for clarity
transitionDuration: 400, // slower than the 250ms default
transitionEasing: 'ease-out', // a plain easing keyword instead of the default cubic-bezier
})The Animation Algorithm
- First placement — an item's very first position is applied instantly (no transition), together with
opacity: 0and themk-item-enterclass, then a moment later (after one shared forced reflow, batched across every item entering in the same pass)opacitytransitions to1. Without this two-step commit, a freshly mounted item would visibly slide in from the coordinate origin instead of just fading into its real spot. - Every placement after that is FLIP-animated: the element already sits at its old
transform, the engine sets atransitionand the newtransformtogether, and the browser animates the difference — items visibly slide into their new slot instead of jumping. - Removal fades
opacityto0on the element the item currently resolves to (mk-item-leaveclass added), but doesn't delay the element's actual removal from the DOM itself — core doesn't own that lifecycle, a framework adapter does.<MasonryGrid>(both the Vue component and the React one) keeps a removed item's wrapper mounted fortransitionDurationafter it disappears fromitems, specifically so this fade gets to play out instead of being cut short. - No scale on entry/exit, deliberately. An accompanying
scale()was tried and dropped — it changes an item's painted size in a way the gap/position math doesn't account for, which reads as the layout itself getting the spacing wrong even though the underlying positions never moved. Opacity alone avoids that. - A no-op transform is skipped entirely — if an item's resolved position hasn't actually changed since the last relayout, nothing is touched, so a static item's
transitionendlistener/will-changechurn stays at zero. will-change: transformis set for the duration of a moving item's transition and cleared (will-change: auto) once itstransitionendfires — never held open indefinitely, avoiding the classicwill-changememory/compositing-layer gotcha.
CSS hooks
Three classes are toggled on an item's own element for styling on top of the built-in transform/opacity animation:
| Class | When |
|---|---|
.mk-item-enter | Applied on an item's first placement, alongside its fade-in. |
.mk-item-moving | Applied while an item is FLIP-transitioning to a new position. |
.mk-item-leave | Applied when an item starts fading out after removal. |
When animate is on, the container also gets two CSS custom properties set on it, mirroring the resolved duration/easing — for a consumer's own CSS (e.g. a hover effect) to reference the same timing without hardcoding it separately. Core's own animation logic uses the resolved JS values directly, not these variables — they're a convenience for your styles, not something core reads back:
.mk-item {
/* your own transition, synced to masonry-kit's own timing */
transition: box-shadow var(--mk-transition-duration) var(--mk-transition-easing);
}