An Animated Intro to RxJS

Share this:

You might have heard of RxJS, or ReactiveX, or reactive programming, or even just functional programming before. These are terms that are becoming more and more prominent when talking about the latest-and-greatest front-end technologies. And if you're anything like me, you were completely bewildered when you first tried learning about it.

ReactiveX is a library for composing asynchronous and event-based programs by using observable sequences.

That's a lot to digest in a single sentence. In this article, we're going to take a different approach to learning about RxJS (the JavaScript implementation of ReactiveX) and Observables, by creating reactive animations.

Understanding Observables

An array is a collection of elements, such as [1, 2, 3, 4, 5]. You get all the elements immediately, and you can do things like map, filter and map them. This allows you to transform the collection of elements any way you'd like.

Now suppose that each element in the array occurred over time; that is, you don't get all elements immediately, but rather one at a time. You might get the first element at 1 second, the next at 3 seconds, and so on. Here's how that might be represented:

This can be described as a stream of values, or a sequence of events, or more relevantly, an observable.

An observable is a collection of values over time.

Just like with an array, you can map, filter, and more over these values to create and compose new observables. Finally, you can subscribe to these observables and do whatever you want with the final stream of values. This is where RxJS comes in.

Note: the dollar sign ($) at the end of the variable is just a convention to indicate that the variable is an observable. Observables can be used to model anything that can be represented as a stream of values over time, such as events, Promises, timers, intervals, and animations.

As is, these observables don't do much of anything, at least until you actually observe them. A subscription will do just that, which is created using .subscribe():

From the mouseMove$ observable, every time a mousemove event occurs, the subscription changes the .innerHTML of the titleElm to the position of the mouse. The .map operator (which works similar to the Array.prototype.map method) can help simplify things:

With a little math and inline styles, you can make the card rotate towards the mouse. Both pos.y / clientHeight and pos.x / clientWidth evaluate to values between 0 and 1, so multiplying that by 50 and subtracting half (25) produces values from -25 to 25, which is just what we need for our rotation values:

Combining with .merge

Now let's say you wanted this to respond to either mouse moves or touch moves, on touch devices. Without any callback mess, you can use RxJS to combine observables in many ways. In this example, the .merge operator can be used. Just like multiple lanes of traffic merging into a single lane, this returns a single observable containing all of the data merged from multiple observables.

Adding Smooth Motion

As neat as the rotating card is, the motion a bit too rigid. Whenever the mouse (or finger) stops, the rotation instantly stops. To remedy this, linear interpolation (LERP) can be used. The general technique is described in this great tutorial by Rachel Smith. Essentially, instead of jumping from point A to B, LERP will go a fraction of the way on every animation tick. This produces a smooth transition, even when mouse/touch motion has stopped.

Let's create a function that has one job: to calculate the next value given a start value and an end value, using LERP:

Short and sweet. We have a pure function that returns a new, linearly interpolated position value every time, by moving a current (start) position 10% closer to the next (end) position on each animation frame.

Schedulers and .interval

The question is, how do we represent animation frames in RxJS? Turns out, RxJS has something called Schedulers which control when data is emitted from an observable, among other things like when subscriptions should start receiving values.

Using Rx.Observable.interval(), you can create an observable that emits values on a regularly scheduled interval, such as every one second (Rx.Observable.interval(1000)). If you create a tiny interval, such as Rx.Observable.interval(0) and schedule it to emit values only on every animation frame using Rx.Scheduler.animationFrame, a value will be emitted about every 16 to 17ms, within the animation frame, as expected:

Now, smoothMove$ is a new observable that emits the latest values from move$only whenever animationFrame$ emits a value. This is desired -- you don't want values emitted outside animation frames (unless you really like jank). The second argument is a function that describes what to do when combining the latest values from each observable. In this case, the only important value is the move value, which is all that's returned.

Transitioning with .scan

Now that you have an observable emitting the latest values from move$ on every animation frame, it's time to add linear interpolation. The .scan() operator "accumulates" the current value and next value from an observable, given a function that takes those values.

Conclusion

RxJS is not an animation library, of course, but handling values over time in a composable, declarative way is such a core concept to ReactiveX that animation serves as a great way to demonstrate the technology. Reactive Programming is a different way of thinking about programming, with many advantages:

It is declarative, composable, and immutable, which avoids callback hell and makes your code more terse, reusable, and modular.

It is very useful in dealing with all types of async data, whether it's fetching data, communicating via WebSockets, listening to external events from multiple sources, or even animations

"Separation of concerns" - you declaratively represent the data that you expect using Observables and operators, and then deal with side effects in a single .subscribe() instead of sprinkling them around your code base.

There are implementations in so many languages - Java, PHP, Python, Ruby, C#, Swift, and others you might not have even heard of.

It is not a framework, and many popular frameworks (such as React, Angular, and Vue) work very well with RxJS.

You can get hipster points if you want, but ReactiveX was first implemented nearly a decade ago (2009), stemming from ideas by Conal Elliott and Paul Hudaktwo decades ago (1997), in describing functional reactive animations (surprise surprise). Needless to say, it's battle-tested.

This article explored a number of useful parts and concepts of RxJS - creating Observables with .fromEvent() and .interval(), operating on observables with .map() and .scan(), combining multiple observables with .merge() and .withLatestFrom(), and introducing Schedulers with Rx.Scheduler.animationFrame. There are many other useful resources for learning RxJS:

Been meaning to get into RxJS but haven’t had the chance. This is an awesome introduction!

For the smoothing example, is it true that the values emitted by smoothMove$ would never actually reach the latest value, at least in a theoretical sense? For each animation step, the last emitted value is brought 10% closer to the last actual value, which means that the actual value will never be emitted.

Of course, with finite precision, and for what it’s used for, this isn’t really a problem. Just an interesting thought.

Well explained and illustrated article!
IMHO the observer pattern you describe here is equivalent to the native event driven approach.
The nice perks of RxJS are to use the ecmascript6 array methods for producing cleaner code. Otherwise this misses some information for how to unsubscribe observable

Thanks! There are many advantages–the biggest, in my opinion, is the “primitive” handling of time-based values without nested callbacks or mutation. I didn’t include unsubscribing because 1) I wanted to keep this article fairly short, and 2) in this example, there is never a need to unsubscribe. Maybe in another article!

👋

CSS-Tricks* is created, written by, and maintained by Chris Coyier and a team of swell people. It is built on WordPress and powered up by Jetpack. It is made possible through sponsorships from products and services we like.