Skip to content

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:

ts
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: 0 and the mk-item-enter class, then a moment later (after one shared forced reflow, batched across every item entering in the same pass) opacity transitions to 1. 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 a transition and the new transform together, and the browser animates the difference — items visibly slide into their new slot instead of jumping.
  • Removal fades opacity to 0 on the element the item currently resolves to (mk-item-leave class 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 for transitionDuration after it disappears from items, 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 transitionend listener/will-change churn stays at zero.
  • will-change: transform is set for the duration of a moving item's transition and cleared (will-change: auto) once its transitionend fires — never held open indefinitely, avoiding the classic will-change memory/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:

ClassWhen
.mk-item-enterApplied on an item's first placement, alongside its fade-in.
.mk-item-movingApplied while an item is FLIP-transitioning to a new position.
.mk-item-leaveApplied 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:

css
.mk-item {
  /* your own transition, synced to masonry-kit's own timing */
  transition: box-shadow var(--mk-transition-duration) var(--mk-transition-easing);
}