0xfeiker

0xfeiker

frontend engineer / web3 新手

How to achieve the code rain effect

I saw a web3 project website where the background features a rain effect made up of code, which looks pretty cool, so I tried to analyze it in this article.

Upon closer inspection of the effect, it can be seen that it consists of two parts:

  1. At the beginning of the effect, text made up of characters is generated uniformly from top to bottom, transitioning from transparent to opaque until it fills the entire window.
  2. After filling the window, the opacity of characters in different columns of each line is inconsistent, creating an effect of falling at different speeds.

We will first try to implement a text matrix generated uniformly from top to bottom using a canvas. The overall idea is to switch the entire canvas to a col * row matrix based on the size of each character, while generating code characters column by column using a timer.

The code logic is quite simple:

  • In each drawing cycle, draw the content of one line while calculating the position of the next line's characters in canvas coordinates.
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
const allChars =
  'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()_+~`|}{[]:;?><,./-=\\';
const chars = Array.from(allChars);

// Font size for each character
const fontSize = 16;

// 2D information for drawing
const dimensions = {
  width: window.innerWidth,
  height: window.innerHeight,
};

canvas.width = dimensions.width;
canvas.height = dimensions.height;

// Number of characters per line
const rows = Math.floor(dimensions.width / fontSize);
const currentHeights = Array.from({ length: rows }).fill(0);
const textColor = '#0F0';
const bgColor = 'rgba(0, 0, 0, 0.08)';

// Draw one line of content each time
function draw() {
  context.fillStyle = textColor;
  context.font = `${fontSize}px monospace`;

  for (let i = 0; i < rows; i++) {
    const x = i * fontSize;
    const y = currentHeights[i] * fontSize;
    context.fillText(chars[Math.floor(Math.random() * chars.length)], x, y);

    // Y coordinate for the next line
    currentHeights[i] = currentHeights[i] + 1;
  }
}

setInterval(draw, 100);

image

Next, we need to handle:

The canvas board slowly becomes blurry from top to bottom, with the text closer to the top being more opaque.

This can be achieved by filling the entire canvas with a background color with transparency at the start of each drawing cycle. This processing will lead to:

  1. Before the current X round of drawing, the color is its foreground color, and based on the browser's color blending calculations, the text will appear transparent.
  2. During the current X round of drawing, the color is the background color, which has no effect on the text of the current line.

The core code is as follows:

const textColor = '#0F0';
const bgColor = 'rgba(0, 0, 0, 0.08)';

// Draw one line of content each time
function draw() {
  // Fill the entire canvas with a layer of black background with 0.08 opacity
  context.fillStyle = bgColor;
  context.fillRect(0, 0, dimensions.width, dimensions.height);

  // Start drawing the current line of text
  context.fillStyle = textColor;
  context.font = `${fontSize}px monospace`;

  for (let i = 0; i < rows; i++) {
    const x = i * fontSize;
    const y = currentHeights[i] * fontSize;

    context.fillText(chars[Math.floor(Math.random() * chars.length)], x, y);

    // Y coordinate for the next line
    currentHeights[i] = currentHeights[i] + 1;
  }
}

`
image

Now the entire main effect is in place, and we need to check if the text at the bottom overflows the canvas boundary. If it does, we start drawing from the top again.

// Draw one line of content each time

function draw() {
  // Fill the entire canvas with a layer of black background with 0.08 opacity
  context.fillStyle = bgColor;
  context.fillRect(0, 0, dimensions.width, dimensions.height);

  // Start drawing the current line of text
  context.fillStyle = textColor;
  context.font = `${fontSize}px monospace`;

  for (let i = 0; i < rows; i++) {
    const x = i * fontSize;
    const y = currentHeights[i] * fontSize;
    context.fillText(chars[Math.floor(Math.random() * chars.length)], x, y);

    // Y coordinate for the next line
    currentHeights[i] = currentHeights[i] + 1;

    // Start from the top
    if (currentHeights[i] * fontSize > dimensions.height) {
      currentHeights[i] = 0;
    }
  }
}

image

It looks a bit ugly for now, so we will make whether each column starts from the top a random event.

// Start from the top

if (currentHeights[i] * fontSize > dimensions.height && Math.random() > 0.9) {
  currentHeights[i] = 0;
}

This way, the code rain effect is achieved.

image

Additionally, we can add a small optimization, such as highlighting special characters and hiding the initial code presentation from top to bottom.

The complete code is as follows:

const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
const allChars =
  '∅abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()_+~`|}{[]:;?><,./-=\\';
const chars = Array.from(allChars);

// Font size for each character

const fontSize = 16;

// 2D information for drawing

const dimensions = {
  width: window.innerWidth,
  height: window.innerHeight,
};

canvas.width = dimensions.width;
canvas.height = dimensions.height;

// Number of characters per line

const rows = Math.floor(dimensions.width / fontSize);
const currentHeights = Array.from({ length: rows }).fill(0);
const textColor = '#0F0';
const highlightColor = '#FF55BB';
const bgColor = 'rgba(0, 0, 0, 0.08)';

// Draw one line of content each time

function draw() {
  // Fill the entire canvas with a layer of black background with 0.08 opacity

  context.fillStyle = bgColor;
  context.fillRect(0, 0, dimensions.width, dimensions.height);
  // Start drawing the current line of text
  context.fillStyle = textColor;
  context.font = `${fontSize}px monospace`;

  for (let i = 0; i < rows; i++) {
    const x = i * fontSize;
    const y = currentHeights[i] * fontSize;
    const char = chars[Math.floor(Math.random() * chars.length)];
    context.fillStyle = char === '∅' ? highlightColor : textColor;
    context.fillText(char, x, y);
    // Y coordinate for the next line
    currentHeights[i] = currentHeights[i] + 1;
    // Start from the top

    if (
      currentHeights[i] * fontSize > dimensions.height &&
      Math.random() > 0.95
    ) {
      currentHeights[i] = 0;
    }
  }
}

setInterval(draw, 100);

#blog

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.