packages/core/src/lib/animation/easing.ts
This is the source snapshot used to build these API details. View this revision on GitHub.
1 import type { tween } from './definition.js';
2 import type { PibblEasing } from './types.js';
3
4 const NEWTON_ITERATIONS = 8;
5 const BISECTION_ITERATIONS = 50;
6
7 function clampProgress(progress: number): number {
8 return Math.min(1, Math.max(0, progress));
9 }
10
11 function assertFiniteControl(value: number, name: string): void {
12 if (!Number.isFinite(value)) {
13 throw new RangeError(`Pibbl cubic bezier ${name} must be finite.`);
14 }
15 }
16
17 function sampleCurve(t: number, first: number, second: number): number {
18 const inverse = 1 - t;
19 return 3 * inverse * inverse * t * first +
20 3 * inverse * t * t * second +
21 t * t * t;
22 }
23
24 function sampleDerivative(t: number, first: number, second: number): number {
25 const inverse = 1 - t;
26 return 3 * inverse * inverse * first +
27 6 * inverse * t * (second - first) +
28 3 * t * t * (1 - second);
29 }
30
31 function cubicBezier(
32 x1: number,
33 y1: number,
34 x2: number,
35 y2: number,
36 ): PibblEasing {
37 assertFiniteControl(x1, 'x1');
38 assertFiniteControl(y1, 'y1');
39 assertFiniteControl(x2, 'x2');
40 assertFiniteControl(y2, 'y2');
41 if (x1 < 0 || x1 > 1 || x2 < 0 || x2 > 1) {
42 throw new RangeError('Pibbl cubic bezier x1 and x2 must be within [0, 1].');
43 }
44
45 return (input: number): number => {
46 const progress = clampProgress(input);
47 if (progress === 0 || progress === 1) return progress;
48
49 let parameter = progress;
50 for (let iteration = 0; iteration < NEWTON_ITERATIONS; iteration++) {
51 const x = sampleCurve(parameter, x1, x2) - progress;
52 const slope = sampleDerivative(parameter, x1, x2);
53 if (Math.abs(slope) < 1e-7) break;
54 const candidate = parameter - x / slope;
55 if (candidate < 0 || candidate > 1) break;
56 parameter = candidate;
57 }
58
59 let lower = 0;
60 let upper = 1;
61 for (let iteration = 0; iteration < BISECTION_ITERATIONS; iteration++) {
62 const x = sampleCurve(parameter, x1, x2);
63 if (x < progress) lower = parameter;
64 else upper = parameter;
65 parameter = (lower + upper) / 2;
66 }
67 return sampleCurve(parameter, y1, y2);
68 };
69 }
70
71 function steps(count: number, position: 'start' | 'end' = 'end'): PibblEasing {
72 if (!Number.isInteger(count) || count <= 0) {
73 throw new RangeError('Pibbl steps count must be a positive integer.');
74 }
75 if (position !== 'start' && position !== 'end') {
76 throw new TypeError('Pibbl steps position must be "start" or "end".');
77 }
78
79 return (input: number): number => {
80 const progress = clampProgress(input);
81 if (position === 'start') {
82 return Math.min(1, (Math.floor(progress * count) + 1) / count);
83 }
84 return progress === 1 ? 1 : Math.floor(progress * count) / count;
85 };
86 }
87
88 /**
89 * Built-in easing functions for mapping normalized animation progress.
90 *
91 * @see {@link PibblEasing}
92 * @see {@link tween}
93 */
94 export const easing = Object.freeze({
95 /**
96 * Returns clamped progress unchanged. See {@link PibblEasing}.
97 * @param progress - Input progress, clamped to the interval [0, 1].
98 * @returns The clamped progress.
99 */
100 linear: (progress: number): number => clampProgress(progress),
101 /**
102 * Creates a cubic Bézier easing; control-point x coordinates must be finite and within [0, 1].
103 * See {@link PibblEasing}.
104 * @param x1 - First control point horizontal coordinate, in [0, 1].
105 * @param y1 - Finite first control point vertical coordinate.
106 * @param x2 - Second control point horizontal coordinate, in [0, 1].
107 * @param y2 - Finite second control point vertical coordinate.
108 * @returns A cubic Bézier easing function. See {@link PibblEasing}.
109 */
110 cubicBezier,
111 /**
112 * Accelerates from rest using the CSS ease-in control points. See {@link easing}.
113 * @param progress - Input progress, clamped to [0, 1].
114 * @returns Eased progress. See {@link PibblEasing}.
115 */
116 easeIn: cubicBezier(0.42, 0, 1, 1),
117 /**
118 * Decelerates toward rest using the CSS ease-out control points. See {@link easing}.
119 * @param progress - Input progress, clamped to [0, 1].
120 * @returns Eased progress. See {@link PibblEasing}.
121 */
122 easeOut: cubicBezier(0, 0, 0.58, 1),
123 /**
124 * Accelerates then decelerates using the CSS ease-in-out control points. See {@link easing}.
125 * @param progress - Input progress, clamped to [0, 1].
126 * @returns Eased progress. See {@link PibblEasing}.
127 */
128 easeInOut: cubicBezier(0.42, 0, 0.58, 1),
129 /**
130 * Creates a stepped easing with a positive integer count and start or end jump placement. See
131 * {@link easing}.
132 * @param count - Positive integer number of steps.
133 * @param position - Whether jumps occur at the start or end of each interval; defaults to end.
134 * @returns A stepped easing function. See {@link PibblEasing}.
135 */
136 steps,
137 });
138
Documentation version
Section titled “Documentation version”Documentation built with @pibbl/core 0.0.2, revision 272a94a. ALPHA — NOT FOR PRODUCTION USE.