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
Reactive Animations with CSS Variables
By David Khourshid
Reactive Animations with CSS Variables
CSSDevConf San Antonio 2016
- 70,266