Creating a cyberpunk-inspired animation
I caught the flu this year. To pass the time, I watched Ghost in the Shell. The visual effects in the movie were mesmerizing, and they inspired me to add visual effects to my personal site.
Side note: If you haven't seen the 1995 sci-fi classic yet, I highly recommend it. Its fascinating questions about life, intelligence, and technology feel as relevant now as ever.
I wanted to add a subtle animation that evokes the mysterious and omnipresent technology from the movie. To do so, I animated the text in the navigation bar to look like words are slowly revealing themselves through random characters, as if being decoded in a glitchy terminal or system startup sequence.
The rest of this post describes how it works.
The Algorithm
The underlying algorithm take 4 inputs.
- A string of text to be animated
- Duration of the effect (in ms)
- Size of the scramble window (max # of random characters to show at once in front of the currently revealed characters)
- A delay to start the effect (in ms)
On startup, the function calculates characters revealed per second by taking the total characters in the string and dividing it by the scramble duration in seconds. It also calculates the frames per character by taking the frames per second and dividing it by the characters per second. I set the default time for each frame of the animation to 30ms.
To illustrate, here are the values I used for my About link.
text = "About"
scrambleDuration = 800ms
scrambleWindowSize = 3
startDelay = 250ms
charsPerSecond = 5 / (800/1000) = 6.25
framesPerChar = (1000 / 30) / 6.25 ≈ 5.33
Then, it calls a method every 30ms until the max number of frames is hit. In this case it is 27 frames because 800/30 = 26.7 frames. We round up because each frame is discrete.
For every recursion, it calculates how many characters should be revealed at the present time by taking the current frame count and dividing it by the frames per character, rounding down.
Using this value, it builds a new string. It runs another recursion that repeats itself as many times are there are total number of characters in the input text string.
If a character is to be revealed, it adds the character in that position from the original input string to the new string. If not and the current position is within the scramble window size, it adds a randomly selected character to the new string. It does not add any characters if the current position is beyond the scramble window. Then, it returns the new string to be displayed.
This is repeated every 30ms until all of the characters in the original string are revealed by the 27th frame.
This is what the lifecycle of the animation looks like for "About".
| Frame | Time | revealedCount | Display |
|---|---|---|---|
| 1 | 250ms | 0 | X@3 |
| 2 | 280ms | 0 | Q*7 |
| 3 | 310ms | 0 | !NZ |
| 4 | 340ms | 0 | %K@ |
| 5 | 370ms | 0 | M*2 |
| 6 | 400ms | 1 | A + !Z@ |
| 7 | 430ms | 1 | A + Q*3 |
| 8 | 460ms | 1 | A + 7!N |
| 9 | 490ms | 1 | A + %KZ |
| 10 | 520ms | 1 | A + X@2 |
| 11 | 550ms | 2 | Ab + *3! |
| 12 | 580ms | 2 | Ab + QN% |
| 13 | 610ms | 2 | Ab + 7ZK |
| 14 | 640ms | 2 | Ab + @!2 |
| 15 | 670ms | 2 | Ab + X*N |
| 16 | 700ms | 3 | Abo + 3% |
| 17 | 730ms | 3 | Abo + Q! |
| 18 | 760ms | 3 | Abo + 7Z |
| 19 | 790ms | 3 | Abo + @N |
| 20 | 820ms | 3 | Abo + *K |
| 21 | 850ms | 3 | Abo + X2 |
| 22 | 880ms | 4 | Abou + ! |
| 23 | 910ms | 4 | Abou + Q |
| 24 | 940ms | 4 | Abou + 7 |
| 25 | 970ms | 4 | Abou + % |
| 26 | 1000ms | 4 | Abou + N |
| 27 | 1030ms | 5 | About |
In real-time, the quick speed gives it the glitchy, cascading reveal effect seen in the video above.
The Implementation
I implemented this on my website using a react component. The full code is available here: gist.github.com.
Simply put, the primary algorithm is wrapped in a component I called <ScrambleText> that accepts the 4 input values.
<ScrambleText text="About" scrambleDuration={800} startDelay={250} scrambleWindowSize={3} />
Then, I offset each link in my navigation by 100ms to give it a slight left-to-right cascading effect.
<ScrambleText text="About" ... startDelay={250} />
<ScrambleText text="Projects" ... startDelay={350} />
<ScrambleText text="Posts" ... startDelay={450} />
<ScrambleText text="History" ... startDelay={550} />
<ScrambleText text="Open Source" ... startDelay={650} />
For accessibility and SEO purposes, I kept an element in the DOM to provide a static version of each piece of text on page load. I hid it visually, but is is available for screen readers and web crawlers. Inversely, I hid the scramble text component from screen readers to prevent them from reading the scrambled strings from each frame of the animation.
<span className="invisible">About</span>
<span aria-hidden="true">
<ScrambleText text="About" scrambleDuration={800} startDelay={250} scrambleWindowSize={3} />
</span>
I had a lot of fun building this cyberpunk animation effect. I hope this is a helpful breakdown for anyone interested in building something similar.
Thank you for reading.