-
Notifications
You must be signed in to change notification settings - Fork 671
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] Custom effects #6861
Comments
That all makes sense to me. Thanks for picking this up. |
Is there a related CSS value interpolation API? |
I hadn't considered that, but it's something that's worth exploring alongside this proposal. Would you have something to propose? |
I'm not super experienced in interpolation and/or making proposals for the csswg, I wouldn't mind though, however, I'm mostlikely going to need some help. cc @mattgperry |
Hey @graouts This came out of a discussion between @okikio and myself about the limitations custom effects leaves us with. I'm super excited about custom effects but I think there's a remaining black hole when talking about interpolating complex values like My first thought was having const mixColors = new Interpolation("#f00", "rgba(255,255,255,0.5)")
document.timeline.animate(p => {
arbitraryElement.innerHTML = mixColors(circIn(p))
}) Ideally the interpolator would support unclamped progress values so we could support overshoot easing. Supported interpolators that would be helpful:
All of these (and more) are already leveraged within browsers, I think direct access would pair very well with |
In addition to what @mattgperry posted something like this would also be awesome, CSS.mix("50٪ by ease", "red", "blue") // purple (in rgb format)
CSS.mix("50٪ by ease", "currentColor", "blue", document.querySelector(".red-text")) // purple (in rgb format)
// Easing is linear by default
// Percentages function like they would on normal CSS
CSS.mix(0.5, "100%", "200", document.querySelector(".red-text"), "width") // 150px The API would very similar to the currently discussed CSS counter part |
I think that the timing with respect to other effects needs to be clear either way. I think having a set of post-animation update callbacks is probably simpler to be honest. The other thing that may be cleaner about an update callback is that we may be able to skip it if the effect easing resulted in no change. E.g. canvas.animate({}, {duration: 1000, easing: steps(10)}).addEventListener('update', (localTime) => {
// Only needs to be called 10 times?
});
I agree that conceptually it makes sense to not have a target, though it could be a nice feature if we could skip custom effects if the target (e.g. the canvas being drawn to) was not in view or at least if the target is detached. |
I think @bramus also had a good use case where an update callback would be much simpler ergonomically than needing to create a separate animation. |
Thanks for pointing me to this thread, @flackr. I’m currently building a demo that uses a scroll-driven animation on an One thing I found missing while building this is having Right now, I rely on the input’s Using a I believe a |
Another situation: Part of the The implementation of that function itself pretty nasty as it relies on const trackProgress = (animation, cb, precision = 5) => {
const updateValue = () => {
let newProgress = animation.effect.getComputedTiming().progress * 1;
if (animation.playState === 'finished') newProgress = 1;
newProgress = Math.max(0.0, Math.min(1.0, newProgress)).toFixed(precision);
// … (pass progress into cb)
requestAnimationFrame(updateValue);
};
requestAnimationFrame(updateValue);
}; With -let newProgress = animation.effect.getComputedTiming().progress * 1;
-if (animation.playState === 'finished') newProgress = 1;
-newProgress = Math.max(0.0, Math.min(1.0, newProgress)).toFixed(precision);
+let newProgress = animation.progress; Again, an |
TBH, I don't see much difference between the 2 methods, except for the effect's
And regarding using the target to play/pause:
I think the playback management would better be solved using In @bramus's example the main issue not being able to get the the pseudo-element, unless you create grab the animation set by CSS and take the const timeline = $input.getAnimations()[0].timeline;
const effect = new CustomEffect(progress => { … }, {
fill: 'both',
direction: 'reverse'
});
const animation = new Animation(effect, timeline);
animation.rangeStart = 'contain 0%';
animation.rangeEnd = 'contain 100%';
animation.play(); We could also decide that
Regarding sync between other effects, I suppose this is something that should probably be left for nesting effects via So to summarize, for me this boils down to ergonomics. So naming some use-cases to make this more concrete:
There are basically 2 goals here:
In most of the use-cases I personally encounter in my work it's mostly more straightforward to use the |
When I say skip, this is a (up to now implementation) detail that affects main frame generations. There are many sites with a lot of complex content that is not yet scrolled on screen (or has been scrolled off screen). Conceptually all animations tick all of the time, but since the offscreen animations do not present any visual change chrome can skip generating frames if all of the updating content is offscreen. When a main frame is generated all animations are updated to the current time such that all styles are as if the animation has been active. With a custom effect callback, we could not do this sort of an optimization unless it was part of the API. This skipping isn't about playback management, when we skip animations they conceptually continue playing, this is about optimizing frame generation. I also don't think this is something that developers usually do unless, as you alluded to, they intentionally want to pause playback. Most developers I've talked to don't manage all of the content on the site. With CSS animations it didn't need to be in spec since a developer wouldn't be able to tell as if they request an animation frame the animations are updated then. However, we wouldn't know for custom effects what they change so it would need to be specified if they are skipped in certain circumstances.
And, having a rooted node to get the animations from for getAnimations |
Thanks for in-depth explanation! I get it now. But it seems that the only difference between the two is having a target, right? My main concern here is that creating a dummy
Yes, I guess working on a large scale, generic tool forced us to take less chances and manage these more strictly.
Right! So adding the |
And that if you want to simultaneously animate some css properties you'd generally end up creating multiple animations. My thinking was that by having a hook on regular animations you could run script driven animations in tandem with the css property update rather than having to set the two up separately. I imagine developers may often animate a custom property which drives the logic of the custom effect.
Yeah, the optimization part might end up being a bit non-trivial to implement, e.g. the UA wouldn't necessarily know whether the developer would animate the position of the element, however, we should spec it to be able to skip calling the the animation update function in cases like |
My eventual goal is to ditch the
If there is a |
I could be wrong here, but from my experience these cases are quite rare. So I wouldn't mind creating separate effects for those, of course, considering I can sync them together using Groups/Timelines.
Using this method isn't really called for if you're just animating CSS properties, unless you're doing hacks that aren't really possible today, like mixing it with Transitions, for delayed effects or velocity-based effects, etc.
OK, I'm mainly coming from an Author POV, and not an expert on the implementation side. So, nothing I can add to that point. I can only say that the shape of the progress event API with an empty effect seems weird from my side.
Of course, 100%.
Again, you guys know the impl. side.
Yeah, impl. wise. And if this resolves in a superior experience for the users then great. Just hoping we can also get a solid-looking API for authors on the way. |
I think a better explanation of how I see it is that if we define our MVP for this feature, same as we defined a simple fade-in animation on entry for scroll-animations, it would be to just start a loop with |
I would like to revive work on custom effects, an idea currently unofficially drafted in Web Animations Level 2. I have filed a patch for WebKit to support the
CustomEffect
interface as well as a newdocument.timeline.animate()
method. The motivation is to bridge the gap between the poorly-named and rudimentaryrequestAnimationFrame()
and Web Animations allowing authors to harness the full power of the Web Animations model such that scripted animations may be paused, resumed, seeked, etc.Some example usage:
This code is equivalent to the more idiomatic:
The idea here is that
document.timeline.animate()
should be toCustomEffect
whatelement.animate()
is toKeyframeEffect
.The section on custom effects in the current level 2 spec starts with this issue:
I personally think that a dedicated
CustomEffect
interface is a simple and purposeful way to specify an animation where its application is performed by script. I expect that it is simpler to specify how this specific class of effects would work rather than trying to add anonupdate
callback that would apply to keyframe effects as well.You'll also notice the lack of a
target
forCustomEffect
. I believe that it should not be necessary to specify a target for a custom effect since it may not target a single node, or even a node at all, but rather a JS object controlling the rendering of a scene using<canvas>
APIs.That being said, I am very open to all feedback to work towards exposing callback-based animations in Web Animations. All work conducted in WebKit is behind an off-by-default experimental feature flag and we have no intention to expose this to the Web until we have consensus on the way forward.
Cc @birtles @flackr @stephenmcgruer @kevers-google @majido @smfr @grorg @hober @ogerchikov @BorisChiou @hiikezoe
The text was updated successfully, but these errors were encountered: