Re: [csswg-drafts] [web-animations-1] Generalized Time Values and Timelines

Here are two examples that showcase how the above AnimationInput construct can be used to create interactive animated effects.

 
## Gesture Driven Image Resizer

In this example we are creating a basic image scale, rotate animation that is linked to corresponding multi-touch gestures using GestureInput.

```html
<img id='target'>

<script>

await CSS.animationWorklet.addModule('scare-rotate-animator.js');
const target = document.getElementById('target');

// Using individual transform properties
const rotateEffect = new KeyFrameEffect(
   target, {rotate: ['rotate(0)', 'rotate(360deg)']}, {duration: 100, fill: 'both' }
);
const scaleEffect = new KeyFrameEffect(
  target, {scale: [0, 100]}, {duration: 100, fill: 'both' }
);

// Note the worklet animation has no timeline but two inputs.
const animation = new WorkletAnimation(
  'image-manipulator', [rotateEffect, scaleEffect],  null,  
  {inputs: {'gesture': new GestureInput(target)}}
);
animation.play();
</script>
```

```js
registerAnimator('image-manipulator', class {
  constructor(options) {
    // Always listen to gestures.
    this.options.inputs.gesture.listen(this);
  }
  animate(currentTime, inputValues, effects) {
    // Note that currentTime is undefined and unused.

    // Get current gesture value and update rotation and scale effects accordingly.
    const {rotate, scale} = inputValues.gesture;
    effect.children[0].localTime = rotate / 100;
    effect.children[1].localTime = Math.min(scale, 100);
  }
});
```

## Mixing Scroll and Time inputs
This example recreates [twitter hidey-bar effect](http://googlechromelabs.github.io/houdini-samples/animation-worklet/twitter-hidey-bar/) that uses two animation input Scroll and Time.
The animation is only attached to time input when it is actively animating. So the effect is 


```html

<div id='scrollingContainer'>
  <div id='header'>Hidey-bar header</div>
  <div>Scrolling content</div>
</div>

<script>
await CSS.animationWorklet.addModule('hidey-bar-animator.js');
const $header = document.getElementById('header');
const $scroller = document.getElementById('scrollingContainer');

const headerHeight = .clientHeight;
const scrollRange = $scroller.scrollHeight - $scroller.clientHeight;
const effect = new KeyFrameEffect($header,
  [{transform: 'translateY(0)'}, {transform: `translateY(${scrollRange}px)`}],
  {duration: scrollRange, fill: 'both' });

const scrollInput = new ScrollInput($scroller);
const timeInput = document.timeline;

// Note the worklet animation has no timeline but two inputs.
const animation = new WorkletAnimation('hidey-bar', effect,  null,  {
  inputs: {'scroll': scrollInput, 'time': timeInput}
  data : {'headeHeight': headerHeight}
});
animation.play();
</script>
```

```js
const MIN_HIDE_AMOUNT = -50;
const MAX_HIDE_AMOUNT = 0;
const HIDE_SPEED = 0.35; // hide animation speed in pixel per millisecond

function clamp(value, min, max) {
  return Math.max(min, Math.min(max, value));
}

function sign(value) {
  return value < 0 ? -1 : 1;
}

registerAnimator('hidey-bar', class  {
  constructor(options) {
    this.scrollInput_ = options.inputs.scroll;
    this.timeInput_ = options.inputs.time;
    // Always listen to scroll changes.
    this.scrollInput_.listen(this);

    this.headerHeight_ = options.data.headerHeight;

    this.hideAmount_ = 0;
    this.lastY_ = -1;
    this.lastTime_ = 0;
    this.lastPhase_ = 'idle';
    this.lastHideSpeed_ = 1;
  }

  animate(currentTime, inputValues, effect) {
    // Note that currentTime is undefined and unused.

    // Scroll value is in {x, y, phase} form.
    const {x, y, phase} = inputValues.scroll;
    const time = inputValues.time;

    var currentMinHideAmount = clamp(this.headerHeight_ - y,  MIN_HIDE_AMOUNT, 0);
    
    if (phase != 'idle') {
      // When actively scrolling hide in keep with scroll amount.
      var scrollDelta = this.lastY_ - y;
      this.lastHideSpeed_ = HIDE_SPEED * sign(scrollDelta) * scrollDelta;
      this.hideAmount_ += scrollDelta;
    } else {
      // When the scroll goes idle we animate based on time
      // determine if we need to keep sliding the header.
      bool isCompleted = this.hideAmount_ == currentMinHideAmount || this.hideAmount_ == MAX_HIDE_AMOUNT;
      bool isStarting = this.lastPhase_ == 'active';

      if (isCompleted) {
        this.timeInput_.unlisten(this);
      } else {
        if (isStarting)
          this.timeInput_.listen(this);

        // Continue hide/show animation following the direction and speed of last scroll.
        var timeDelta = time - this.lastTime_;
        this.hideAmount_ +=  lastHideSpeed_ * timeDelta;
      }
    }

    this.hideAmount_ = clamp(this.hideAmount_, currentMinHideAmount, MAX_HIDE_AMOUNT);
    // Position the hidey bar relative to the current scroll amount.
    effect.localTime = y + this.hideAmount_;

    this.lastY_ = y;
    this.lastTime_ = time;
    this.lastPhase_ = phase;
  }
});
```






-- 
GitHub Notification of comment by majido
Please view or discuss this issue at https://github.com/w3c/csswg-drafts/issues/2493#issuecomment-422153926 using your GitHub account

Received on Monday, 17 September 2018 20:12:04 UTC