Skip to content

Instantly share code, notes, and snippets.

@lewdev
Last active June 9, 2024 02:09
Show Gist options
  • Select an option

  • Save lewdev/2845e8aba51b7bc77ccf7bb2a054bf7d to your computer and use it in GitHub Desktop.

Select an option

Save lewdev/2845e8aba51b7bc77ccf7bb2a054bf7d to your computer and use it in GitHub Desktop.
👨‍💻 My Matrix Digital Rain implementation

👨‍💻 Matrix Digital Rain

I was really curious about implementing the Matrix Digital Rain effect and looked at many implementations.

The best implementation I saw was Digital Rain by javascriptacademy.

I think the others were a little overly complicated.

Naturally, I had to code golf it and make it work in an address bar:

🕸 Address Bar implementation (373b)

data:text/html,<body style=overflow:hidden;margin:0 onload="x=c.getContext`2d`;j=l=>x.fillStyle=l;r=Math.random;c.width=w=innerWidth;c.height=h=innerHeight;p=[];f=20;x.font=f+'px A';setInterval(`j('rgba(0,0,0,.05)');x.fillRect(0,0,w,h);j('lime');for(i=1e3;i--;){x.fillText(String.fromCharCode(r()*93+33),i*f,y=(p[i]=p[i]||1)*f);p[i]=y>h&&r()>.98?0:y/f+1}`,f)"><canvas id=c>

🔠 Display words randomly (581b)

This snippet of code also displays words randomly with the digital rain in white.

To have random words appear on screen, replace PUT,RANDOM,WORDS,HERE with words of your own. This also resizes the canvas when the screen resizes.

Because the code has gotten bigger, I went ahead and just added those other features including a black background.

data:text/html,<body bgcolor=🔥 style=overflow:hidden;margin:0 onload="a='PUT,RANDOM,WORDS,HERE'.split`,`;b='';f=30;x=c.getContext`2d`;j=l=>x.fillStyle=l;r=Math.random;z=()=>{c.width=w=innerWidth;c.height=h=innerHeight;d=~~(w/f);x.font=f+'px monospace'};z();onresize=z;p=[];setInterval(`j('rgba(0,0,0,.05)');x.fillRect(0,0,w,h);for(i=0;i<d;i++){if(!b){g=~~(r()*(d-1))+1;b=a[~~(r()*a.length)];}j(g&&g==i?'white':'lime');x.fillText(g&&g==i?b.charAt(0):String.fromCharCode(r()*93+33),i*f,y=(p[i]=p[i]||1)*f);if(g&&g==i)b=b?b.substr(1):0;p[i]=y>h&&r()>.98?0:y/f+1}`,f)"><canvas id=c>

👨‍🏫 Lessons learned

  • The address bar does not like certain character which need to use its proper URL Encode version.
    • # needs to be %23
    • % needs to be %25
  • The equivalent of #0F0 became %230F0 so using lime used less characters.
  • Using unescaped special characters gives no error messages. Just a blank screen, which is terribly hard to debug.

😀 Character sets

Use this code to display the char codes. Using char codes helps save on characters used in your code.

const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const randChar = letters.charAt(Math.random() * letters.length);

vs

const randChar = String.fromCharCode(Math.random() * 25 + 65);

The 2nd implementation uses less code and has the potential to use other and larger character sets.

Char-sets: Start value + values

  • Chess Pieces: 9812 + 11
  • Card Suits: 9824 + 8
  • Music Notes: 9833 + 7
  • Stars: 10022 + 37

👀 Observe char codes to their characters

Run this code to display the list of code values.

<table width="100%"><tbody><tr id=o></tr></tbody></table>

<script>
const showFromChar = (c, count) => new Array(count).fill().map((_, i) => {
  const startCode = c.codePointAt();
  const code = startCode + i;
  return `${i}-` + String.fromCodePoint(code) + ` (${code})`;
}).join`<br>`;

o.innerHTML = ['ア', 'あ', 'A', 'a', '0', '😀', 'ㄴ'].map(s => `<td valign="top">${showFromChar(s, 100)}</td>`).join``;
</script>

⌨ Character sets

  • 48-58 (10 chars) Numbers (0-9)
  • 65-90 (26 chars) Capital Letters (A-Z)
  • 97-122 (26 chars) Lowercase Letters (a-z)
  • 12354 (84 chars) Hiragana
  • 12450 (88 chars) Katakana
  • 128512 (68 chars) Emoji Faces 😀
  • 128512 (88 chars) Korean
  • 35830 (??? chars) Chinese (I put in 999 and saw more characters)

Display character set

<p id=o></p>
<script>
const getCharSet = (start, count) => (
  new Array(count).fill().map((_, i) => String.fromCodePoint(start + count))
);
//displays Katanaka set
o.innerHTML = getCharSet(12450, 88).join`<br>`;
</script>

👩‍💻 Methods to learn

Getting the Char Code with codePointAt and charCodeAt

'A'.codePointAt()`
returns 65

Display the character with String.fromCharCode and String.fromCodePoint

String.fromCharCode(65)
returns 'A'
String.fromCodePoint(65)
returns 'A'

The difference between the two is that fromCharCode doesn't work with emojis. So You should use codePointAt and fromCodePoint.

Variations

Hiragana Version

data:text/html,<body onload="x=c.getContext`2d`;r=Math.random;c.width=innerWidth;c.height=innerHeight;f=20;n=~~(c.width/f);p=new Array(n).fill(1);x.font=f+'px monospace';setInterval(`x.fillStyle='rgba(0,0,0,0.05)';x.fillRect(0,0,c.width,c.height);x.fillStyle='lime';for(i=0;i<n;i++){x.fillText(String.fromCharCode(~~(r()*84)+12354),i*f,p[i]*f);if(p[i]*f>c.height&&r()>.975)p[i]=0;p[i]++}`,60)"><canvas id=c><style>body{overflow:hidden;margin:0}

Katakana Version

data:text/html,<body onload="x=c.getContext`2d`;r=Math.random;c.width=innerWidth;c.height=innerHeight;f=20;n=~~(c.width/f);p=new Array(n).fill(1);x.font=f+'px monospace';setInterval(`x.fillStyle='rgba(0,0,0,0.05)';x.fillRect(0,0,c.width,c.height);x.fillStyle='lime';for(i=0;i<n;i++){x.fillText(String.fromCharCode(r()*88+12450),i*f,p[i]*f);if(p[i]*f>c.height&&r()>.975)p[i]=0;p[i]++}`,20)"><canvas id=c><style>body{overflow:hidden;margin:0}

Emoji Version

data:text/html,<body onload="x=c.getContext`2d`;r=Math.random;c.width=innerWidth;c.height=innerHeight;f=20;n=~~(c.width/f);p=new Array(n).fill(1);x.font=f+'px monospace';setInterval(`x.fillStyle='rgba(0,0,0,0.05)';x.fillRect(0,0,c.width,c.height);x.fillStyle='lime';for(i=0;i<n;i++){x.fillText(String.fromCodePoint(~~(r()*68)+128512),i*f,p[i]*f);if(p[i]*f>c.height&&r()>.975)p[i]=0;p[i]++}`,60)"><canvas id=c><style>body{overflow:hidden;margin:0}

Korean Version

data:text/html,<body onload="x=c.getContext`2d`;r=Math.random;c.width=innerWidth;c.height=innerHeight;f=20;n=~~(c.width/f);p=new Array(n).fill(1);x.font=f+'px monospace';setInterval(`x.fillStyle='rgba(0,0,0,0.05)';x.fillRect(0,0,c.width,c.height);x.fillStyle='lime';for(i=0;i<n;i++){x.fillText(String.fromCodePoint(~~(r()*88)+12596),i*f,p[i]*f);if(p[i]*f>c.height&&r()>.975)p[i]=0;p[i]++}`,60)"><canvas id=c><style>body{overflow:hidden;margin:0}

Chinese Version

data:text/html,<body onload="x=c.getContext`2d`;r=Math.random;c.width=innerWidth;c.height=innerHeight;f=20;n=~~(c.width/f);p=new Array(n).fill(1);x.font=f+'px monospace';setInterval(`x.fillStyle='rgba(0,0,0,0.05)';x.fillRect(0,0,c.width,c.height);x.fillStyle='lime';for(i=0;i<n;i++){x.fillText(String.fromCodePoint(~~(r()*999)+35830),i*f,p[i]*f);if(p[i]*f>c.height&&r()>.975)p[i]=0;p[i]++}`,60)"><canvas id=c><style>body{overflow:hidden;margin:0}

Vehicles

data:text/html,<body onload="x=c.getContext`2d`;r=Math.random;c.width=innerWidth;c.height=innerHeight;f=20;n=~~(c.width/f);p=new Array(n).fill(1);x.font=f+'px monospace';setInterval(`x.fillStyle='rgba(0,0,0,0.05)';x.fillRect(0,0,c.width,c.height);x.fillStyle='lime';for(i=0;i<n;i++){x.fillText(String.fromCodePoint(~~(r()*36)+128640),i*f,p[i]*f);if(p[i]*f>c.height&&r()>.975)p[i]=0;p[i]++}`,20)"><canvas id=c><style>body{overflow:hidden;margin:0}

Chess Pieces

data:text/html,<body onload="x=c.getContext`2d`;r=Math.random;c.width=innerWidth;c.height=innerHeight;f=30;n=~~(c.width/f);p=new Array(n).fill(1);x.font=f+'px monospace';setInterval(`x.fillStyle='rgba(0,0,0,0.05)';x.fillRect(0,0,c.width,c.height);x.fillStyle='lime';for(i=0;i<n;i++){x.fillText(String.fromCharCode(~~(r()*12)+9812),i*f,p[i]*f);if(p[i]*f>c.height&&r()>.975)p[i]=0;p[i]++}`,60)"><canvas id=c><style>body{overflow:hidden;margin:0}

Card Suits

data:text/html,<body onload="x=c.getContext`2d`;r=Math.random;c.width=innerWidth;c.height=innerHeight;f=30;n=~~(c.width/f);p=new Array(n).fill(1);x.font=f+'px monospace';setInterval(`x.fillStyle='rgba(0,0,0,0.05)';x.fillRect(0,0,c.width,c.height);x.fillStyle='lime';for(i=0;i<n;i++){x.fillText(String.fromCharCode(~~(r()*8)+9824),i*f,p[i]*f);if(p[i]*f>c.height&&r()>.975)p[i]=0;p[i]++}`,60)"><canvas id=c><style>body{overflow:hidden;margin:0}

Cards

data:text/html,<body onload="x=c.getContext`2d`;r=Math.random;c.width=innerWidth;c.height=innerHeight;f=50;n=~~(c.width/f);p=new Array(n).fill(1);x.font=f+'px monospace';b=new Array(86).fill(0).map((_,i)=>i+127136).filter(a=>![127151,127152,127168,127183,127184].includes(a));setInterval(`x.fillStyle='rgba(0,0,0,0.05)';x.fillRect(0,0,c.width,c.height);x.fillStyle='lime';for(i=0;i<n;i++){
t=b[~~(r()*b.length)];x.fillText(String.fromCodePoint(t),i*f,p[i]*f);if(p[i]*f>c.height&&r()>.975)p[i]=0;p[i]++}`,60)"><canvas id=c><style>body{overflow:hidden;margin:0}

Stars

data:text/html,<body onload="x=c.getContext`2d`;r=Math.random;c.width=innerWidth;c.height=innerHeight;f=30;n=~~(c.width/f);p=new Array(n).fill(1);x.font=f+'px monospace';setInterval(`x.fillStyle='rgba(0,0,0,0.05)';x.fillRect(0,0,c.width,c.height);x.fillStyle='lime';for(i=0;i<n;i++){x.fillText(String.fromCodePoint(~~(r()*38)+10022),i*f,p[i]*f);if(p[i]*f>c.height&&r()>.975)p[i]=0;p[i]++}`,60)"><canvas id=c><style>body{overflow:hidden;margin:0}
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Matrix digital rain</title>
<style>
html { background: black; height: 100%; overflow: hidden; }
body { margin: 0; padding: 0; height: 100%; }
</style>
</head>
<body>
<canvas id="Matrix"></canvas>
<script>
//Source: https://github.com/javascriptacademy-stash/digital-rain
const canvas = document.getElementById('Matrix');
const context = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const katakana = 'アァカサタナハマヤャラワガザダバパイィキシチニヒミリヰギジヂビピウゥクスツヌフムユュルグズブヅプエェケセテネヘメレヱゲゼデベペオォコソトノホモヨョロヲゴゾドボポヴッン';
const latin = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
const nums = '0123456789';
const alphabet = katakana + latin + nums;
const fontSize = 16;
const columns = canvas.width/fontSize;
const rainDrops = [];
for( let x = 0; x < columns; x++ ) {
rainDrops[x] = 1;
}
const draw = () => {
context.fillStyle = 'rgba(0, 0, 0, 0.05)';
context.fillRect(0, 0, canvas.width, canvas.height);
context.fillStyle = '#0F0';
context.font = fontSize + 'px monospace';
for(let i = 0; i < rainDrops.length; i++)
{
const text = alphabet.charAt(Math.floor(Math.random() * alphabet.length));
context.fillText(text, i*fontSize, rainDrops[i]*fontSize);
if(rainDrops[i]*fontSize > canvas.height && Math.random() > 0.975){
rainDrops[i] = 0;
}
rainDrops[i]++;
}
};
setInterval(draw, 30);
</script>
</body>
</html>
<style>body { overflow: hidden; margin: 0; background: #000 }</style>
<canvas id=c></canvas>
<script>
const x = c.getContext('2d');
const jln = [['ア', 89], ['A', 25], ['0', 10]];
let fontSize = 16;
let columns;
let rainDrops;
const r = () => Math.random();
const resize = () => {
c.width = innerWidth;
c.height = innerHeight;
fontSize = c.width / 50;
columns = ~~(c.width / fontSize);
rainDrops = new Array(columns).fill(1);
x.font = fontSize + 'px monospace';
};
resize();
addEventListener('resize', resize);
setInterval(() => {
x.fillStyle = "rgba(0, 0, 0, 0.05)";
//x.fillStyle = "rgba(255, 255, 255, 0.05)";
x.fillRect(0, 0, c.width, c.height);
x.fillStyle = "#0F0";
// x.fillStyle = "#080";
for (let i = 0; i < columns; i++) {
const index = ~~(r() * 3);
const [char, len] = jln[index];
const text = String.fromCharCode(r() * len + char.charCodeAt());
x.fillText(text, i * fontSize, rainDrops[i] * fontSize);
if(rainDrops[i] * fontSize > c.height && r() > 0.975){
rainDrops[i] = 0;
}
rainDrops[i]++;
}
}, 20);
</script>
data:text/html,<body onload="x=c.getContext`2d`;r=()=>Math.random();c.width=innerWidth;c.height=innerHeight;f=20;n=~~(c.width/f);p=new Array(n).fill(1);x.font=f+'px monospace';setInterval(`x.fillStyle='rgba(0,0,0,0.05)';x.fillRect(0,0,c.width,c.height);x.fillStyle='lime';for(i=0;i<n;i++){x.fillText(String.fromCharCode(r()*58+65),i*f,p[i]*f);if(p[i]*f>c.height&&r()>.975)p[i]=0;p[i]++}`,20)"><canvas id=c><style>body{overflow:hidden;margin:0}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment