Reactive Animations

CSS Variables

with

JSConf Iceland 2016

Florida

+

What if?

CSS is not powerful

But that's a good thing.

CSS Variables

RxJS Observables

Custom Properties

RxJS & Observables

Why?

CSS Animations

.modal {
  animation: modal-enter 0.6s ease-in-out both;
}

@keyframes modal-enter {
  from {
    opacity: 0;
    transform: translateY(50%);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

JS Animations

const modal = document.querySelector('.modal');

modal.animate([
  {
    opacity: 0,
    transform: 'translateY(50%)',
  },
  {
    opacity: 1,
    transform: 'translateY(0)',
  }
], {
  duration: 600,
  easing: 'ease-in-out',
  direction: 'both'
});

JS Animations (dynamic)

const box = document.querySelector('.box');

document.addEventListener('mousemove', (event) => {
  requestAnimationFrame(() => {
    box.style.transform = `
      translateX(${event.pageX})
      translateY(${event.pageY})
    `;
  });
});

CSS Variables

RxJS Observables

--my-color: blue;
:root {

}
.lagoon {

}
color: var(--my-color);
const docStyle = document.documentElement.style;


docStyle.setProperty('--color-primary', 'blue')


docStyle.getPropertyValue('--color-primary');
// => 'blue'


docStyle.removeProperty('--color-primary');

Sets the custom property

CSS Variables (Custom Properties) + JS

Gets the custom property

Removes the custom property

Big surprise

PREPROCESSOR SUPPORT

CSS Variables

RxJS Observables

[

]

Arrays

STREAMS

An Array

Asynchronous,

Immutable,

Subscribable.

An Observable stream is

that is

and



Rx.Observable.from([1, 2, 3, 4, 5]);


Rx.Observable.fromPromise(fetch(url));


Rx.Observable.fromEvent(document, 'mousemove');


Rx.Observable.fromEventPattern((h) =>
  hammerFoo.on('pan', h));

Creating an Observable

Creates Observable from iterable (array, set, etc.)

Creates Observable from promise

Creates Observable from DOM node and event name

Creates Observable from passing handler to event pattern

const ball = document.querySelector('#ball');


const hBall = new Hammer(ball);


const pan$ = Rx.Observable.fromEventPattern((handler) =>
  hBall.on('pan', handler));

Example: HAMMER.JS

const logger = ticker$.subscribe(
  

  (value) => { console.log(value); },


  (error) => { console.error(e); },


  () => {
    console.log('You have reached the end of time');
  }
);

Subscribing to an Observable

executes on every pushed (next) value

executes on error

executes on completion

event$.subscribe((event) =>
  doSomething(event));

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

Operators!

think of RxJS as
lodash for Observables

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

1

2

3

5

6

8

1

1

1

2

1

2

.mergeAll()

Are you thinking
what I'm thinking?

Before we go to the next slide, I want you to take a moment and forget everything you've read about CSS-in-JS.

Keep an open mind.

What if we modeled

Observable events

CSS Variables?

As

What if we could write

JavaScript in CSS?

Functional Reactive Animations

With CSS Variables &

RxJS Observables

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.

Rx.Subject()

CSS Variables

mouseMove$

scroll$

tap$

swipe$

timer$

...etc.

(Observers)

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

RXCSS



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


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


style$.subscribe(...);

npm install rxcss --save



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


.ball {
  transform:
    translateX(var(--mouse-x))
    translateY(var(--mouse-y));
}

โ€“ scroll$ Observable

  1. get target.scrollTop
  2. divide by threshold
  3. var(--scroll, 0)
  • image opacity:
    calc(1 - var(--scroll))
  • image scale:
    calc(1 + 0.5 * var(--scroll))

Why use
CSS Variables?

No excessive DOM manipulation ๐Ÿ‘

Easily debuggable

DOM node independent

Theming

Progressive enhancement

calc() FTW

They work in SVG!

What's next?

  • Constraint Layouts
  • Interruptible FLIP transitions
  • Physics modeling
  • Complex animations
  • Choreography
  • Configurability
  • Canvas, WebGL
  • Much, much more!

What if?

Thank you JSConf Iceland!

Reactive Animations With CSS Variables

By David Khourshid

Reactive Animations With CSS Variables

  • 10,250