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