[HN Gopher] I'm Unsatisfied with Easing Functions
___________________________________________________________________
I'm Unsatisfied with Easing Functions
Author : ndyg
Score : 27 points
Date : 2025-07-16 17:30 UTC (7 days ago)
(HTM) web link (www.davepagurek.com)
(TXT) w3m dump (www.davepagurek.com)
| ndyg wrote:
| The friction Dave describes where changing one param effects all
| the other params in an animation brought to mind this section of
| an old Bret Victor talk:
| https://www.youtube.com/watch?v=a-OyoVcbwWE&t=716s
| unconed wrote:
| Bouncy animations that overshoot just seem like a bad idea in
| general. The purpose of a UI animation is to guide the eye, but
| the bounce explicitly introduces a reversal of motion at the end
| before stopping.
|
| Easing functions are just very cargo culty. We've had the same
| basic set that dates from the Flash era. Now there's an Apple
| variant that's just a parametric version of the same idea, but it
| lacks guaranteed continuity and it's even harder to control?
|
| Personally I've had far better results using the repeated-
| lerping-towards-a-target trick, aka a true exponential ease. When
| stacked, you get a classic LTI (linear time invariant) system,
| and the math around how those behave is well established.
|
| Classic hand-drawn animation does often use stretching and
| squeezing to emphasize and to create a sense of anticipation, but
| that's very different and always dependent on the specific
| motion. You can't automate that by making everything act like
| jello.
| pahgawk wrote:
| Hi, author here! When writing this, I was thinking more in the
| space of procedural character animation and motion graphics
| than UI animations. That's part of why I want a system with
| nice parameters, so that I do have the ability to fine tune and
| tweak the motion to fit the context. My background is in
| classical animation so it's something I might just keyframe by
| hand in a non-code context, or in Flash when it's easier to
| jump back and forth between code and non code. Although I think
| having it parameterized still can lead to interesting
| opportunities for variation in procedural animation!
| ipv6ipv4 wrote:
| Overshoot is important for scroll views. Without the bounce,
| there is no feedback if you have scrolled to the edge or not.
| Early Android lacked the bounce, and it would invariably lead
| to users scrolling again to be sure they had indeed scrolled to
| the edge of the view.
| Jyaif wrote:
| > Early Android lacked the bounce
|
| Early android had some visual feedback (a gradient that faded
| out) instead of the bounce, possibly because Apple owns a
| patent ( https://patents.google.com/patent/US7469381B2/en ).
|
| Current Android still does not use the bounce, and instead
| stretches the content, which works well enough on the high
| resolution screens that we have now.
| Trung0246 wrote:
| There was an old experiment of mine where I tried to mix match
| easing function, maybe it'll be of interest to you
|
| https://codepen.io/Trung0246/pen/jxvvzY
| EsportToys wrote:
| Closed-form (non-iterative) PID solution:
|
| https://www.desmos.com/calculator/mu80ttc9aa
| function sprung_response(t,pos,vel,k,c,m) local decay =
| c/2/m local omega = math.sqrt(k/m) local
| resid = decay*decay-omega*omega local scale =
| math.sqrt(math.abs(resid)) local T1,T0 = t , 1
| if resid<0 then T1,T0 = math.sin( scale*t)/scale ,
| math.cos( scale*t) elseif resid>0 then
| T1,T0 = math.sinh(scale*t)/scale , math.cosh(scale*t)
| end local dissipation = math.exp(-decay*t)
| local evolved_pos = dissipation*( pos*(T0+T1*decay) + vel*( T1
| ) ) local evolved_vel = dissipation*( pos*(-T1*omega^2)
| + vel*(T0-T1*decay) ) return evolved_pos , evolved_vel
| end
|
| For anticipation, just add an extra initial velocity in the
| opposite direction and let the closed-form solution handle the
| time evolution. The main trick here is to keep both position and
| velocity as state. There is no need to "step through the
| simulation".
| thomascountz wrote:
| Wow, this is great!
| joenot443 wrote:
| Wow, this is excellent!
|
| Is there a name for this kind of motion? I'd love to use it
| sometime.
| EsportToys wrote:
| It's the general solution to a damped harmonic
| oscillator/mass-spring-damper system: m *
| x'' + c * x' + k * x = f(t)
|
| See https://web.archive.org/web/20230604215211/https://esport
| toy...
| pahgawk wrote:
| This is good! Although I'd also say that initial velocity
| doesn't _quite_ cover what I was talking about in the post --
| even anticipation arguably can start from 0 velocity,
| accelerate backwards, decelerate, then accelerate in the
| opposite direction. Imo, any sudden change in velocity should
| by default be avoided (there are always valid uses where
| breaking that expectation is good, but I 'd want it smooth by
| default.)
|
| That could possibly be done by incrementally changing force to
| move it back first, then forward, or to model this as a PD
| controller following an input with some baked in reversal
| before moving forward. That can still be closed-form (state
| response to a known input will be; Laplace transforms can help
| there), but still would need a bit of effort to model and tune
| to look right.
| LegionMammal978 wrote:
| You wouldn't really need an incremental force: a step-
| function force (first backward for some time steps, then
| instantly forward) will still produce a continuous velocity
| curve.
| pahgawk wrote:
| true! I suppose you'd risk getting some oscillations in the
| anticipation depending on the scale of the force, but that
| could be desirable, or might not happen if the scale is
| small enough, and certainly makes the math a little easier
| EsportToys wrote:
| You can trivially calculate the time-to-peak overshoot
| using the closed form equation. It's the first
| (0-indexed) zero of the velocity response, i.e. happens
| at t = (pi / scale)
|
| And obviously it would only apply for the underdamped
| case.
| f33d5173 wrote:
| To me this article is ridiculous bcause the first thing I think
| of when I hear "easing function" is the css version of that
| concept, which seems to solve all the authors problems? The css
| easing functions are weirdly limited, because they consist of a
| single function that takes 4 parameters, then a bunch of
| specializations of that function. There are great websites on the
| internet for configuring those parametere though, and they are
| quite general from a practical standpoint. It should be trivial
| to emulate the concept in any other language/system.
| shermantanktop wrote:
| I read the article as an unreasonably deep dive into something
| that few people care about as much as the author. I certainly
| don't, but that's what makes it good. That's perfect for HN.
| mattkrause wrote:
| My only real beef with the article is that "Uneasy about
| easing functions" would have been a much better title.
| pahgawk wrote:
| Author here -- kicking myself because this is way better
| lmao
| ziml77 wrote:
| You should edit the title. I'm sure there's plenty of
| people who will find this post later on and be tickled by
| that :)
| mattkrause wrote:
| Take it--it's all yours :-)
| Bluestein wrote:
| Oh, you beat me to it, I see :)
|
| Kudos.-
| mattkrause wrote:
| Great minds, etc!
| Bluestein wrote:
| Totally :)
|
| PS. And, if I may as a recovering Flash 5/Actionscript
| victim ...
|
| ... this (great) article brought back memories.-
| f33d5173 wrote:
| It didn't seem unreasonably deep at all. It was long, sure,
| but it seemed to only scratch the surface of the topics it
| was discussing. That was sort of my point in bringing up the
| css function - it is itself incredibly bare-bones, but just
| describing it's usage would have covered more ground imo.
| shermantanktop wrote:
| "Unreasonably deep" is a relative term. Certainly deeper
| than I care to go. But sure, it could be unreasonably
| deeper.
|
| Every time I have used animation my approach has been to
| treat it as frosting to be used sparingly. As such I would
| be much more attracted to simple/supported modes (that
| someone else will be interested in keeping functional) than
| I would be to obsessing over physics realism. But I am also
| a UI caveman with no taste, so there's that.
| ajkjk wrote:
| how would any of that make the article ridiculous?
| f33d5173 wrote:
| It circles around the point without ever reaching it. Each
| section seemed like it was missing the obvious. "To me"
| meaning that was my personal impression.
| jakelazaroff wrote:
| By "CSS version" I'm assuming you mean cubic-bezier [1], which
| definitely does not solve all the author's problems. For
| example, you cannot use it to create their third example
| (easeOutElastic), nor many of Apple's kinematic easing
| functions or the feedback control.
|
| [1] https://developer.mozilla.org/en-US/docs/Web/CSS/easing-
| func...
| f33d5173 wrote:
| Yes, you can't use it to create those other functions, but
| you can use it to fulfill the authors purported goal in using
| those other functions. It also solves the deficiencies the
| author highlights in those other approaches. You could of
| course point to deficiencies in it and seek to resolve those
| deficiencies, but that wasn't how the article was structured.
| jakelazaroff wrote:
| I'm not sure why you think that? The author defines "easing
| functions" like this (which describes CSS's `cubic-
| bezier`):
|
| _> An easing function takes in a linear progress value,
| and returns a new progress value, but converted to
| nonlinear motion._
|
| They then describe the problem with them in the section
| "Easing is not so easy", after introducing easing functions
| that are already _more expressive_ than `cubic-bezier`:
|
| _> In traditional animation, the principles are just
| guidelines; you still end up creating new unique motion
| each time based on what acting the scene calls for. Easing
| functions in code don 't quite give you the flexibility to
| do that._
| bhk wrote:
| Oscullation/"anticipation" in transitions is so gimmicky and
| cartoonish. Living things don't move like that, and mechanical
| things don't unless they are broken, chintzy, or poorly designed
| (underdamped).
| nilamo wrote:
| Sure they do? When people do a standing jump, they first crouch
| lower to the ground. An arrow is pulled back on the bow before
| fired. A ball pushed uphill will roll uphill before stopping
| and rolling downhill.
| pc86 wrote:
| Eh, sort of. You do that if you're working out, because
| you're trying to maximize performance.
|
| Most living beings aren't trying maximize performance,
| they're trying to maximize survival. That crouch gives away
| that you're about to jump, and it allows prey to avoid you
| more easily or predators to adjust their attack to counter
| for it. A rabbit trying to get away from a predator simply
| bounds away in its given direction without giving away what
| its doing.
| nilamo wrote:
| My experience seeing animals is apparently different from
| yours. The anticipation/tell is very clear for a wide range
| of animals... Birds squat down before taking off, cats
| hunker down to the ground before pouncing, etc.
|
| Even the human jumping example again... Suggesting people
| only bend their knees for performance would mean the
| "normal" way of jumping involves keeping your legs straight
| at all times and using your ankles/toes for all your
| thrust?
| fidotron wrote:
| Those are distinct actions though.
|
| "Anticipation" in character animations is a source of
| persistent friction between game programmers and animators
| because if incorporated as part of user/player action it
| simply kills responsiveness. OTOH being able to see NPCs
| prepare to do something (like pulling the bow) is incredibly
| useful.
| thomascountz wrote:
| Think of a slingshot? Or a person jumping?[1].
|
| We see this, not only in mammals and muscles, but in organic
| systems which evolved to trade speed for power by storing
| mechanical, electrical, or chemical potential energy over time.
|
| Overshoot and damping is the same in reverse: dissipating
| kinetic energy when you, for example, bend your knees when you
| land from a jump (hopefully).
|
| [1]: https://www.youtube.com/watch?v=qN3apht8zRs&t
| jcalx wrote:
| Apple's easing function (like others) is parameterized by
| physical characteristics (e.g. spring force, damping) that are
| easier to model from first principles, but there are other
| parameters (overshoot distance, anticipation size, animation
| time) that are more useful for animation. A closed-form
| parametrization with the latter might be tricky to derive, but
| some kind of iterative solver (plug in desired animation
| parameters, get values for the physical parameters) shouldn't be
| too difficult?
| Liftyee wrote:
| As an engineer I'm attracted towards the PID based one, but also
| wary of the amount of CPU cycles used just for a little
| animation. It seems like overkill, for some reason.
| MITSardine wrote:
| I don't imagine stepping through the ODE with finite
| differences at the same time step as drawing should be very
| expensive, essentially it's a handful of sums and products. In
| fact, using a closed form solution with an exponential is
| probably more expensive.
| EsportToys wrote:
| A middleground is to use fixed timesteps with the closed form
| solution. Then the integration factors can be statically
| precomputed as constants, and every frame becomes just a 2x2
| matrix multiplication.
|
| And even if you are to calculate the integration factors
| based on dynamic frame rates, they only need to be computed
| just once per frame for all objects that share the same
| damping configuration.
| pahgawk wrote:
| You're right, it's not that expensive! The main limiting
| factor for me is that not all animations play monotonically
| forward in time. I'm also interested in using these in
| animation software where you have a timeline and can seek
| anywhere. So a closed form is helpful in handling that case
| too.
| thomascountz wrote:
| The section on feedback control reminded me of procedural
| animation where you don't calculate the velocities and positions
| directly; instead the animation is a consequence of constraints
| and a target.
|
| I took your PD controller concept and added anticipation using
| two targets: the original mouse target, and an "anticipation"
| target set proportionally based on the distance from the point to
| the main target[1].
|
| This also made me think of Jelly Car and the amazing simulations
| they did using rigid frames for soft bodies, called Shape
| Matching[2]. Instead of simulating the soft body physics
| directly, they used a frame, springs, and "gas" to constrain
| points, which moved towards targets fixed to the frame.
|
| [1]: https://editor.p5js.org/Thomascountz/sketches/YXWm_VV6s
|
| [2]: https://www.gamedeveloper.com/programming/deep-dive-the-
| soft...
| pahgawk wrote:
| Author here! Nice, thanks for taking the time to create that
| demo! I also like the look of commenting out `vel.set(0, 0)`
| when the anticipation target is reached, as it has less "snap"
| between velocities. Although if you keep velocity the same, now
| it'll go a little farther than the anticipation target. So
| maybe if you wanted to have a specific distance of anticipation
| (I think something an animator would reasonably want to do),
| one could do some math to figure out where to place the target
| position so that by the time it decelerates to 0, the distance
| is the real anticipation distance.
| fidotron wrote:
| Honestly, I think once you are tired of easing functions you are
| best off going to real physics. (By which I mean a simple Verlet
| integrator).
|
| I've posted this before for other reasons but
| https://www.luduxia.com/showdown/ - in this case everything is
| easing functions, except for, bizarrely, the giant text that
| announces win/draw and the text inside the counter, which is done
| with actual physics because that proves better, especially with
| constantly moving targets.
| franciscop wrote:
| I also didn't like when a new easing happened _while_ another
| easing was happening, which often felt very jerky. Had to do a
| bunch of calculus (derivatives) by hand and wrote a small library
| for it in JS:
|
| https://github.com/franciscop/ola
|
| Note: ola means (sea) wave in Spanish
| pahgawk wrote:
| Thanks for sharing this library! Author of the post here, I'll
| definitely check out your implementation.
| franciscop wrote:
| Thanks! From your article, you might not want/like my library
| since it's based on a single easing function. I used a cubic
| function to find out the interpolation values, and the
| derivative to make sure it's always smooth. The equation
| looks like this, it's on the source code:
|
| https://www.wolframalpha.com/input/?i=x+%3D+2+*+t+%5E+3+-+3+.
| ..
|
| If you wanted some more details please feel free to ping me
| (email through my website's resume, or Twitter) and I'll dig
| the handwritten equations.
| bsimpson wrote:
| > maybe I can start by just writing up my rant, and maybe someone
| else will read this and find a Research Project in here and will
| make the time before me
|
| This is one of the things I find fascinating about Bret Victor.
| In the early 10s, he was giving a lot of really influential talks
| on creative coding. Someone asked him what his motivation was. He
| essentially said that he gives his ideas away, because he wants
| to live in a world where they exist. It's easy enough to build
| enough of a demo to give a talk, but he wanted someone else to do
| the legwork to grow it into a full project.
| shermantanktop wrote:
| That runs counter to conventional wisdom about patents: very
| few ideas are worth protecting, because they have zero value
| until they have been built into something of value in the real
| world.
|
| And most builders are most passionate about their own ideas. An
| idea "gifted" from someone else may actually have _negative_
| value.
| ajkjk wrote:
| imo there just need to be a few more default easing functions,
| the standard ones aren't quite natural enough. There must be a
| standard curve equivalent to the "human pushing/pulling an object
| from A->B" function that would actually look natural.
| EaseIn/EaseOut/etc is never quite it; human motion has a bit of
| higher-order feedback used to regulate it. Maybe to do with the
| combination of: all the motion happens via muscular springs,
| plus, there's a well-trained system that regulates the motion to
| minimize inefficient use of energy. Whether I jerk suddenly and
| then stop it, or move it slowly so it smoothly decelerates to the
| final point, it never looks as mechanical as the stuff you see in
| code.
| eviks wrote:
| And the parameters should be tied to reality as well like
| object weight, surface tension, hand strength, ...?
| ajkjk wrote:
| If you want? I don't care about the parameters, I just think
| the default functions should include some more sensible
| options that look less mechanical.
| x187463 wrote:
| I have found myself leaning heavily on easing functions to smooth
| motion within my terminal visual effects engine. I do not have a
| very strong math background so I am limited in how much I can
| modify the commonly available functions. I have found it useful
| to create custom easing functions by mapping the easing function
| progress across a bezier curve. There's an example in the
| changeblog write-up from the last release:
|
| https://chrisbuilds.github.io/terminaltexteffects/changeblog...
| MITSardine wrote:
| Interesting article. But why is it an issue for it to be
| iterative?
|
| I would imagine you're going to step through time anyways, to
| draw frame after frame. Wouldn't it in fact be cheaper to step
| through using some finite differences scheme, with one FD step
| for one draw step? It could be as cheap as a handful of additions
| per time step. Unless I'm overlooking something?
|
| If needed for stability or accuracy, you can also step through
| several FD steps in one frame, but I'd imagine at a rate of
| 20fps, this should not be necessary for the types of equations
| considered.
| pahgawk wrote:
| Hi, author here! Two things come to mind:
|
| - Part of my use case is that I build animation software. In
| there, you've got a timeline, and you can seek anywhere on the
| timeline. So in that scenario, you're not always moving
| consistently forward in time.
|
| - In real-time contexts, sometimes you drop frames, even for
| simple motion, just due to the hardware it's being run on and
| what else the computer is doing. Simulations can be sensitive
| to time steps and produce slightly different results depending
| on them. The size of the issue depends on a lot of factors, but
| you don't have that issue at all with a closed form solution.
| MITSardine wrote:
| I see!
|
| For your second point, you can decouple ODE time stepping and
| frame time stepping. I think it suffice to step until the
| lowest time ODE time step that is greater than the current
| frame time and interpolate between that and the previous ODE
| time step.
|
| This technique is used in loosely coupled systems, for
| instance in mechanics where a rigid body needs a much lower
| time step (higher frequencies) than a soft body to compute
| the dynamics of, but you still need common time steps to
| compute interactions. Often times, the time steps are
| dictated by CFL conditions, and they may not even be integer
| multiples of each other.
|
| However your first point is where I see the iterative
| approach really wouldn't work. Especially if the user might
| change the parameters before you've done anything with them,
| it wouldn't make sense to precompute values using the
| iterative scheme. Otherwise, that could be done, and then
| values interpolated between steps.
|
| If you have few parameters, one solution that comes to mind
| if you cannot find a closed form solution is to grid the
| params space, and precompute the curve at each point of the
| grid. Then, when the user requests any value, simply localize
| that in the grid and interpolate from the nodes of the
| element. It becomes problematic if start and end points are
| parameters, though... In that case I suppose a linear
| transform of the curve to fit the end points precisely would
| be in order. You can consider the end points alone, it
| wouldn't be very complicated.
|
| An improvement would be an adaptive grid; each time a point
| is inserted in a hypercube, compute also at the projections
| of that point on the hypercube's facets (and then the
| projection of that onto the facets' facets, and so on...),
| and split it. Consider whether to insert based on an
| indicator that takes into account the solutions at the nodes
| of the initial hypercube (if they are too distinct, a split
| is in order). Maybe this is too much complication for
| something this simple, but anyways there would be solutions
| for using more difficult ODEs that don't have closed-form
| solutions. Note if a single family of ODEs is considered,
| then this can be done offline, or online but cached.
|
| There are more sophisticated methods like PINNs of course,
| but I don't know if you'd gain anything in performance versus
| a bespoke scheme. In particular if the inference step is more
| expensive than localizing in a kd-tree and interpolating from
| a few points.
| pgorczak wrote:
| I guess the issue is having to store the intermediate state
| somewhere. It's true for the PID example that numerical
| integration is easier to compute - if you look at the comment
| with the closed form solution, you need trigonometric and
| exponential functions to evaluate it. It's kind of fascinating
| that the iterative method approximates the same thing with just
| addition and multiplication.
| MITSardine wrote:
| At the same time, the cos and exp are also computed using
| addition and multiplication. Unless the values are tabulated,
| I imagine you'd use a series expansion. Or an even more
| straightforward argument is computers only know how to do
| addition and multiplication...
|
| In the case of the exponential, if you're willing to start
| from a known value (say 0, where exp(0) = 1) and need values
| until the last, then using the very definition of the
| exponential is even more straightforward... this is the only
| function s.t. f' = f and f(0) = 1. In other words, step
| through it! The most natural definition of the exponential is
| as an ODE to begin with.
|
| Tangentially related, this is one of my favourite articles
| "Nineteen Dubious Ways to Compute the Exponential of a
| Matrix":
|
| https://www.cs.jhu.edu/~misha/ReadingSeminar/Papers/Moler03..
| ..
| sixthDot wrote:
| Basically any transfert function that is used as interpolator can
| also be used as "easing". E.g a quadratic Bezier (let's say with
| empirically determined coeffs). One lesser known I used to like
| much is the super ellipse, although very costly.
| socalgal2 wrote:
| Why does literally every single article on easing functions
| mention Penner? First off he didn't invent them. Games had been
| using those functions since at least the 80s.
|
| Second, why only him and these functions? I don't write: In
| JavaScript by Brendan Eich using Node.js by Ryan Dahl I installed
| a package using npm by Isaac Z. Schlueter called React by Jordan
| Walke and for the backend I used TJ Holowaychuk's express.js.
| Instead just write: In JavaScript using node.js I installed react
| and express.js
|
| But literally, I've never read an article about easing functions
| that just says "easing functions". they all feel obligated to
| mention Penner" Why? and no, it's not because open source or
| multiple contributors. I used those examples because I can't name
| the 1000s of people for functions which is precisely my point
| istjohn wrote:
| I'd like to see a toggle switch easing function. Slow down as you
| approach 40%, then snap to 100%.
| spankalee wrote:
| There are a lot of post and talks out there about why spring is
| easier to use and gives nicer results than most easing functions.
|
| In CSS, there's a long standing feature request to add a spring()
| timing function: https://github.com/w3c/csswg-drafts/issues/280
|
| This would not only be great for developer ergonomics, but would
| remove JS from the animation path for these cases.
| Bluestein wrote:
| > I'm unsatisfied with easing functions
|
| Er ... "un ... _eased_ "? :)
| SimplyUnknown wrote:
| I have the feeling that B-splines would be a good solution for
| this problem. Given that they have a continuous zeroth (i.e., the
| function is continuous), first, and second derivative, the motion
| will always be smooth and there will be no kinks. However, maybe
| it's moving the problem because now you must tune the
| coefficients of the B-spline instead of damping parameters (even
| though a direct mapping between these must exist but this mapping
| may not be trivial).
___________________________________________________________________
(page generated 2025-07-23 23:00 UTC)