const lerp = (x, y, a) => x * (1 - a) + y * a;
const clamp = (a, min = 0, max = 1) => Math.min(max, Math.max(min, a));
const invlerp = (x, y, a) => clamp((a - x) / (y - x));
const range = (x1, y1, x2, y2, a) => lerp(x2, y2, invlerp(x1, y1, a));
A lerp returns the value between two numbers at a specified, decimal midpoint:
lerp(20, 80, 0) // 20
lerp(20, 80, 1) // 80
lerp(20, 80, 0.5) // 40
It’s great for answering gnarly maths questions like: “What number is 35% between 56 and 132?” with elegance: lerp(56, 132, 0.35)
. My maths skills aren’t all that, so it’s great to have these up my sleeve.
The clamp method is wonderfully dull. You give it a number and then a minimum & maximum. If your number falls within the bounds of the min & max, it’ll return it. If not, it’ll return either the minimum it’s smaller, or the maximum if it’s bigger.
clamp(24, 20, 30) // 24
clamp(12, 20, 30) // 20
clamp(32, 20, 30) // 30
It’s really handy for preventing absurd numbers from entering a calculation, stopping an element from rendering off screen, or controlling the edges of a <canvas>
.
This works in the opposite way to the lerp. Instead of passing a decimal midpoint, you pass any value, and it’ll return that decimal, wherever it falls on that spectrum. Internally it also uses a clamp, so you never get unwieldy values back.
invlerp(50, 100, 75) // 0.5
invlerp(50, 100, 25) // 0
invlerp(50, 100, 125) // 1
This is great for scroll animations. Questions like “How far through this section has the user scrolled?” can be neatly answered with code like:
const position = el.getBoundingClientRect();
const howFarThrough = invlerp(
position.top,
position.bottom,
window.scrollY
);
Here’s an example that tracks the percentage scroll position of a target slab against the viewport.
This final method is ace. It’s a one-liner that converts a value from one data range to another. That might sound a bit arbitrary, but it’s surprisingly useful. We pass in two data ranges and a value that sits within data range one (it will still be clamped).
// Range 1 Range 2 Value
range(10, 100, 2000, 20000, 50) // 10000
Taking the previous example up a notch, let’s say that as the user scrolls through a section, we want to subtly move an element down the page by 150px
. The section is in the middle of the document, starting at 3214px
and ending at 3892px
, and we want to convert window.scrollY
from the big range down to a value between 0px
and 150px
. That’s a pretty nasty calculation to make, but range()
makes it nice and clean.
const position = el.getBoundingClientRect();
const transformY = range(
position.top,
position.bottom,
0,
150,
window.scrollY
);
If the user is above the section, it’ll be clamped to 0px
. If they’re below, it’ll be clamped to 150px
. And in all positions in between, it’ll evenly interpolate between the values.
const lerp = (x: number, y: number, a: number) => x * (1 - a) + y * a;
const invlerp = (x: number, y: number, a: number) => clamp((a - x) / (y - x));
const clamp = (a: number, min = 0, max = 1) => Math.min(max, Math.max(min, a));
const range = (
x1: number,
y1: number,
x2: number,
y2: number,
a: number
) => lerp(x2, y2, invlerp(x1, y1, a));