Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[web-animations-2] Idea: Custom Effects #2062

Open
birtles opened this issue Dec 5, 2017 · 6 comments
Open

[web-animations-2] Idea: Custom Effects #2062

birtles opened this issue Dec 5, 2017 · 6 comments
Labels
web-animations-2 Current Work

Comments

@birtles
Copy link
Contributor

birtles commented Dec 5, 2017

From @notoriousb1t on July 27, 2016 15:49

I think that animation libraries would benefit from a simple way to create custom effects. Here is what I think it might look like:

var effectOptions = {
    createContext() {
        return {
            startValue: 0,
            endValue: 1000,
            currentValue: 0
        };
    }
    updateContext(ctx, timing) {
        ctx.currentValue = ((endValue - startValue) * timing.computedOffset) + startValue;
    }
    render(ctx, target) {
        target.innerHTML = ctx.currentValue;
    }
};

var timingOptions = { 
    duration: 1000, 
    easing: 'ease-in' 
};

var target = document.createElement('div');
var webEffect = new WebEffect(target, effectOptions, timingOptions);
var animation = new Animation(webEffect, document.timeline);
animation.play();

Hopefully I am not butchering Web IDL, but the ctx argument would be

interface WebEffectTimingContext {
    readonly attribute double offset;
    readonly attribute double computedOffset;
    readonly attribute double playbackRate;
    readonly attribute AnimationPlayState playState;
};

and the effectOptions variable would be

interface WebEffectOptions {
    Dictionary createContext();
    void updateContext(Dictionary context, WebEffectTimingContext timing);
    void render(Dictionary context, object target);
};

I would think createContext and render should occur in the main thread and updateContext would occur outside of the main thread. If that was the case, the context object would be passed by value the same way postMessage works with a Web Worker.

I think exposing an interface like this would make it easier to build out custom effects while still benefiting from the different timing functions and animation controls. If allowed with Group or Sequence effects, I think it would make it easy to choreograph events between canvas, unsupported svg properties, and other things.

Would something like this be possible?

Copied from original issue: w3c/web-animations#162

@birtles birtles added the web-animations-2 Current Work label Dec 5, 2017
@birtles
Copy link
Contributor Author

birtles commented Dec 5, 2017

Custom effects are somewhat defined in Web Animations level 2. However, the current thinking is that an onsample callback would be more useful since it could run alongside other kinds of effects.

I'm curious about your proposal, however. How does running updateContext on a separate thread help?

@birtles
Copy link
Contributor Author

birtles commented Dec 5, 2017

From @notoriousb1t on July 28, 2016 3:5

I did not think to check the level 2. Honestly, I'm still trying to get a handle on how the language is written in these specs.

I think having it on a separate thread would enforce a clear separation between model updates and view updates and allow the browser to ignore long running update cycles if necessary. By only providing timing information outside of the main thread, it would force all updates based on this information to occur outside of rendering and effectively shrink the amount of work done each time render is called. I think having the update logic execute in a separate context might allow browser vendors to perform additional optimizations on the code itself.

I'm making a lot of assumptions though on how the internals of browsers work. I modeled that on what some game engines do. I'm not sure it really applies or that my assumptions are based in reality.

I can see how having onsample would provide good secondary effects, but it might be weird to use by itself. That is, creating an empty KeyframeEffect to render to Canvas or audio feels a little tacked on. I think it might be better to have the lower level construct that could in theory be used to create a completely different KeyframeEffect if desired. I'm not saying that to be hyperbolic. I think it would provide a lot of extensibility.

@birtles
Copy link
Contributor Author

birtles commented Dec 5, 2017

From @Martin-Pitt on August 2, 2016 21:16

Any particular reason why the API can't be like this instead?

var node = new JavaScriptEffect(function(timeFraction) {
  // When animating, timeFraction is from 0.0 to 1.0
  // When null, WAAPI is telling us to 'cleanup', e.g. fill: 'none' = unset element style for example when animation is complete
}, timingOptions);

var animation = new Animation(node, document.timeline);
animation.play();

I always felt this was a clean and direct way to go about things, kind of like a Promise. It makes no assumptions. You could be rendering to the Web Audio API or via Web Bluetooth to some custom hardware – animate the world!

@birtles
Copy link
Contributor Author

birtles commented Dec 5, 2017

From @notoriousb1t on August 3, 2016 22:1

I think it is better to pass options to future proof the API a little. By passing a function in directly, there are no opportunities to add additional hooks. Also, you might need to know what direction and other timing information such as the playback rate to render your non-animation correctly, so I think that passing the delta isn't going to be sufficient.

If there isn't a desire for the browser to do render optimization based on model changes, something like this would be simpler and provide opportunity for additional hooks:

const targetEl = document.createElement('div');
const effectOptions = {
    onsample(target, timing) {
        // renders "offset: x, rate: y"
        target.innerHTML = `offset: ${timing.computedOffset}. rate: ${timing.playbackRate}`;
    }
};
const timingOptions = { 
    duration: 1000, 
    easing: 'ease-in' 
};

const effect = new WebEffect(targetEl, effectOptions, timingOptions);
const animation = new Animation(effect, document.timeline);
animation.play();

That sort of falls in line with using onsample in a KeyframeEffect though.

@birtles
Copy link
Contributor Author

birtles commented Dec 5, 2017

From @Martin-Pitt on August 11, 2016 12:26

Do agree that in terms of flexibility/compat passing an options object makes more sense.

However still think we can go a level lower by not having the element as it is still implying a dependency with the DOM.

I noticed that computed timing options have a progress which is equivalent to the fraction from onsample, so this object does make a lot more sense and gives all the info needed.

Perhaps this then?

const effectOptions = {
    onsample(timing) {
        let fraction = timing.progress;
    }
};
const timingOptions = {
    duration: 1250,
    easing: 'ease-in'
};
const effect = new WebEffect(effectOptions, timingOptions);
const animation = new Animation(effect, document.timeline);
animation.play();

@birtles
Copy link
Contributor Author

birtles commented Dec 5, 2017

From @notoriousb1t on August 13, 2016 2:36

I think that would work well as a low level API. It removes a lot of the math involved in timing, maintaining a RAF loop, and it is super straightforward.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
web-animations-2 Current Work
Projects
None yet
Development

No branches or pull requests

1 participant