Matrix Code Animation in React

A simple React based animation of code rain from Matrix trilogy

Janko Sokolović
ITNEXT

--

This is my first blog post and I wanted to make it special in much the same way that the first kiss usually isn’t.

I’ve been a fan of Matrix movies ever since the first green symbol fell down on that vintage screen. That’s one of the reasons I wanted to test rendering speed of React and push to see where the limit is. Also, it’s always fun building something you find interesting. Like Lego house, or a pillow fort.

Version of React I’ve used is 16, and since I worked with collections, of course, Lodash (v4)

tl;dr ->If you are lazy as am I, see the full code here: github.com/jasofalcon/matrix

The Font Part

First things first, we want this to look professional. Also, someone from Zion might be reading this. I’m joking, I know Zion isn’t real. Anywho, I’ve used Matrix Font NFI , check it out here — matrix-font-nfi. Looks pretty Matrixy to me.
Another thing I did, is defining a set of characters the app would use, since the font provides symbols for just some letters. Like so:

const chars = [
'a',
'b',
'c',
// ... many more symbols...
'0',
'~',
'!',
'#',
'$'
];
export default chars;

The Fun Part

Ok, Mr. Anderson, let’s take the *red pill* and dive into an awful abyss of mucous.
The app structure is quite simple, and it only has 3 components. The Symbol, the Code and The Matrix!
I wanted to make it atomic, so a Symbol component represents one single character and all the presentation logic connected to it. A Code component is nothing more than an array of symbols, basically a single line falling down. You can easily assume that that Matrix component is nothing but an array of Code components, which are the lists of Symbols. So we have a list of a list of symbols, by mathematical definition forming a …….. matrix of symbols.

Symbol

A simple character. Naturally, it contains a char as its state.

constructor(props) {
super(props);
this.state = { char: this.getRandomChar() };
}

You noticed a getRandomChar method. It does nothing more than getting a random character from the list of symbols we defined earlier.

import chars from "../chars/chars";
// ...
getRandomChar() {
return chars[Math.floor(Math.random() * chars.length)];
}

Now, you might’ve noticed while watching the movie, that some symbols change while falling on the screen. More specifically, last symbol (let’s call it leading symbol, or primary) is always changing, and some other symbols randomly change as well. That being said, let’s make primary symbol changeable and only ‘some’ others.

componentWillMount() {
if (this.props.primary || Math.random() > 0.95) {
this.makeSymbolDynamic();
}
}
makeSymbolDynamic() {
setInterval(() => {
this.setState({ char: this.getRandomChar() });
}, 500);
}

This can cause some performacne issues, because we are registering a handler that is triggered every 500ms. That’s why we only do it if the symbol is primary and for 5% of the others. Math.rand returns value between 0 and 1.

Another detail which I found important is opacity. Every line gets faded towards the end, so there’s another prop which I’m sending to Symbol component, that’s opacity. This is how we render a symbol:

render() {
const { primary, opacity } = this.props;
return (
<div className={"symbol " + (primary ? "primary" : "")}
style={{ opacity }} >
{this.state.char}
</div>
);
}

There’s just ome small css defining that primary symbol is a bit more shiny, that’s it.

Code

Now here’s where we do heavy-lifting. As mentioned, Code component is a collection of symbols. We have several relevant information about this line of symbols. Those are position (x, y), number of symbols, transition (falling) speed, and scale ratio (some are closer and some are further away).

constructor(props, state) {
super(props);
this.state = {
codeLength: 0,
yPosition: 0,
xPosition: 0,
transition: "",
transform: ""
};
}

Before component mounts, we need to set everything up. It might look a bit complex but it is quite straight-forward once you look each line separatelly. Btw, most of these values are calculated empirically.

const SYMBOL_HEIGHT = 30; // Empirically :)
const SYMBOL_WIDTH = 18;
componentWillMount() {
// Some lines are zoomed-in, some zoomed-out
const scaleRatio = _.random(0.8, 1.4); // Empirically chosen numbers
//Min code height is height of screen. No good reason but - why not
const minCodeHeight = _.round(window.innerHeight / SYMBOL_HEIGHT);
//This should calculate how much pixels does line take
const codeLength = _.random(minCodeHeight, minCodeHeight * 2);
//Hacky solution to get the line above top=0 at start (hide it)
const yPosition = (codeLength + 1) * SYMBOL_HEIGHT * scaleRatio;
// we don't want to have partially overlaping lines - make columns
// it basically mean line can only fall in descrete positions
const stepCount = _.round((window.innerWidth - 20) / SYMBOL_WIDTH);
const xPosition = _.random(0, stepCount) * SYMBOL_WIDTH;
// we divide by scale ratio because if it is small it is probably far => thus slower :)
const transition = ` top linear ${_.random(5, 10) / scaleRatio}s`; //different speed
const transform = `scale(${scaleRatio})`;
this.setState({ codeLength, yPosition, xPosition, transition, transform });
}

Next we need to do is to organize the fall. We do not want all to fall at the same time, so for a single line we will set random starting time. setTimeout makes sure that it will start at some pseudo random time

componentDidMount() {
const startTime = _.random(300, 10000); // each starts in different time
setTimeout(() => {
const newHeight = window.innerHeight + this.state.yPosition;
this.setState({ yPosition: -newHeight }); //must be - b/c of start
}, startTime);
}

Now all we need to do is render the component. There’s a nice trick how we create different opacity look for last 5 symbols.

render() {
const code = _.times(this.state.codeLength).map((x, i) => (
// Set opacity to small for last 5
// last one will have least opacity, 5th from last will have almost full
<Symbol key={i} opacity={i <= 5 ? i / 5 : 1} />
));

const { yPosition, xPosition, transition, transform } = this.state;
const styles = {
left: xPosition,
top: -yPosition,
transition,
transform
};
// here we render list of symbols and one more - primary
return (
<div className="code" style={styles}>
{code}
<Symbol primary="true" />
</div>
);
}

Matrix

Don’t worry, this is the easiest part. Let’s say we want to have 100 lines in total.

const CODE_LINES_COUNT = 100;render() {
const codes = _.times(CODE_LINES_COUNT).map((x, i) => <Code key={i} />);
return <div className="Matrix">{codes}</div>;
}

And that’s how you get -> demo

Fun fact

While testing the rendering performance, it turned out that for given number of lines (~100) there might be some lags. However, I have to admit, Chrome is the only browser that is able to render this without any noticable lagging. While Safari, Edge and even new and super-fast Firefox is struggling a bit. So at least all the RAM Chrome is devouring isn’t for nothing.

Thanks for the read, if you liked it share some claps :)

For any kind of feedback, feel free to write

--

--