Block Reveal Page Transition

A mosaic grid transition effect for multi-page websites. Built with GSAP and data attributes. Works with Webstudio, static sites, and any HTML page.

Code Reference

1. CDN Script

Add GSAP to your page. No plugins required.

<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>

Webstudio: Paste this in the Custom Code → Head Code section of your project settings, or in a page-level HTML embed.

2. Required CSS

.block-transition-grid {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 9999;
  overflow: hidden;
}

.block-transition-block {
  position: absolute;
  opacity: 0;
}

Webstudio: Add this CSS in a global style embed or in Project Settings → Custom Code → Head Code wrapped in <style> tags.

3. HTML Setup

Add the transition container element to every page. Configure the effect using data attributes:

<!-- Place this on every page (e.g. in a global embed or site-wide component) -->
<div
  data-block-transition
  data-block-size="60"
  data-block-color="#0f0f0f"
  data-stagger-amount="0.5"
  data-block-duration="0.05"
  data-enter-delay="0.3"
  data-stagger-from="random"
></div>

Then add data-transition-link to any link that should trigger the transition:

<!-- Any link with this attribute will trigger the block transition -->
<a href="/about" data-transition-link>About</a>
<a href="/work" data-transition-link>Our Work</a>
<a href="/contact" data-transition-link>Contact</a>

Webstudio: Select your link element, open the Settings panel, scroll to Custom Attributes, and add data-transition-link with an empty value. For the config div, use an HTML Embed component.

4. JavaScript

This script handles everything: grid creation, link interception, leave/enter animations, and cross-page coordination via sessionStorage.

document.addEventListener("DOMContentLoaded", () => {
  // ── Read config from data attributes ──
  const configEl = document.querySelector("[data-block-transition]");
  if (!configEl) return;

  const BLOCK_SIZE = parseInt(configEl.dataset.blockSize) || 60;
  const BLOCK_COLOR = configEl.dataset.blockColor || "#0f0f0f";
  const STAGGER_AMOUNT = parseFloat(configEl.dataset.staggerAmount) || 0.5;
  const BLOCK_DURATION = parseFloat(configEl.dataset.blockDuration) || 0.05;
  const ENTER_DELAY = parseFloat(configEl.dataset.enterDelay) || 0.3;
  const STAGGER_FROM = configEl.dataset.staggerFrom || "random";

  // ── Create the grid container ──
  const grid = document.createElement("div");
  grid.className = "block-transition-grid";
  document.body.appendChild(grid);

  let blocks = [];

  function createGrid() {
    grid.innerHTML = "";
    blocks = [];

    const cols = Math.ceil(window.innerWidth / BLOCK_SIZE);
    const rows = Math.ceil(window.innerHeight / BLOCK_SIZE) + 1;
    const offsetX = (window.innerWidth - cols * BLOCK_SIZE) / 2;
    const offsetY = (window.innerHeight - rows * BLOCK_SIZE) / 2;

    for (let row = 0; row < rows; row++) {
      for (let col = 0; col < cols; col++) {
        const block = document.createElement("div");
        block.className = "block-transition-block";
        block.style.cssText =
          "width:" + BLOCK_SIZE + "px;" +
          "height:" + BLOCK_SIZE + "px;" +
          "left:" + (col * BLOCK_SIZE + offsetX) + "px;" +
          "top:" + (row * BLOCK_SIZE + offsetY) + "px;" +
          "background-color:" + BLOCK_COLOR;
        grid.appendChild(block);
        blocks.push(block);
      }
    }
  }

  createGrid();
  window.addEventListener("resize", createGrid);

  // ── Enter animation (reveal new page) ──
  if (sessionStorage.getItem("block-transition-active")) {
    sessionStorage.removeItem("block-transition-active");

    gsap.set(blocks, { opacity: 1 });

    gsap.to(blocks, {
      opacity: 0,
      duration: BLOCK_DURATION,
      delay: ENTER_DELAY,
      ease: "power2.inOut",
      stagger: { amount: STAGGER_AMOUNT, from: STAGGER_FROM },
    });
  }

  // ── Leave animation (cover page, then navigate) ──
  document.querySelectorAll("[data-transition-link]").forEach((link) => {
    link.addEventListener("click", (e) => {
      e.preventDefault();
      const href = link.getAttribute("href");
      if (!href || href === "#") return;

      sessionStorage.setItem("block-transition-active", "true");

      gsap.to(blocks, {
        opacity: 1,
        duration: BLOCK_DURATION,
        ease: "power2.inOut",
        stagger: { amount: STAGGER_AMOUNT, from: STAGGER_FROM },
        onComplete: () => {
          window.location.href = href;
        },
      });
    });
  });
});

Webstudio: Paste this inside a <script> tag in Project Settings → Custom Code → Body Code (end of body). This ensures it runs on every page.

5. Data Attributes Reference

Config element ([data-block-transition]):

Attribute Default Description
data-block-transition required Marks the config element. Place one per page.
data-block-size 60 Size of each grid block in pixels.
data-block-color #0f0f0f Background color of the blocks.
data-stagger-amount 0.5 Total time (seconds) to spread the stagger across all blocks.
data-block-duration 0.05 Duration (seconds) of each individual block's fade.
data-enter-delay 0.3 Delay (seconds) before the enter/reveal animation starts.
data-stagger-from random Stagger pattern. Options: random, center, edges, end, or a number index.

Link element ([data-transition-link]):

Attribute Default Description
data-transition-link required Add to any <a> tag to enable the block transition on click.

6. Webstudio Setup Summary

What Where in Webstudio
GSAP <script> tag Project Settings → Custom Code → Head Code
CSS (.block-transition-grid, etc.) Project Settings → Custom Code → Head Code (in <style> tags)
Config <div> with data-block-transition HTML Embed component on each page (or in a shared layout section)
JavaScript Project Settings → Custom Code → Body Code (in <script> tags)
data-transition-link on links Select link → Settings panel → Custom Attributes

7. Customization Examples

<!-- Larger blocks, white color, slower animation -->
<div
  data-block-transition
  data-block-size="100"
  data-block-color="#ffffff"
  data-stagger-amount="0.8"
  data-block-duration="0.08"
  data-enter-delay="0.5"
  data-stagger-from="center"
></div>

<!-- Small dense blocks, fast transition -->
<div
  data-block-transition
  data-block-size="30"
  data-block-color="#0f0f0f"
  data-stagger-amount="0.3"
  data-block-duration="0.03"
  data-enter-delay="0.2"
  data-stagger-from="random"
></div>