WEB ANIMATIONS REDEFINED

Reactive Animations With
RxJS & CSS Variables

Florida

Australia

Austria

(Web) Animation is hard.

also Austria

What we will talk about

What we won't talk about

  • History of FRAN
  • Reactive Extensions (RxJS)
  • CSS Custom Properties
  • History of Franz Ferdinand
  • React
  • Inline Styles / CSS-in-JS

Reactive Animations?

WHAT ARE

A "reactive animation" is one involving discrete changes, due to events.

Reactive Animations?

WHAT ARE

A "reactive animation" is one involving discrete changes, due to events.

By allowing programmers to express the "what" of an interactive animation, one can hope to then automate the "how" of its presentation.

Reactive Animations?

WHAT ARE

Reactive Animations

CSS Variables

​CSSDevConf San Antonio
David Khourshid @davidkpiano

Music & Animation

HAVE MANY THINGS IN COMMON

Continuous

Discrete

&

Declarative

Imperative

vs.

Immutable

Mutable

vs.

&

Functional Programming

Constraints

CSS is not powerful

BUT THAT'S A GOOD THING

The Rule of Least Power

Powerful languages inhibit information reuse.

Use the least powerful language suitable for expressing information, constraints or programs on the WWW.

PRINCIPLE

GOOD PRACTICE

CSS

IS

AWESOME

FUN

box-sizing: soap-box;

So here's the plan.

STREAM

VALUES

CUSTOM PROPERTIES

REACTIVE STYLES

Why 

?

CSS Variables

CUSTOM PROPERTIES

:root {
  --my-color: white;
}
button {
  background-color: var(--my-color, blue);
}

SPECIFICITY

INHERITANCE

CSS Variables

CUSTOM PROPERTIES

button.special {
  --my-color: red;
}

Wait for it...

element.style
  .setProperty('--my-color', 'blue');
element.style
  .getPropertyValue('--my-color');
// => 'blue'
element.style
  .removeProperty('--my-color');

CSS Variables

CUSTOM PROPERTIES

Isn't conference WIFI awesome?

CSS Variables

BROWSER SUPPORT

Nobody cares

CSS Variables

PREPROCESSOR SUPPORT

💅

Pretend this is a really cool demo

CSS Variables

CSS Variables

JavaScript Events

const docStyle = document.documentElement.style;
const ball = document.querySelector('.ball');

document.addEventListener('mousemove', (e) => {
  ball.style.transform = `
    translateX(${e.clientX}px)
    translateY(${e.clientY}px)
  `;
});

JAVASCRIPT

class Particle {
  constructor() {
    this.this.this.this.this.this.
    this.this.this.this.this.this.
    this.this.this.this.this.this.
    this.this.this.this.this.this.
    this.this.this.this.this.this.
    this.this.this.this.this.this.
    this.this.this.this.this.this.
    this.this.this.this.this.this.
    this.this.this.this.this.this.
    this.this.this.this.this.this.
    this.this.this.this.this.this.
    this.this.this.this.this.this.
    this.this.this.this.this.this.
    this.this.this.this.this.this.
    this.this.this.this.this.this.
    this.this.this.this.this.this.
    this.this.this.this.this.this.
    this.this.this.this.this.this
  }
}
const docStyle = document.documentElement.style;
const ball = document.querySelector('.ball');

document.addEventListener('mousemove', (e) => {
  ball.style.setProperty('--mouse-x', e.clientX);
  ball.style.setProperty('--mouse-y', e.clientY);
});
.ball {
  transform:
    translateX(calc(var(--mouse-x) * 1px))
    translateY(calc(var(--mouse-y) * 1px));
}

JAVASCRIPT

CSS

APP

STATE

MOUSE

TOUCH

AUDIO

ASYNC DATA

OTHER ANIMATIONS

CSS VARIABLES

CSS Variables

CSS Variables

JS Observables

JS Observables

[

]

ARRAYS

JS Observables

1s

3s

4s

6.5s

9.5s

STREAMS

JS Observables

  • ARRAYS
  • ASYNC
  • IMMUTABLE
  • SUBSCRIBABLE
  • are like
  • that are
  •  
  • and
Rx.Observable
  .from([1, 2, 3, 4, 5]);
Rx.Observable
  .fromEvent(document, 'mousemove');
Rx.Observable
  .fromEventPattern((callback) =>
    hammerFoo.on('pan', callback));
const ball = document.querySelector('#ball');

const hBall = new Hammer(ball);

const pan$ = Rx.Observable
  .fromEventPattern((handler) =>
    hBall.on('pan', handler));
pan$.subscribe((event) => {
  // do anything you want
});
pan$.subscribe(
  handleNext,
  handleError,
  handleComplete
);
Aggregate, All, Amb, and_, And, Any, apply, as_blocking, asObservable, AssertEqual, asyncAction, asyncFunc, Average, averageDouble, averageFloat, averageInteger, averageLong, blocking, Buffer, bufferWithCount, bufferWithTime, bufferWithTimeOrCount, byLine, cache, case, Cast, Catch, catchError, catchException, collect, collect (RxScala version of Filter), CombineLatest, combineLatestWith, Concat, concat_all, concatMap, concatMapObserver, concatMapTo, concatAll, concatWith, Connect, connect_forever, cons, Contains, controlled, Count, countLong, Create, cycle, Debounce, decode, DefaultIfEmpty, Defer, deferFuture, Delay, delaySubscription, delayWithSelector, Dematerialize, Distinct, distinctKey, distinctUntilChanged, distinctUntilKeyChanged, Do, doAction, doAfterTerminate, doOnCompleted, doOnEach, doOnError, doOnRequest, doOnSubscribe, doOnTerminate, doOnUnsubscribe, doseq, doWhile, drop, dropRight, dropUntil, dropWhile, ElementAt, ElementAtOrDefault, Empty, emptyObservable, empty?, encode, ensures, error, every, exclusive, exists, expand, failWith, Filter, filterNot, Finally, finallyAction, finallyDo, find, findIndex, First, FirstOrDefault, firstOrElse, FlatMap, flatMapFirst, flatMapIterable, flatMapIterableWith, flatMapLatest, flatMapObserver, flatMapWith, flatMapWithMaxConcurrent, flat_map_with_index, flatten, flattenDelayError, foldl, foldLeft, for, forall, ForEach, forEachFuture, forIn, forkJoin, From, fromAction, fromArray, FromAsyncPattern, fromCallable, fromCallback, FromEvent, FromEventPattern, fromFunc0, from_future, from_iterable, fromIterator, from_list, fromNodeCallback, fromPromise, fromRunnable, Generate, generateWithAbsoluteTime, generateWithRelativeTime, generator, GetEnumerator, getIterator, GroupBy, GroupByUntil, GroupJoin, head, headOption, headOrElse, if, ifThen, IgnoreElements, indexOf, interleave, interpose, Interval, into, isEmpty, items, Join, join (string), jortSort, jortSortUntil, Just, keep, keep-indexed, Last, lastOption, LastOrDefault, lastOrElse, Latest, latest (Rx.rb version of Switch), length, let, letBind, limit, LongCount, ManySelect, Map, map (RxClojure version of Zip), MapCat, mapCat (RxClojure version of Zip), map-indexed, mapTo, mapWithIndex, Materialize, Max, MaxBy, Merge, mergeAll, merge_concurrent, mergeDelayError, mergeObservable, mergeWith, Min, MinBy, MostRecent, Multicast, multicastWithSelector, nest, Never, Next, Next (BlockingObservable version), none, nonEmpty, nth, ObserveOn, ObserveOnDispatcher, observeSingleOn, of, of_array, ofArrayChanges, of_enumerable, of_enumerator, ofObjectChanges, OfType, ofWithScheduler, onBackpressureBlock, onBackpressureBuffer, onBackpressureDrop, OnErrorResumeNext, onErrorReturn, onExceptionResumeNext, orElse, pairs, pairwise, partition, partition-all, pausable, pausableBuffered, pluck, product, Publish, PublishLast, publish_synchronized, publishValue, raise_error, Range, Reduce, reductions, RefCount, Repeat, repeat_infinitely, repeatWhen, Replay, rescue_error, rest, Retry, retry_infinitely, retryWhen, Return, returnElement, returnValue, runAsync, Sample, Scan, scope, Select (alternate name of Map), select (alternate name of Filter), selectConcat, selectConcatObserver, SelectMany, selectManyObserver, select_switch, selectSwitch, selectSwitchFirst, selectWithMaxConcurrent, select_with_index, seq, SequenceEqual, sequence_eql?, SequenceEqualWith, Serialize, share, shareReplay, shareValue, Single, SingleOrDefault, singleOption, singleOrElse, size, Skip, SkipLast, skipLastWithTime, SkipUntil, skipUntilWithTime, SkipWhile, skipWhileWithIndex, skip_with_time, slice, sliding, slidingBuffer, some, sort, sort-by, sorted-list-by, split, split-with, Start, startAsync, startFuture, StartWith, startWithArray, stringConcat, stopAndWait, subscribe, SubscribeOn, SubscribeOnDispatcher, subscribeOnCompleted, subscribeOnError, subscribeOnNext, Sum, sumDouble, sumFloat, sumInteger, sumLong, Switch, switchCase, switchIfEmpty, switchLatest, switchMap, switchOnNext, Synchronize, Take, take_with_time, takeFirst, TakeLast, takeLastBuffer, takeLastBufferWithTime, takeLastWithTime, takeRight (see also: TakeLast), TakeUntil, takeUntilWithTime, TakeWhile, takeWhileWithIndex, tail, tap, tapOnCompleted, tapOnError, tapOnNext, Then, thenDo, Throttle, throttleFirst, throttleLast, throttleWithSelector, throttleWithTimeout, Throw, throwError, throwException, TimeInterval, Timeout, timeoutWithSelector, Timer, Timestamp, To, to_a, ToArray, ToAsync, toBlocking, toBuffer, to_dict, ToDictionary, ToEnumerable, ToEvent, ToEventPattern, ToFuture, to_h, toIndexedSeq, toIterable, toIterator, ToList, ToLookup, toMap, toMultiMap, ToObservable, toSet, toSortedList, toStream, ToTask, toTraversable, toVector, tumbling, tumblingBuffer, unsubscribeOn, Using, When, Where, while, whileDo, Window, windowWithCount, windowWithTime, windowWithTimeOrCount, windowed, withFilter, withLatestFrom, Zip, zipArray, zipWith, zipWithIndex

😱perators!

.filter(ball => ball.color === 'green')        
.map(ball => makeSquare(ball))  
.throttleTime(1000)
.scan((a, b) => a + b, 0)

1

2

3

5

6

8

1

1

1

2

1

2

Marble Diagrams

Observaballs

Think of RxJS as
Lodash for observables.

If you can read this, the WIFI sucks

Just kidding, the WIFI isn't that bad

Are you thinking what I'm thinking?

Are you thinking what I'm thinking?

What if we could model observable events
as CSS variables?

Are you thinking what I'm thinking?

What if we could model observable events
as CSS variables?

What if we could write JavaScript in CSS?

CSS VARIABLES +
RxJS OBSERVABLES =

Reactive Animations!

If you can read this, the wifi still sucks

Rx.Subject()

CSS Variables

mouseMove$

scroll$

tap$

swipe$

timer$

...etc.

(Observers)

An Rx.Subject is both
an observable and an observer.

const mouse$ = Rx.Observable
  .fromEvent(document, 'mousemove')
  .map(({ clientX, clientY }) => ({
    x: clientX,
    y: clientY,
  }));

RxCSS

const style$ = RxCSS({
  mouse: mouse$,
});


style$.subscribe(...);

yarn whatever ¯\_(ツ)_/¯

RxCSS

:root {
  --mouse-x: 0;
  --mouse-y: 0;
}

.ball {
  transform: translate(
    calc(var(--mouse-x) * 1px))
    calc(var(--mouse-y) * 1px))
  );
}

0kb

Minified, GZIPped, Deleted

Oh my god

Please wifi just work

😭

wifi y u do dis

WHY ANIMATE WITH

  • Easily debuggable
  • No excessive DOM manipulation
  • DOM node independent
  • Full power of CSS
  • Theming & media queries
  • calc() is your new best friend
  • They work in SVG!

CSS Variables?

RxCSS({
  mouse: mouse$,
  pan: pan$,
  // ...
}).subscribe((values) => {
  
  // do anything!

});

WHY ANIMATE WITH

CSS Variables?

WHY ANIMATE WITH

CSS Variables?

WHY ANIMATE WITH

RxJS Observables?

What if?

  • Constraint Layouts
  • Shared element transitions
  • Physics modeling
  • Additive animations
  • Choreography
  • Configurability
  • Canvas, WebGL
  • Much, much more!

Thank you Agent Conf!

@davidkpiano

Reactive Animations with RxJS and CSS Variables

By David Khourshid

Reactive Animations with RxJS and CSS Variables

Agent Conference 2017

  • 9,409