• An Animated Intro to RxJS
  • By David Khourshid
  • The Nuggets translation Project
  • Translator: luoyaqifei
  • Proofread: Vuuihc, AceLeeWinnie

Watch animation and learn RxJS

You’ve probably heard of RxJS, ReactiveX, reactive programming, or just functional programming before. These terms are becoming more and more important when we talk about the latest and greatest front-end technologies. If your mind is like mine, you must have no idea how to learn it at first.

According to the ReactiveX. IO:

ReactiveX is a library that uses observable sequences to organize asynchronous, event-based programs.

There is much to ponder in this sentence alone. In this article, we’ll take a different approach to learning about RxJS (a JavaScript implementation of ReactiveX) and Observables (observables) by creating responsive animations.

Understand observables

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

Now assume that each element in the array flows over time, that is, you don’t get all the elements at once, you get them one at a time. You might get the first element in the first second, the next element in the third, and so on. As shown in the picture:



observable

An Observable is a collection of data that flows over time.

Just like you do with arrays, you can map, filter, or do other things to the data to create and combine new Observables. Finally, you can subscribe to these Observables to do whatever you want with the last stream of data. This is where RxJS comes in.

RXJS to fit

The easiest way to get started with RxJS is to use a CDN, although there are many ways to install it depending on your project’s needs.

HTML <! -- Latest, minimized version of RxJS --><scriptsrc="https://unpkg.com/ @reactivex/rxjs@latest/dist/global/Rx.min.js"></script>Copy the code

Once you have RxJS in your project, you can create an Observable from anything:

JS

const aboutAnything = 42;

// Created from just about anything (single data).
// Observable sends the data and completes.
const meaningOfLife$ = Rx.Observable.just(aboutAnything);

// Created from an array or an iterable.
Observable sends each element in the array and completes.
const myNumber$ = Rx.Observable.from([1.2.3.4.5]);

// Created from a promise.
Observable sends the final result and completes (or throws an error).
const myData$ = Rx.Observable.fromPromise(fetch('http://example.com/users'));

// Created from an event.
Observable sends events continuously to the event listener.
const mouseMove$ = Rx.Observable
  .fromEvent(document.documentElement, 'mousemove');Copy the code

Note that the dollar character ($) after the variable is just a convention to indicate that the variable is an Observable. Observables can be used to represent anything that can be represented as a flow of data over time, such as events, promises, timed execution functions, interval execution functions, and animations.

The observables now created don’t do anything meaningful unless you actually observe them. Subscription does this, and can be created with.subscribe().

JS

// Whenever we receive a number from an Observable,
// Print it on the console.
myNumber$.subscribe(number= > console.log(number));

/ / the result:
/ / > 1
/ / > 2
/ / > 3
/ / > 4
/ / > 5Copy the code

Let’s learn in real combat:

codepen

JS

const docElm = document.documentElement;
const cardElm = document.querySelector('#card');
const titleElm = document.querySelector('#title');

const mouseMove$ = Rx.Observable
  .fromEvent(docElm, 'mousemove');

mouseMove$.subscribe(event= > {
  titleElm.innerHTML = `${event.clientX}.${event.clientY}`
});Copy the code

Subscription changes titleElm’s.innerHTML to the current mouse position each time a mouseMove$Observable occurs. The.map operator (similar to array.prototype. map) can help simplify this code:

JS

// produce results like {x: 42, y: 100} instead of the whole event
const mouseMove$ = Rx.Observable
  .fromEvent(docElm, 'mousemove')
  .map(event= > ({ x: event.clientX, y: event.clientY }));Copy the code

With a little calculation and inline style, you can make the card rotate with the mouse. Y/clientHeight and pos.x/clientWidth are both between 0 and 1, so multiplying by 50 and subtracting by half (25) yields a value between -25 and 25, which is what we need for our rotation value:

codepen

JS

const docElm = document.documentElement;
const cardElm = document.querySelector('#card');
const titleElm = document.querySelector('#title');

const { clientWidth, clientHeight } = docElm;

const mouseMove$ = Rx.Observable
  .fromEvent(docElm, 'mousemove')
  .map(event= > ({ x: event.clientX, y: event.clientY }))

mouseMove$.subscribe(pos= > {
  const rotX = (pos.y / clientHeight * - 50) - 25;
  const rotY = (pos.x / clientWidth * 50) - 25;

  cardElm.style = `
    transform: rotateX(${rotX}deg) rotateY(${rotY}deg);
  `;
});Copy the code

use.mergeTo combine

Now if you want to respond to mouse movements and touch movements on touch devices, you can use RxJS to combine observables in different ways without any confusion due to callbacks. In this example, we will use the.merge operator. Like merging multiple lanes into a single lane, this returns a single Observable containing all the data fused from multiple Observables.

JS

const touchMove$ = Rx.Observable
  .fromEvent(docElm,'touchmove').map(event= >({
    x: event.touches[0].clientX,
    y: event.touches[0].clientY
  }));
const move$ = Rx.Observable.merge(mouseMove$, touchMove$);

move$.subscribe(pos= >{/ /... });Copy the code

Going ahead, try panning left and right on the touch device:

codepen

There are other useful operators for composing observables, such as.switch(),.combinelatest (), and.withlatestFrom (), which we’ll discuss next.

I’m gonna add Smooth Motion

Because the rotation card is implemented so succinctly, the motion is a little stiff. Whenever the mouse (or finger) stops, the rotation stops. To remedy this, linear interpolation (LERP) can be used. This general technique is described in this tutorial by Rachel Smith. In essence, instead of jumping directly from point A to point B, LERP will go A portion of the way on each animation frame. This results in a smooth transition, even if the mouse/touch has stopped.

Let’s create a function that has one responsibility: given a start value and an end value, use LERP to calculate the next value:

JS

function lerp(start, end) {
  const dx = end.x - start.x;
  const dy = end.y - start.y;

  return {
    x: start.x + dx * 0.1.y: start.y + dy * 0.1}; }Copy the code

Very short but very nice piece of code. We have a pure function that returns a new, linearly interpolated position value at a time, approaching the next (end) position by moving the current (start) position by 10% at each animation frame.

The Scheduler and.interval

Now the question is, how do we represent animation frames in RxJS? The answer is that RxJS has something called Scheduler, which controls when data is sent from an Observable, as well as other functions such as when subscription should start receiving data.

With rx.Observatory.interval (), you can create an Observable that sends data at regularly timed intervals, such as every second (rx.Observatory.interval (1000)). If you create a small interval, such as the Rx. Observables. The interval (0), and timing for use only in the Rx. The Scheduler. AnimationFrame each animation frames to send data, a data will be sent every 16 to 17 milliseconds, As you would expect, within an animation frame:

JS

const animationFrame$ = Rx.Observable.interval(0, Rx.Scheduler.animationFrame);Copy the code

use.withLatestFromTo combine

To create a smooth linear interpolation, you only need to care about the latest mouse/touch position in each animation frame. This can be done using the.withlatestfrom () operator:

JS

const smoothMove$ = animationFrame$
  .withLatestFrom(move$, (frame, move) => move);Copy the code

SmoothMove $is now a new Observable that only sends the latest data from Move$when animationFrame$sends a data. This is also what we want — you don’t want the data to be sent from outside the animation frame (unless you really like the lag). The second parameter is a function that describes what to do when combined with the latest data of each Observable. In this case, the only value that matters is the move value, which is everything that is returned.

use.scanIn the transition

Now that you have an Observable that sends the latest data from Move $on every animation frame, it’s time to add linear interpolation. If you specify a function that passes in the current and next values, the scan() operator “accumulates” those values from an Observable.

This is best for our linear interpolation use case. Remember that our lerp(start, end) function takes two arguments: the start (current) value and the end (next) value.

JS

const smoothMove$ = animationFrame$
  .withLatestFrom(move$, (frame, move) => move)
  .scan((current, next) = > lerp(current, next));
  // or simplified: .scan(lerp)Copy the code

Now you can subscribe to smoothMove$instead of move$to see linear interpolation in the action:

codepen

conclusion

RxJS is not an animation library, of course, but using composable, descriptive ways to process data over time is a core concept for ReactiveX, so animation is a good way to demonstrate this technology. Reactive programming is another way of thinking about programming and has many advantages:

  • It’s declarative, composable, and immutable, which avoids callback hell and makes your code more concise, reusable, and modular.
  • It’s useful for handling any type of asynchronous data, whether it’s fetching data, communicating over WebSockets, listening for external events from multiple sources, or animation.
  • “Separation of concerns” – you use Observables and operators to declaratively represent the data you want, and then in a single.subscribe()Deal with side effects, rather than spilling them all over your code base.
  • There are implementations in so many languages — Java, PHP, Python, Ruby, C#, Swift, and other languages you’ve never even heard of.
  • It is not a framework, and many popular frameworks (such as React, Angular, and Vue) work well with it.
  • You can get cool points if you want, but ReactiveX was first implemented nearly a decade ago (2009), from an idea by Conal Elliott and Paul Hudak two decades ago (1997), This idea describes functional responsive animation (amazing, amazing, amazing). Needless to say, it is battle-tested.

This article explored a number of useful parts and concepts in RxJS — creating observables using.fromevent () and.interval(), manipulating observables using.map() and.scan(), Using the merge () and withLatestFrom () combined with multiple observables, and use of Rx. The Scheduler. AnimationFrame introduce the Scheduler. Here are some other useful resources for learning RxJS:

  • ReactiveX: RxJS – Official documentation
  • RxMarbles – Visual Observable
  • Andre Staltz’s Introduction to Responsive Programming that you missed

If you want to drill deeper into RxJS animation (and become more declarative with CSS variables) check out my CSS Development Conference 2016 slide and my talk at JSConf Iceland 2016. To give you more inspiration, here’s some code that uses RxJS for animation:

  • 3D digital clock
  • Heart rate APP concept
  • Use RxJS lens drag