1 of 35

UIWorker and stuff

extensibility + speed

2 of 35

Problems

It’s tough to write performant, cross-UA code.

Can’t synchronize with threaded effects.

Can we make a pit of success?

3 of 35

Motivating Examples

Dragging stuff

4 of 35

Motivating Examples

Parallaxing stuff

T

5 of 35

Motivating Example

(Lots of others)

  • hidey bars
  • snap points
  • pull-to-refresh
  • position:sticky
  • ...

6 of 35

Problem

Current choices

  • Extensibility
  • Cross-UA Performance

7 of 35

Problem

Current choices

  • Extensibility
  • Cross-UA Performance

Can we have both?

8 of 35

Sorta!

9 of 35

Stratospheric View

Here’s your page

10 of 35

Stratospheric View

Here’s your page

These are draw ops

11 of 35

Stratospheric View

These are transformed

This is a transform

12 of 35

Stratospheric View

Can I mutate this in sync with scroll? touch?

13 of 35

Yup.

14 of 35

Stratospheric View

main thread stuff

compositor thread stuff

15 of 35

Stratospheric View

stable identifier

16 of 35

Stratospheric View

// some javascript to muck with

17 of 35

Strawperson: UIWorker

// On main thread.

worker = new UIWorker('ui.js');

worker.postMessage(

spinner.bindAnimatedProperty('transform'));

18 of 35

Strawperson: UIWorker

// On compositor thread.

var token = null;

onmessage = function(e) {

token = e.data;

var context = new RAFContext([token]);

requestAnimationFrame(context, tick);

};

19 of 35

Strawperson: UIWorker

function tick(context) {

var seconds = context.timestamp / 1000.0;

var matrix = context.getMatrix(token);

matrix.m14 =

200.0 + 100.0 * Math.sin(seconds);

context.setMatrix(token, matrix);

requestAnimationFrame(context, tick);

}

20 of 35

Demo!

21 of 35

But that API is pretty gross

  • Tokens and context aren’t “webby”.
  • Doesn’t handle input.

What might a nice API look like?

22 of 35

Idea 1: Squeeze input into UIWorker

// main

var t1 = elem.bind(“touchmove”);

var t2 = elem.bind(“beforescroll”);

23 of 35

Idea 2: AnimationProxy

// main

var proxy = elem.getProxy(“transform”);

// compositor

proxy.transform = thingy;

24 of 35

Idea 3: Scene Graph

What if we had a DAG?

25 of 35

Idea 3: Scene Graph

What if we had a DAG?

wall clock

a.touch

b.scrollTop

c.xform

e.xform

d.xform

26 of 35

Idea 3: Scene Graph

Permits minimal updates.

wall clock

a.touch

b.scrollTop

c.xform

e.xform

d.xform

27 of 35

Idea 3: Scene Graph

Say touch and time have changed..

wall clock

a.touch

b.scrollTop

c.xform

e.xform

d.xform

28 of 35

Idea 3: Scene Graph

Cleaning step 1

wall clock

a.touch

b.scrollTop

c.xform

e.xform

d.xform

29 of 35

Idea 3: Scene Graph

Cleaning step 2

wall clock

a.touch

b.scrollTop

c.xform

e.xform

d.xform

30 of 35

Idea 3: Scene Graph

// Parallax. (Warning: magical, illegal JS to follow.)

element.style.transform = pureFunction(top) {

return CSSTransform(“translateY(“ + (top * 0.8) + “px)”);

}.fancyBind(other, “scrollTop”);

31 of 35

Idea 3: Scene Graph

// This lets us reuse our unbound functions.

var parallax = pureFunction(top, rate) {

return CSSTransform(“translateY(“ + (top * rate) + “px)”);

}

back_a_bit.style.transform =

parallax.fancyBind(other, “scrollTop”, 0.8);

way_back.style.transform =

parallax.fancyBind(other, “scrollTop”, 0.2);

32 of 35

Idea 3: Scene Graph

// Before scroll.

element.beforescroll = pureFunction(scrollState) {

scrollState.delta *= 0.5;

return scrollState;

}

// Before scroll, custom bubbling.

element.beforescroll = pureFunction(scrollState) {

...

}.fancyBind(scroller, “beforescroll”);

33 of 35

Idea 3: Scene Graph

// Traditional animation, bound to wall clock.

element.style.opacity =

pureFunction(timestamp){...}.fancyBind(time);

element.style.transform = pureFunction(e1, e2) {

// Do some stuff while the mouse is over.

}.fancyBind(element, “touchmove”, window, “touchmove”);

34 of 35

Next Steps

  • Implement behind a flag.
  • Attempt to build tough test cases.

35 of 35

Further Reading