0xfeiker

0xfeiker

frontend engineer / web3 新手

How to achieve the effect of code rain

I saw a website for a web3 project with a cool rain effect made up of code. So I tried to analyze it in this article.

Upon closer inspection, the effect consists of two parts:

  1. At the beginning of the effect, text composed of characters is generated from top to bottom at a uniform speed, gradually transitioning from transparent to opaque until it fills the entire window.
  2. After filling the window, the characters in different columns of each row have varying levels of transparency, creating an effect of falling at different speeds.

We first attempt to implement the generation of a text matrix that moves uniformly from top to bottom using canvas. The overall idea is to switch the entire canvas into a matrix of col * row based on the size of each character, and then generate code characters column by column using a timer.

The code logic is relatively simple:

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

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

// Dimensions of the canvas
const dimensions = {
  width: window.innerWidth,
  height: window.innerHeight,
};

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

// Number of characters in each row
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 the content of each row
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);

    // Next y-coordinate of the row
    currentHeights[i] = currentHeights[i] + 1;
  }
}

setInterval(draw, 100);

image

Next, we need to handle the following:

The canvas board gradually becomes blurred from top to bottom, and the text closer to the top has higher transparency.

This can be achieved by filling the entire canvas with a background color with transparency at the beginning of each drawing cycle. This approach will result in:

  1. Before the current X rounds of drawing, the background color acts as the foreground color, resulting in a transparent effect for the text based on the browser's color blending calculation.
  2. During the current X rounds of drawing, the background color acts as the background color and has no effect on the text of the current row.

The core code is as follows:

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

// Draw the content of each row
function draw() {
  // Fill the entire canvas with a background color with transparency of 0.08
  context.fillStyle = bgColor;
  context.fillRect(0, 0, dimensions.width, dimensions.height);

  // Start drawing the text of the current row
  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);

    // Next y-coordinate of the row
    currentHeights[i] = currentHeights[i] + 1;
  }
}

`
image

With this, the main effect is achieved. Next, we check if the bottommost text overflows the canvas boundary. If it does, we start drawing from the top again.

// Draw the content of each row
function draw() {
  // Fill the entire canvas with a background color with transparency of 0.08
  context.fillStyle = bgColor;
  context.fillRect(0, 0, dimensions.width, dimensions.height);

  // Start drawing the text of the current row
  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);

    // Next y-coordinate of the row
    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 let's make each column randomly decide whether to start from the top.

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

With this, the code rain effect is achieved.

image

To further improve the effect, we can highlight special characters and hide 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 of each character
const fontSize = 16;

// Dimensions of the canvas
const dimensions = {
  width: window.innerWidth,
  height: window.innerHeight,
};

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

// Number of characters in each row
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 the content of each row
function draw() {
  // Fill the entire canvas with a background color with transparency of 0.08
  context.fillStyle = bgColor;
  context.fillRect(0, 0, dimensions.width, dimensions.height);

  // Start drawing the text of the current row
  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);

    // Next y-coordinate of the row
    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.