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
Browser Support

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
- get target.scrollTop
- divide by threshold
- 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
- 9,388