Skip to content

[css-easing-2] - Custom Timing Functions in JavaScript #1012

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

Open
notoriousb1t opened this issue Feb 8, 2017 · 6 comments
Open

[css-easing-2] - Custom Timing Functions in JavaScript #1012

notoriousb1t opened this issue Feb 8, 2017 · 6 comments
Labels
css-easing-2 Current Work

Comments

@notoriousb1t
Copy link

This is in reference to this issue on the webanimations spec: w3c/web-animations#169

I think that it would be very useful to be able to define timing functions in JavaScript. Although, I propose this mainly for the sake of WAAPI, I think that it should be possible to define a way to register custom timing functions for CSS as well

Custom Timing Function in WAAPI

element.animate({
  /* ... */
  easing: function(x) {
    if (x < 0.3) {
      return x * x;
    }
    if (x < 0.6) {
      return x * x * x;
    }
    if (x < 0.8) {
      return x * x + 0.1;
    }
    return x * x * x - 0.01;
  }
})

In the above example, x is a number normally between 0 and 1 that describes the time of the animation 0% to 100%. The return should be an integer between 0 and 1 that describes the progression of the animation. (for the sake of supporting elastic functions and certain cubic bezier functions, it should support overshooting on either side)

Proposed timing function registration

document.registerTimingFunction('rough', function(x) {
    if (x < 0.3) {
      return x * x;
    }
    if (x < 0.6) {
      return x * x * x;
    }
    if (x < 0.8) {
      return x * x + 0.1;
    }
    return x * x * x - 0.01;
  });

Usage in CSS

.rough.animated {
  animation-timing-function: rough;
}

In the example, I have registered a custom timing function somewhere in JavaScript named 'rough'. I think that if the browser has not had a function registered by the time it renders, it should ignore the value until it does and fallback to its inherited timing function.

Since this may need to be sampled at any time by the browser for performance reasons, I think it makes sense that best practice dictate that this function should be stateless.

Adding this functionality would allow animation libraries a path forward for timing functions that are hard to describe otherwise, and provide an opportunity for new timing functions to be invented and to be polyfilled.

Here are some examples of how this signature could be used for future-proofing/feature-gap filling:

https://github.com/just-animate/just-curves

@birtles birtles changed the title [css-timing-1] - Custom Timing Functions in JavaScript [css-timing-2] - Custom Timing Functions in JavaScript Feb 8, 2017
@birtles birtles added the css-easing-2 Current Work label Feb 8, 2017
@vidhill
Copy link

vidhill commented Feb 11, 2017

I made a somewhat similar suggestion before:
-for reference
w3c/css-houdini-drafts#269

@notoriousb1t
Copy link
Author

@vidhill I looked over your proposal (and followed some of the links in it) and have a few thoughts of how to tie some of your ideas into this proposal.

In JavaScript

// registers the timing-function for use in CSS
window.registerTimingFunction('frames', (frameCount) => {
    // allows for pre-calculation based on input
    const co = 1 / (frameCount - 1);

    // returns the function to sample the curve
    return (offset) => {
        const out = floor(offset * frameCount) * co;
        return offset >= 0 && out < 0 ? 0 : offset <= 1 && out > 1 ? 1 : out;
    };
});

In CSS

.stopwatch {
    animation: tick 1s frames(60);
}

@cdoublev
Copy link
Collaborator

Similarly to RCS (Relative Color Syntax, defined in CSS Color 5), <easing-function> could additionally accept <number>, which can be replaced by a math function that would be allowed to use p (for progress) as a calculation term. Math functions do not accept if/else though.

@bramus
Copy link
Contributor

bramus commented Jan 12, 2024

I recently wrote about the troubles authors have to go through to use a custom easing function: https://www.bram.us/2024/01/12/waapi-custom-easing-function/

The easiest approach today is to convert the custom easing function to a linear() string, yet I think the solution proposed in this issue is worth pursuing as it will give more precise results.

@birtles birtles changed the title [css-timing-2] - Custom Timing Functions in JavaScript [css-easing-2] - Custom Timing Functions in JavaScript Jan 13, 2024
@trusktr
Copy link

trusktr commented Feb 18, 2024

Hello, I'm maintainer of Tween.js. This would be excellent. For Web Animations, allowing a function be passed to easing seems like the perfect solution (in Tween.js, easings are functions just like in the OP).

Bramus's guide on how to pre-calculate keyframes or linear() strings is great. Besides being cumbersome, these techniques have limitations: namely, animations have to be pre-calculated for pre-determined amounts of animation durations to make them look the best, and this can fall apart when the duration is not necessarily known up front.

The real benefit of an easing function is that it implicitly adapts to any length duration, because it always operates on the current time value which is irrespective of an animation's duration.


Along with this, the following issue would be great to solve too, as it would make the Web Animations API a lot more useful for not only DOM elements, but for other cases like JS properties on Custom Elements, WebGL, canvas 2D, and even non-browser JS runtimes:

@tabatkins
Copy link
Member

An issue with JS-driven easing functions is that animations are often done on the compositing thread, which doesn't have access to the JS engine and has stricter timing guarantees. Precompiling to linear(), while inconvenient, definitely fixes that issue.

An alternative might be that custom JS timing functions get compiled by the browser, called repeatedly at a browser-chosen resolution and essentially translated into a linear function automatically.

Another alternative is we just provide the compilation function as a browser API - given a function and a desired resolution, it'll auto-generate a linear() string for you. Of course, this is also a pretty trivial function to do yourself:

function compileTimingFunction(fn, resolution=.01) {
  let args = [];
  for(var i = 0; i*resolution <= 1; i++) {
    let input = i*resolution;
    let output = fn(input);
    args.push(`${output} ${input*100}%`);
  }
  return `linear(${args.join(", ")})`;
}

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

No branches or pull requests

7 participants