Back

Technical: How we built our Midjourney inspired background animation

2024-07-28

We took inspiration from the stunning visual effects of Midjourney to create an engaging and dynamic animated sequence for our landing page. Here's a step-by-step guide on how we implemented this effect, complete with the code.

image

Overview

The animation displays text from a given set of sentences in a grid format, animating the characters to create a mesmerizing visual effect. This guide will walk you through the code, explaining each part of the implementation.

The Code

1. Setting Up the Component

We start by importing the necessary hooks from React and defining our LandingEffects component. The component uses a useRef hook to reference the text grid container.

"use client";

import { useEffect, useRef } from 'react';

const LandingEffects = () => {
  const textGridRef = useRef<HTMLDivElement>(null);

2. Populating the Grid with Text

In the useEffect hook, we define the text content and split it into sentences. We then create a grid of cells and populate each cell with characters from the sentences.

  useEffect(() => {
    const str = `
      /boop Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
      /boop Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
      /boop Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
      /boop Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
      /boop At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum.
      /boop Et harum quidem rerum facilis est et expedita distinctio.
      /boop Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo.
      /boop Minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus.
      /boop Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates.
      /boop Repudiandae sint et molestiae non recusandae.
      /boop Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis.
      /boop Voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat.
    `;

    const sentences = str.split(/[\n\r]/).filter(s => s.length > 0).map(s => s + ' ');

    function getCharAt(x: number, y: number): string {
      const si = y % sentences.length;
      const ci = Math.min(x, sentences[si].length - 1);
      return sentences[si][ci];
    }

    const rows = 30;
    const cols = 100;

    const cellMap: HTMLDivElement[][] = [];
    const root = textGridRef.current!;
    root.innerHTML = '';

    for (let y = 0; y < rows; y++) {
      const row = document.createElement('div');
      root.appendChild(row);
      cellMap[y] = [];
      for (let x = 0; x < cols; x++) {
        const cell = document.createElement('div');
        cell.classList.add('cell');
        cell.setAttribute('data-row', y.toString());
        cell.setAttribute('data-col', x.toString());
        cell.innerText = getCharAt(x, y);
        row.appendChild(cell);
        cellMap[y][x] = cell;
      }
    }

3. Creating the Animation

Next, we define the animation logic. The animate function is called recursively using requestAnimationFrame to update the text positions based on the elapsed time.

    let animationBegin: number | null = null;

    function animate() {
      if (animationBegin === null) animationBegin = Date.now();
      const timeElapsed = (Date.now() - animationBegin) / 1000;
      drawText(timeElapsed);
      requestAnimationFrame(animate);
    }
    requestAnimationFrame(animate);

    function transform(x: number, y: number, centerX: number, centerY: number, time: number): [number, number] {
      const dx = x - centerX;
      const dy = y - centerY;
      const dist = Math.sqrt(dx * dx + dy * dy);
      const currAngle = Math.atan2(dy, dx);
      const newAngle = currAngle - Math.pow(dist, 0.5) * time * 0.1;

      return [
        centerX + Math.cos(newAngle) * dist,
        centerY + Math.sin(newAngle) * dist
      ];
    }

    function drawText(time: number) {
      for (let y = 0; y < rows; y++) {
        for (let x = 0; x < cols; x++) {
          cellMap[y][x].innerText = ' ';
        }
      }

      for (let y = 0; y < rows; y++) {
        for (let x = 0; x < cols; x++) {
          let [nx, ny] = transform(x / cols, y / rows, 0.5, 0.5, time);
          nx = Math.floor(nx * cols);
          ny = Math.floor(ny * rows);
          if (nx < 0 || ny < 0 || ny >= rows || nx >= cols) continue;
          cellMap[ny][nx].innerText = getCharAt(x, y);
        }
      }
    }
  }, []);

4. Rendering the Component

Finally, we render the textGridRef container, which will hold the animated text grid.

  return (
    <div>
      <div id="text-grid" ref={textGridRef} style={{ display: 'flex', flexDirection: 'column' }}></div>
    </div>
  );
};

export default LandingEffects;

How It Works

  1. Text Content: The text content is defined and split into sentences. Each character in the grid is sourced from these sentences.
  2. Grid Initialization: A grid of div elements is created, and each cell is populated with characters from the sentences.
  3. Animation Logic: The animate function updates the text positions based on the elapsed time, creating a dynamic animation effect. The transform function calculates the new positions of the characters to create the animation.
  4. Rendering: The LandingEffects component renders the grid container, which displays the animated text.

Fun, thank you Midjourney!

Wanted to share how we did it, hope it helps! b33p b00p!

image

HomeBlog