🚀

Update available

We just released a new resource or update, refresh the Vault to access the latest version.

Cancel

Refresh now

Harri

Profile Picture

Harri

Lemke

Interactive Pixel Grid

Documentation

Webflow

Code

Setup: External Scripts

HTML

Copy
<script src="https://cdn.jsdelivr.net/npm/gsap@3.15/dist/gsap.min.js"></script>

Step 1: Add HTML

HTML

Copy
<div 
  class="bg"
  data-grid=""	
  data-grid-size-desktop="20"
  data-grid-size-mobile="8"
  data-grid-background="#FFFFFF"
  data-grid-border-size="2"
  data-grid-border-color="rgba(0, 0, 0, 0.2)"
  data-grid-colors="[#C5D4FF, #B7B0FF, #FF5FCE, #4136FF, #FFF751,  #87FEFF, #C4FF3F]"
></div>

Step 2: Add CSS

CSS

Copy
.grid-bg {
  position: absolute;
  inset: 0%;
  z-index: 0;
}

Step 2: Add Javascript

Step 3: Add Javascript

Javascript

Copy
function debounce(func, wait) {
  let timeout;
  return function(...args) {
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(this, args), wait);
  }
}

function initGrid(el) {
  // Define default values that are used if no attributes other than [data-grid] are used
  const defaults = {
    gridBackground: "#FFFFFF",
    gridSizeDesktop: 20,
    gridSizeMobile: 8,
    gridBorderSize: 2,
    gridBorderColor: "rgba(0, 0, 0, 0.2)",
    gridColors: ["#C5D4FF", "#B7B0FF", "#FF5FCE", "#4136FF", "#FFF751", "#87FEFF", "#C4FF3F"]
  };

  const gridBackground = el.getAttribute("data-grid-background") || defaults.gridBackground;
  const gridSizeDesktop = parseInt(el.getAttribute("data-grid-size-desktop")) || defaults.gridSizeDesktop;
  const gridSizeMobile = parseInt(el.getAttribute("data-grid-size-mobile")) || defaults.gridSizeMobile;
  const gridBorderSize = parseFloat(el.getAttribute("data-grid-border-size")) || defaults.gridBorderSize;
  const gridBorderColor = el.getAttribute("data-grid-border-color") || defaults.gridBorderColor;
  
  // Parse grid colors so you can use HEX or RGBA values in the attribute
  let gridColors = defaults.gridColors;
  const attrColors = el.getAttribute("data-grid-colors");
  if (attrColors) {
    try {
      gridColors = JSON.parse(attrColors);
    } catch (e) {
      try {
        gridColors = JSON.parse(attrColors.replace(/'/g, '"'));
      } catch (e2) {
        gridColors = defaults.gridColors;
      }
    }
  }


  el.style.backgroundColor = gridBackground;
  const canvas = document.createElement("canvas");
  el.appendChild(canvas);
  const ctx = canvas.getContext("2d");

  let cols, rows, squareSize, blocks, lastHoveredIndex = null;
  
  // Generate the actual grid
  function setupGrid() {
    canvas.width = el.offsetWidth;
    canvas.height = el.offsetHeight;
    cols = (window.innerWidth < 768) ? gridSizeMobile : gridSizeDesktop;
    squareSize = canvas.width / cols;
    rows = Math.ceil(canvas.height / squareSize);
    blocks = [];
    for (let y = 0; y < rows; y++) {
      for (let x = 0; x < cols; x++) {
        blocks.push({ x: x * squareSize, y: y * squareSize, color: "white", alpha: 0 });
      }
    }
  }
  
  // Draw the squares
  function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    blocks.forEach(block => {
      ctx.fillStyle = block.color;
      ctx.globalAlpha = block.alpha;
      ctx.fillRect(block.x, block.y, squareSize, squareSize);
      ctx.globalAlpha = 1;
      ctx.strokeStyle = gridBorderColor;
      ctx.lineWidth = gridBorderSize;
      ctx.strokeRect(block.x, block.y, squareSize, squareSize);
    });
    requestAnimationFrame(draw);
  }

  // Define how long it takes for blocks to fade out
  function fadeOut(block) {
    gsap.to(block, { alpha: 0, duration: 2, delay: 0.5 });
  }

  function supportsTouch() {
    return "ontouchstart" in window || navigator.maxTouchPoints;
  }

  // Init mousemove listener if we're NOT on a touchscreen
  if (!supportsTouch()) {
    canvas.addEventListener("mousemove", (event) => {
      const rect = canvas.getBoundingClientRect();
      const mouseX = event.clientX - rect.left;
      const mouseY = event.clientY - rect.top;
      const hoveredIndex = blocks.findIndex(block =>
        mouseX >= block.x &&
        mouseX < block.x + squareSize &&
        mouseY >= block.y &&
        mouseY < block.y + squareSize
      );
      if (hoveredIndex !== -1 && hoveredIndex !== lastHoveredIndex) {
        const block = blocks[hoveredIndex];
        block.color = gridColors[Math.floor(Math.random() * gridColors.length)];
        
        // Define duration of fade in animation
        gsap.to(block, { alpha: 1, duration: 0.1, overwrite:true });
        
        // Start fade out
        fadeOut(block);
        lastHoveredIndex = hoveredIndex;
      }
    });
  }

  window.addEventListener("resize", debounce(setupGrid, 200));

  setupGrid();
  draw();
}

function initGrids() {
  document.querySelectorAll("[data-grid]").forEach(el => initGrid(el));
}

// Initialize Interactive Pixel Grid
document.addEventListener("DOMContentLoaded", () => {
  initGrids();
});

Implementation

This script is setup to be as flexible as possible. In essence, all you need is a div with [data-grid] attribute. All of the attributes (also listed below) are optional overrides of the default values listed in the JavaScript. So imagine you want to have the same grid all over your website, just change the default values, and you don't have to include any extra variables on your data-grid elements.

Default options:

const defaults = { 
  gridBackground: "#FFFFFF", // Background color of the canvas
  gridSizeDesktop: 20, // Amount of blocks in a row for desktop
  gridSizeMobile: 8, // Amount of blocks for screens < 768px
  gridBorderSize: 2, // Stroke width of the blocks
  gridBorderColor: "rgba(0, 0, 0, 0.2)", // Stroke color
  
  // Aray with default color options for hover
  gridColors: ["#C5D4FF", "#B7B0FF", "#FF5FCE", "#4136FF", "#FFF751", "#87FEFF", "#C4FF3F"] 
};
Copy

Available HTML attributes:

data-grid="" → The main selector, not optional

data-grid-size-desktop="20" → Controls the amount of blocks that will fit in a row.

data-grid-size-mobile="8" → Same as above, but for when the screen is smaller than 768px.

data-grid-background="#FFFFFF" → Set the background color of the canvas, can be 'transparent' too.

data-grid-border-size="2" → Set the stroke width of the blocks.

data-grid-border-color="rgba(0, 0, 0, 0.2)" → Set the color of the stroke on all the blocks.

data-grid-colors="[#C5D4FF, #B7B0FF, #FF5FCE, #4136FF, #FFF751, #87FEFF, #C4FF3F]" → An array of colors that are randomized on hover. This can be rgba values too, just wrap each color in between ' ' like so: ['rgba(0,0,0,0.1)', 'rgba(0,0,0,0.2)'].

Live preview

Osmo Robot AI

Copy context for AI

Beta

Webflow

HTML/CSS/JS

Save video

Copy share link

Resource details

  • Published

    February 28, 2025

  • Category

    Hover Interactions

  • Popularity

    1.2K visitors

  • Need help?

    Join Slack

Advanced
Background
Code
Sections
VanillaJS
Canvas
Hover
Pixels
Cursor

Original source

Ilja van EckIlja van Eck

Creator Credits

We always strive to credit creators as accurately as possible. While similar concepts might appear online, we aim to provide proper and respectful attribution.

s