Skip to example

Grid stagger

An example of staggering animations across a grid, outwards from the centre or a clicked cell, with stagger() in Motion.

JavaScript

Source code

<div class="stagger-stage">
    <div class="example">
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    <button aria-label="Ripple from here"></button>
    </div>
</div>

<script type="module">
    import { animate, stagger } from "motion"

    const grid = document.querySelector(".example")
    const cells = grid.querySelectorAll("button")

    // Read the number of columns and rows from the CSS grid
    const style = getComputedStyle(grid)
    const columns = style.gridTemplateColumns.split(" ").length
    const rows = style.gridTemplateRows.split(" ").length

    // Fade in from the centre of the grid
    animate(
        cells,
        { opacity: 1, scale: [0.5, 1] },
        { delay: stagger(0.05, { grid: [columns, rows], from: "center" }) }
    )

    // Ripple outwards from the clicked cell
    cells.forEach((cell, i) => {
        cell.addEventListener("click", () => {
            animate(
                cells,
                { scale: [1, 0.4, 1] },
                {
                    duration: 0.6,
                    delay: stagger(0.05, { grid: [columns, rows], from: i }),
                }
            )

            // Mark the origin cell
            grid.querySelector(".stagger-origin")?.classList.remove("stagger-origin")
            cell.classList.add("stagger-origin")
        })
    })
</script>

<style>
    #example-container {
        display: flex;
        flex: 1;
        align-self: stretch;
    }

    .stagger-stage {
        --ink: oklch(0.145 0.01 148);
        --paper: oklch(0.94 0.008 96);
        --plate: var(--paper);
        --origin: oklch(0.88 0.18 96);

        position: relative;
        display: flex;
        flex: 1;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        box-sizing: border-box;
        padding: 32px 16px;
        background-color: var(--ink);
        color: var(--paper);
    }

    .example {
        display: grid;
        grid-template-columns: repeat(8, clamp(22px, 6vw, 40px));
        grid-template-rows: repeat(6, clamp(22px, 6vw, 40px));
        gap: clamp(4px, 1.4vw, 10px);
    }

    .example button {
        box-sizing: border-box;
        width: 100%;
        height: 100%;
        padding: 0;
        border: none;
        background-color: var(--plate);
        cursor: pointer;
        opacity: 0;
    }

    .example button:hover {
        background-color: var(--origin);
    }

    .example button:focus-visible {
        outline: 1px solid var(--paper);
        outline-offset: 2px;
    }

    .example button.stagger-origin {
        background-color: var(--origin);
    }
</style>