The (Finite) State of
Reactive Animations
@davidkpiano · RxJS Live 2019
📞
🔙
🙏🤐
🅰️⚓️
🅰️🏋️♀️
🔭
🅰️
⚽️🎾🎱
Callbacks
Promises
Async-Await
Observables
Time is the most neglected variable.
A "reactive animation" is one involving
discrete changes, due to events.
Discrete
Continuous
By allowing programmers to
express the "what" of an interactive
animation, one can hope to then
automate the "how" of its presentation.
RxJS Core Team MemberWork at GoogleGoogle Developer ExpertAngular Developer
- Made a PR once (rejected)
- Work at Microsoft
- Googles things to pretend I'm an expert developer
- React Developer
@davidkpiano 🎹
sorry conference wifi
loading...
I warned you there would be lots of demos
const animFrame$ =
interval(0, animationFrameScheduler);
const smoothMove$ = animFrame$.pipe(
withLatestFrom(move$, (frame, move) => move)
);
Animation Frames
In RxJS
.pipe(withLatestFrom(ball$,
(frame, ball) => ball))
animFrame$
const lerp = (current, next) => {
const dx = next.x - current.x;
const dy = next.y - current.y;
const ratio = 0.1;
return {
x: current.x + dx * ratio;
y: current.y + dy * ratio;
};
};
200
300
deltaX = 300 - 200 = 100
100 * 0.1 = 10
200 + 10 = 210
210
300
100
Linear Interpolation (LERP)
Linear Interpolation (LERP)
1.0
0.5
0.2
ball$.pipe(
scan((a, b) => lerp(a, b)))
1
1
1
2
2
2
3
3
1
1
1
1.5
1.75
2
2.5
2.75
const lerp = // ...
const animFrame$ = // ...
const smoothMove$ = animFrame$.pipe(
withLatestFrom(move$, (frame, move) => move)
scan(lerp));
smoothMove$.subscribe(values => {
// render values
});
oh my 🐐
STREAM
VALUES
CUSTOM PROPERTIES
REACTIVE STYLES
Everything is a stream
...is not enough
Animations are transitions
between states
due to events
Finite state machines
and statecharts
Finite state machines
-
have one initial state
-
a finite number of states
-
a finite number of events
-
a mapping of state transitions
triggered by events -
a finite number of final states
Idle
Pending
Rejected
Fulfilled
Fetch
Resolve
reject
Fulfilled
Rejected
Idle
Searching...
SEARCH
Success
Failure
RESOLVE
REJECT
SEARCH
SEARCH
SEARCH
🚫
SEARCH?
Define transitions between
states & events
function searchReducer(state = 'idle', event) {
switch (state) {
case 'idle':
switch (event.type) {
case 'SEARCH':
return 'searching';
default:
return state;
}
case 'searching':
switch (event.type) {
case 'RESOLVE':
return 'success';
case 'REJECT':
return 'failure';
default:
return state;
}
case 'success':
switch (event.type) {
case 'SEARCH':
return 'searching';
default:
return state;
}
case 'failure':
switch (event.type) {
case 'SEARCH':
return 'searching';
default:
return state;
}
default:
return state;
}
}
FSMs with switch/case
const machine = {
initial: 'idle',
states: {
idle: {
on: { SEARCH: 'searching' }
},
searching: {
on: {
RESOLVE: 'success',
REJECT: 'failure',
SEARCH: 'searching'
}
},
success: {
on: { SEARCH: 'searching' }
},
failure: {
on: { SEARCH: 'searching' }
}
}
};
Define transitions between
states & events
FSMs with
object mapping
const transition = (state, event) => {
return machine
.states[state] // current state
.on[event] // next state
|| state; // or same state
}
merge(...)
scan(reducer, initial)
initial
next
pipe( )
import { fromEvent, fromEventPattern, merge } from 'rxjs';
import { scan } from 'rxjs/operators';
const pan$ = fromEventPattern(/* ... */);
const esc$ = fromEvent(/* ... */);
const other$ = fromEventPattern(/* ... */);
const event$ = merge(pan$, esc$, other$);
const initialState = {
// ...
}
const state$ = event$.pipe(
scan((state, event) => {
// state machine logic here!
}, initialState)
);
state$.subscribe(state => {
// render state
});
npm install xstate
const machine = {
initial: 'idle',
states: {
idle: {
on: { SEARCH: 'searching' }
},
searching: {
on: {
RESOLVE: 'success',
REJECT: 'failure',
SEARCH: 'searching'
}
},
success: {
on: { SEARCH: 'searching' }
},
failure: {
on: { SEARCH: 'searching' }
}
}
};
import { Machine } from 'xstate';
const machine = Machine({
initial: 'idle',
states: {
idle: {
on: { SEARCH: 'searching' }
},
searching: {
on: {
RESOLVE: 'success',
REJECT: 'failure',
SEARCH: 'searching'
}
},
success: {
on: { SEARCH: 'searching' }
},
failure: {
on: { SEARCH: 'searching' }
}
}
});
import { Machine } from 'xstate';
const machine = Machine({
// ...
});
const searchingState = machine
.transition('idle', 'searching');
searchingState.value;
// => 'searching'
import { Machine, interpret } from 'xstate';
const machine = Machine({
// ...
});
const service = interpret(machine)
.onTransition(state => console.log(state))
.start();
service.send('SEARCH');
// State {
// value: 'searching',
// ...
// }
const weekMachine = Machine({
// ...
states: {
dragging: {
invoke: [
{ src: mouseMove$ },
{ src: mouseUp$ }
],
on: {
mousemove: {
actions: drag
},
mouseup: [
{ target: "selected", cond: isNotTrashElement },
{ target: "disposed", actions: showDaysRemoved }
]
}
},
selected: {/* ... */},
disposed: {/* ... */}
}
});
dragging
selected
disposed
entry: subscribe mouseMove$
entry: subscribe mouseUp$
exit: unsubscribe mouseMove$
exit: unsubscribe mouseUp$
mouseup
mouseup
MOUSEMOVE
mouseMove$
mouseUp$
dragging
selected
disposed
grabbing
mouseDown$
import { interpret } from 'xstate';
const weekService = interpret(weekMachine)
.start(); // start interpreting the machine
// services are observables!
weekService.subscribe(state => {
console.log(state);
});
...etc.
Think in states,
not just events.
Advantages
of using statecharts
- Visualized modeling
- Precise diagrams
- Automatic code generation
- Comprehensive test coverage
- Accommodation of late-breaking requirements changes
The future of state
is nothing new.
The world of statecharts
Spectrum community
Make your code do more.
Thank you RxJS Live!
@davidkpiano · RxJS Live 2019
The Finite State of Reactive Animations
By David Khourshid
The Finite State of Reactive Animations
- 5,042