Reactive Animations

CSS Variables

​CSSDevConf San Antonio
David Khourshid @davidkpiano

Continuous

Discrete

&

Declarative

Imperative

vs.

Immutable

Mutable

vs.

&

Functional Programming

Constraints

What if?

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;

What are
Reactive Animations?

Events

Discrete Changes

STREAM

VALUES

CUSTOM PROPERTIES

REACTIVE STYLES

Why 

?

CSS Variables

CUSTOM PROPERTIES

CSS Variables

CUSTOM PROPERTIES

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

SPECIFICITY

INHERITANCE

CSS Variables

CUSTOM PROPERTIES

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

CSS Variables

BROWSER SUPPORT

They're working on it!

CSS Variables

PREPROCESSOR SUPPORT

CSS Variables

CSS Variables

JavaScript Events

const docStyle = document.documentElement.style;
const someElement = document.querySelector(...);

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

JAVASCRIPT

  • It just works™
  • Detached element?
  • CSS overrides (media queries)?
  • Overwritten transforms?
  • Maintenance?
  • Performance?
const docStyle = document.documentElement.style;

document.addEventListener('mousemove', (e) => {
  docStyle.setProperty('--mouse-x', e.clientX);
  docStyle.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((handler) =>
    hammerFoo.on('pan', handler));
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
);

Performance matters.

const tick$ = Rx.Observable
  .interval(1, Rx.Scheduler.animationFrame);

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))  
.throttle(1000)
.scan((a, b) => a + b, 0)

1

2

3

5

6

8

1

1

1

2

1

2

Think of RxJS as
Lodash for observables.

If you can read this, the wifi sucks

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 =

Functional Reactive Animation

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.

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(({ x, y }) => ({ x, y }));


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


style$.subscribe(...);

RxCSS

RxCSS

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

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

– scroll$ Observable

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

const scroll$ = Rx.Observable
  .fromEvent(content, 'scroll')
  .map(({target}) =>
    2 * target.scrollTop / target.clientHeight);

RxCSS({
  scroll: scroll$,
});
.content {
  opacity: calc(1 - var(--scroll));
  transform: scale(calc(1 + var(--scroll) / 3));
}

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?

Why animate with

CSS Variables?

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

});

Why animate with

CSS Variables?

What if?

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

Thank you CSSDevConf!

@davidkpiano