Cascading Letters effect
Everybody has heard of The Matrix, right? I think just about every graphics website on the planet has also done a tutorial on how to achieve the effect. So what’s one more?
Fun and easy, mainly because it’s code complete. This code is good to run locally in your own computer browser, but if you plan to put this code online, you should first add an input sanitization routine!
To use:
- Open Notepad
- Copy and paste the html code below into the document
- Save document using HTML as the filename extension
- Drag and drop your new HTML file into a blank browser tab.
This code should work fine under all of the major browsers but no guarantees! If you are using an ultra-secure browser, and/or have Javascript disabled it simply won’t run.
What you will see/experience:
Assuming your browser still has that old-school JavaScript personality, once you drag the HTML file into a new browser tab it should open up and play the cascading screen effect. By default it displays 1’s and 0’s at random speeds, but you can use your own characters if you want!
The input screen lets you enter in your own characters / phrase if you want something other than 0’s and 1’s. If you plan on using a phrase, plan on it too being randomized (there is a small statistical chance that all letters will appear in the correct order, but otherwise, it’s gonna look fairly garbled!)
Each column drops at a different speed. You can influence the minimum and maximum drop speed by setting the Minimum and Maximum speed values. 1 is slowest, 10 is fastest. You can get some interesting visual results by varying the length of your custom text and the min/max speeds. If you like it the way it looks like when it first starts, just enter the characters 0 1 as custom text, leave the min/max at 1 and 3 and click on Apply Settings to Matrix.
Clicking on Apply Settings to Matrix makes the UX portion disappear.
To change your custom text or min/max speeds, refresh your browser (usually Ctrl + R).
CLEAN/FULLSCREEN MODE: This effect looks great when your browser is operating at max size in full screen mode. (You can switch to full screen mode in most major browsers by pressing F11 on PCs, or Control + Command + F on a Mac). Pressing F11 again will return your browser to normal.)
Here is the code. Enjoy!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Matrix Cascade</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap');
body {
font-family: 'Inter', sans-serif;
overflow: hidden; /* Prevent scrollbars */
background-color: #000; /* Black background for the matrix effect */
margin: 0; /* Remove default body margin */
padding: 0; /* Remove default body padding */
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh; /* Ensure it takes full viewport height */
color: white; /* Default text color */
}
canvas {
display: block;
background-color: #000; /* Ensure canvas background is black */
position: absolute; /* Position canvas to cover the whole screen */
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1; /* Place canvas behind other content */
}
.controls-container {
position: relative; /* Bring controls above the canvas */
z-index: 1;
background-color: rgba(0, 0, 0, 0.7); /* Semi-transparent background for readability */
padding: 20px;
border-radius: 10px;
display: flex;
flex-direction: column;
gap: 15px;
align-items: center;
box-shadow: 0 0 15px rgba(0, 255, 0, 0.5); /* Green glow effect */
margin-bottom: 20px; /* Space from the bottom if content is centered */
transition: opacity 0.5s ease-in-out, visibility 0.5s ease-in-out; /* Smooth fade transition */
opacity: 1;
visibility: visible;
}
.controls-container.fade-out {
opacity: 0;
visibility: hidden;
pointer-events: none; /* Disable interactions when faded out */
}
.controls-container input[type="text"],
.controls-container input[type="number"] { /* Apply styles to number inputs too */
padding: 10px 15px;
border-radius: 5px;
border: 1px solid #0F0; /* Green border */
background-color: #1a1a1a; /* Darker input background */
color: #0F0; /* Green text */
font-size: 1rem;
width: 250px;
max-width: 80vw; /* Responsive width */
}
.controls-container input[type="text"]::placeholder,
.controls-container input[type="number"]::placeholder { /* Apply styles to number inputs too */
color: #080; /* Lighter green placeholder */
}
.controls-container button {
background-color: #0F0; /* Green button */
color: #000; /* Black text */
padding: 10px 20px;
border-radius: 5px;
border: none;
cursor: pointer;
font-weight: bold;
transition: background-color 0.3s ease, transform 0.1s ease;
box-shadow: 0 0 8px rgba(0, 255, 0, 0.7); /* Button glow */
}
.controls-container button:hover {
background-color: #0C0; /* Darker green on hover */
transform: translateY(-2px); /* Slight lift effect */
}
.controls-container button:active {
transform: translateY(0); /* Press effect */
}
.speed-inputs {
display: flex;
gap: 10px; /* Space between min/max speed inputs */
width: 100%;
justify-content: center;
}
.speed-inputs div {
display: flex;
flex-direction: column;
align-items: center;
gap: 5px;
}
/* Style for the noscript fallback message */
.noscript-message {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #333;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
text-align: center;
padding: 20px;
box-sizing: border-box;
z-index: 100; /* Ensure it's on top */
}
</style>
</head>
<body>
<canvas id="matrixCanvas"></canvas>
<div class="controls-container" id="controlsContainer">
<label for="textInput" class="text-lg text-green-300">Enter your custom text:</label>
<input type="text" id="textInput" placeholder="e.g., Hello World! 123 @#$" autocomplete="off">
<div class="speed-inputs">
<div>
<label for="minSpeedInput" class="text-sm text-green-300">Min Speed (1-10):</label>
<input type="number" id="minSpeedInput" value="1" min="1" max="10">
</div>
<div>
<label for="maxSpeedInput" class="text-sm text-green-300">Max Speed (1-10):</label>
<input type="number" id="maxSpeedInput" value="3" min="1" max="10">
</div>
</div>
<button id="applyTextButton">Apply Settings to Matrix</button>
</div>
<noscript>
<div class="noscript-message">
<p>JavaScript is required to run this animation. Please enable JavaScript in your browser.</p>
</div>
</noscript>
<script>
const canvas = document.getElementById('matrixCanvas');
const ctx = canvas.getContext('2d');
const textInput = document.getElementById('textInput');
const minSpeedInput = document.getElementById('minSpeedInput');
const maxSpeedInput = document.getElementById('maxSpeedInput');
const applyTextButton = document.getElementById('applyTextButton');
const controlsContainer = document.getElementById('controlsContainer'); // Get the controls container
// Set canvas dimensions to fill the window
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// Default characters (binary numbers)
let currentCharacters = '01';
const fontSize = 16;
let columns = canvas.width / fontSize; // Number of columns for the cascade
// Speed variables, initialized with default values
let minFallSpeed = parseFloat(minSpeedInput.value);
let maxFallSpeed = parseFloat(maxSpeedInput.value);
// An array to store the y-position and speed of each character in each column
const drops = [];
// Function to initialize or re-initialize drops with random speeds
function initializeDrops() {
drops.length = 0; // Clear existing drops
// Ensure min and max speeds are valid and min <= max
const validatedMinSpeed = Math.max(1, Math.min(10, minFallSpeed));
const validatedMaxSpeed = Math.max(validatedMinSpeed, Math.min(10, maxFallSpeed));
for (let i = 0; i < columns; i++) {
// Each drop now has a y position and a random speed within the user-defined range
drops[i] = {
y: 1, // Start at the top of the canvas
// Generate a random speed between validatedMinSpeed and validatedMaxSpeed
speed: Math.random() * (validatedMaxSpeed - validatedMinSpeed) + validatedMinSpeed
};
}
}
initializeDrops(); // Initialize drops on load
// Function to update characters and speed based on user input and hide/show controls
function updateSettings() {
const inputText = textInput.value.trim();
if (inputText.length > 0) {
currentCharacters = inputText;
} else {
currentCharacters = '01'; // Revert to binary if input is empty
}
// Update speed variables from input fields
minFallSpeed = parseFloat(minSpeedInput.value);
maxFallSpeed = parseFloat(maxSpeedInput.value);
// Re-initialize drops with new speed settings
initializeDrops();
// Handle fade-out/in of controls
if (inputText.length > 0) { // Only fade out if custom text is entered
controlsContainer.classList.add('fade-out');
} else {
controlsContainer.classList.remove('fade-out');
}
}
// Event listener for the button
applyTextButton.addEventListener('click', updateSettings);
// Event listener for 'Enter' key in the input fields
textInput.addEventListener('keypress', (event) => {
if (event.key === 'Enter') {
updateSettings();
}
});
minSpeedInput.addEventListener('keypress', (event) => {
if (event.key === 'Enter') {
updateSettings();
}
});
maxSpeedInput.addEventListener('keypress', (event) => {
if (event.key === 'Enter') {
updateSettings();
}
});
// Event listener for input changes to show controls if text is cleared
textInput.addEventListener('input', () => {
if (textInput.value.trim() === '') {
controlsContainer.classList.remove('fade-out');
}
});
// The main drawing loop
function draw() {
// Semi-transparent black rectangle to create the fading trail effect
// This makes previous characters fade out over time
ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Set the color for the binary numbers (Matrix green)
ctx.fillStyle = '#0F0'; // Green color
ctx.font = `${fontSize}px Inter`; // Set font size and family
// Loop through each column
for (let i = 0; i < drops.length; i++) {
// Get a character from the currentCharacters string
const text = currentCharacters.charAt(Math.floor(Math.random() * currentCharacters.length));
// Draw the character at the current position in the column
// Use drops[i].y for the y-coordinate
ctx.fillText(text, i * fontSize, drops[i].y * fontSize);
// If the character has reached the bottom of the screen,
// or a random condition is met, reset its position to the top
// Use drops[i].y for the y-coordinate check
if (drops[i].y * fontSize > canvas.height && Math.random() > 0.975) {
drops[i].y = 0; // Reset to top
// Randomize speed again when resetting to top, using current min/max speeds
const validatedMinSpeed = Math.max(1, Math.min(10, minFallSpeed));
const validatedMaxSpeed = Math.max(validatedMinSpeed, Math.min(10, maxFallSpeed));
drops[i].speed = Math.random() * (validatedMaxSpeed - validatedMinSpeed) + validatedMinSpeed;
}
// Move the character down for the next frame using its individual speed
drops[i].y += drops[i].speed;
}
}
// Handle window resizing to keep the canvas full screen
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// Recalculate columns based on new width
columns = canvas.width / fontSize; // Update columns variable
// Re-initialize drops to adjust for new column count and assign new speeds
initializeDrops();
});
// Start the animation loop
setInterval(draw, 33); // Approximately 30 frames per second
</script>
</body>
</html>