Go SUPER SAIYAN with RxJS Observables

Goku going Super Saiyan

Growing up in the 90s, few things captured my imagination like the DragonBall Z anime. This epic saga of super-powered martial artists defending the Earth enthralled a generation. And no moment was more iconic than when Goku, the series protagonist, first transformed into a Super Saiyan.

Goku‘s hair turns golden blonde and stands on end as a fiery aura surrounds him. Lightning crackles as his power level skyrockets. In this legendary form, his speed, strength, and abilities ascend to godlike heights.

As web developers, we may not be able to shoot energy blasts or fly, but with a web browser and RxJS Observables, we can recreate some of that Super Saiyan magic! Today, we‘ll build a web app that brings Goku‘s transformation to life with cutting-edge JavaScript. Let‘s go!

What are RxJS Observables?

RxJS is a library for composing asynchronous and event-based programs using observable sequences. It provides a powerful toolset for reactive programming, a paradigm that focuses on reacting to streams of data over time.

The key entity in RxJS is an observable. Observables represent a stream that can emit zero to infinite values over time. You can think of them as a fusion of arrays and promises. Like an array, an observable can emit multiple values. And like a promise, it can asynchronously deliver values in the future.

The true power of observables shines when you transform, filter, and compose them using operators. RxJS provides a rich set of operators that let you map, filter, reduce, combine and manipulate observables in many ways. Operators can take an observable as input and return a new observable with the specified behavior.

We‘ll see several key operators in action as we build our Super Saiyan app. But first, let‘s set the stage!

Setting up the App

We‘ll keep the HTML simple with just a few key elements:

<div id="root">
  <div id="meter-container">
    <span>Hold any key to POWER UP!</span>
    <div id="meter"></div>
  </div>
  <div id="sprite" class="base"></div>  
</div>

The #sprite div will house our Goku sprite animation. The #meter-container and #meter will form our power meter UI.

In our CSS, we‘ll define some properties for Goku‘s default and Super Saiyan states:

.base, .ssj {
  width: 120px; 
  height: 250px;
  animation: stand 0.8s steps(2) infinite;
}

.base {
  background-image: url(‘img/goku-standing-sheet.png‘);  
}

.ssj {
  background-image: url(‘img/ssj-goku-standing-sheet.png‘);
}

Both will use a stand animation that quickly alternates between two sprite frames to create a bobbing effect. The .base and .ssj classes will specify different sprite sheets.

Goku base and Super Saiyan sprite sheets

We‘ll then add a powerup class and animation that rapidly cycles through the frames of Goku powering up.

.powerup {
  animation: powerup 0.2s steps(8) infinite;
}

@keyframes powerup {
  from { background-position: 0px; }
  to { background-position: -960px; }  
}

Goku power up sprite sheet

With our UI in place, it‘s time to bring Goku to life with RxJS!

Powering Up Reactively

Inside our JavaScript code, we want Goku to power up anytime a key is pressed down, and stop when the key is released. We can create two observables to represent these events.

const { fromEvent } = rxjs;

const powerup = fromEvent(document, ‘keydown‘);
const rest = fromEvent(document, ‘keyup‘);

The fromEvent operator creates an observable that emits whenever the specified event occurs on the target. So powerup will emit a value when any key is pressed, and rest will emit when the key is released.

We can then subscribe to these observables and add or remove the powerup class accordingly.

powerup.subscribe(() => {
  sprite.classList.add(‘powerup‘);
});

rest.subscribe(() => {
  sprite.classList.remove(‘powerup‘);
});

Now Goku will animate powering up whenever we hold down a key! But to reach Super Saiyan, he‘ll need to raise his power level over time as he charges up. Let‘s track that level using RxJS.

Over 9000!?!?

The scan operator is ideal for tracking how a value accumulates over time. It‘s like reduce for observables. As the source observable emits values, you provide a function that takes the current accumulated value and the emitted value and returns the new accumulated value.

We can use scan to track Goku‘s power level as keys are pressed down.

const { scan } = rxjs.operators;

powerup.pipe(
    scan(level => level + 1, 1),
    tap(level => {
      console.log(`Power Level: ${level}`);
    })  
  )
  .subscribe(() => {
    sprite.classList.add(‘powerup‘);
  });

Now each time a key is pressed down, we‘ll increment Goku‘s power level starting from 1. The tap operator lets us perform side effects, in this case logging the level, without affecting the observable stream.

Power level increasing in console

It‘s over 9000!!! But to truly go Super Saiyan, Goku needs to reach a power level of 100. When that happens, we‘ll swap out his sprite sheet.

We can define a mapping of power levels to transformations:

const transformations = {
  100: {
    current: ‘base‘,
    next: ‘ssj‘
  }
};

Once Goku‘s power level reaches 100, we‘ll remove the .base class and add the .ssj class. To implement this, we‘ll use the map and filter operators:

powerup.pipe(  
    scan(level => level + 1, 1),
    tap(level => {
      sprite.classList.add(‘powerup‘);
    }),
    map(level => transformations[level]),
    filter(transformation => transformation)
  )
  .subscribe(({ current, next }) => {
    sprite.classList.remove(current);
    sprite.classList.add(next);
  });

Inside our pipe, we map the current power level to its corresponding transformation in our mapping, if any. Then we filter out any undefined transformations. So the observable will only emit when Goku actually transforms.

Finally, we destructure the current and next states and use them to update Goku‘s classes in the subscribe block.

Goku going super saiyan

AWESOME! Goku is now a Super Saiyan in our app!

Let‘s add one last touch: a power meter to show the user Goku‘s progress.

Power Meter UI

Inside our tap operator, we can call a function to update our power meter‘s width based on Goku‘s percentage to 100.

const fillMeter = level => {  
  const maxWidth = meter.parentNode.offsetWidth;
  const percent = Math.min(level / 100, 1);
  meter.style.width = `${percent * maxWidth}px`;  
};

tap(level => {
  sprite.classList.add(‘powerup‘);
  fillMeter(level);  
})

And with that, our app is complete!

Full app with power meter

Thirst for More!

We‘ve only scratched the surface of Goku‘s transformations and RxJS‘s potential. You could extend this app with even more transformations like Super Saiyan 2, 3, and dare I say Super Saiyan God?!

On the RxJS side, there are tons more operators to explore for combining, filtering, delaying, and otherwise manipulating observables. Subjects and behavior subjects are also key concepts for more advanced use cases.

I hope this nostalgic example has sparked your interest in RxJS! It‘s a powerful tool for crafting elegant, reactive web apps. So the next time you‘re building a UI, consider how observables could help. Your users may just shout "OVER 9000!!!" with joy.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *