Skip to content

Commit 30e2f51

Browse files
authored
feat: Allow custom timing functions for easing (#639)
Allows specifying a timing function instead of selecting from predefined easings or a cubic-bezier ease. I've reused the `easing` property to set the timing function, so you can specify a function or a string as before.
2 parents 8f3a738 + f539316 commit 30e2f51

File tree

4 files changed

+31
-17
lines changed

4 files changed

+31
-17
lines changed

examples/tests/animation.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,15 @@
1717
* limitations under the License.
1818
*/
1919

20-
import type { IAnimationController } from '@lightningjs/renderer';
21-
20+
import type {
21+
IAnimationController,
22+
TimingFunction,
23+
} from '@lightningjs/renderer';
2224
import type { ExampleSettings } from '../common/ExampleSettings.js';
25+
2326
interface AnimationExampleSettings {
2427
duration: number;
25-
easing: string;
28+
easing: string | TimingFunction;
2629
delay: number;
2730
loop: boolean;
2831
stopMethod: 'reverse' | 'reset' | false;
@@ -87,6 +90,7 @@ export default async function ({ renderer, testRoot }: ExampleSettings) {
8790
'ease-in-out-back',
8891
'cubic-bezier(0,1.35,.99,-0.07)',
8992
'cubic-bezier(.41,.91,.99,-0.07)',
93+
'loopCustomTiming',
9094
'loopStopMethodReverse',
9195
'loopStopMethodReset',
9296
'loop',
@@ -121,6 +125,12 @@ export default async function ({ renderer, testRoot }: ExampleSettings) {
121125
} else if (easing === 'loop') {
122126
animationSettings.easing = 'linear';
123127
animationSettings.loop = true;
128+
} else if (easing === 'loopCustomTiming') {
129+
animationSettings.easing = (t: number) => {
130+
return Math.round(t * 5) / 5;
131+
};
132+
animationSettings.loop = true;
133+
animationSettings.stopMethod = 'reverse';
124134
} else {
125135
animationSettings.loop = false;
126136
animationSettings.stopMethod = false;

exports/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export {
5050
} from '../src/core/CoreTextureManager.js';
5151
export type { MemoryInfo } from '../src/core/TextureMemoryManager.js';
5252
export type { AnimationSettings } from '../src/core/animations/CoreAnimation.js';
53+
export type { TimingFunction } from '../src/core/utils.js';
5354
export type { Inspector } from '../src/main-api/Inspector.js';
5455
export type { CoreNodeRenderState } from '../src/core/CoreNode.js';
5556

src/core/animations/CoreAnimation.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@
1818
*/
1919

2020
import { type CoreNode, type CoreNodeAnimateProps } from '../CoreNode.js';
21-
import { getTimingFunction } from '../utils.js';
21+
import { getTimingFunction, type TimingFunction } from '../utils.js';
2222
import { mergeColorProgress } from '../../utils.js';
2323
import { EventEmitter } from '../../common/EventEmitter.js';
2424

2525
export interface AnimationSettings {
2626
duration: number;
2727
delay: number;
28-
easing: string;
28+
easing: string | TimingFunction;
2929
loop: boolean;
3030
repeat: number;
3131
repeatDelay: number;
@@ -43,7 +43,7 @@ export class CoreAnimation extends EventEmitter {
4343
private progress = 0;
4444
private delayFor = 0;
4545
private delay = 0;
46-
private timingFunction: (t: number) => number | undefined;
46+
private timingFunction: TimingFunction;
4747

4848
propValuesMap: PropValuesMap = {};
4949

@@ -92,7 +92,8 @@ export class CoreAnimation extends EventEmitter {
9292
repeatDelay: settings.repeatDelay ?? 0,
9393
stopMethod: settings.stopMethod ?? false,
9494
};
95-
this.timingFunction = getTimingFunction(easing);
95+
this.timingFunction =
96+
typeof easing === 'string' ? getTimingFunction(easing) : easing;
9697
this.delayFor = delay;
9798
this.delay = delay;
9899
}
@@ -162,14 +163,14 @@ export class CoreAnimation extends EventEmitter {
162163
}
163164

164165
private applyEasing(p: number, s: number, e: number): number {
165-
return (this.timingFunction(p) || p) * (e - s) + s;
166+
return this.timingFunction(p) * (e - s) + s;
166167
}
167168

168169
updateValue(
169170
propName: string,
170171
propValue: number,
171172
startValue: number,
172-
easing: string | undefined,
173+
easing: string | TimingFunction | undefined,
173174
): number {
174175
if (this.progress === 1) {
175176
return propValue;
@@ -201,7 +202,7 @@ export class CoreAnimation extends EventEmitter {
201202
private updateValues(
202203
target: Record<string, number>,
203204
valueMap: Record<string, PropValues>,
204-
easing: string | undefined,
205+
easing: string | TimingFunction | undefined,
205206
) {
206207
const entries = Object.entries(valueMap);
207208
const eLength = entries.length;

src/core/utils.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ export const RANDOM = Math.random;
3030
export const ANGLE_ORDER = 'zyx';
3131
const degree = Math.PI / 180;
3232

33+
export type TimingFunction = (t: number) => number;
34+
3335
export const setMatrixArrayType = (
3436
type: Float32ArrayConstructor | ArrayConstructor,
3537
) => {
@@ -57,15 +59,15 @@ const getTimingBezier = (
5759
b: number,
5860
c: number,
5961
d: number,
60-
): ((time: number) => number | undefined) => {
62+
): TimingFunction => {
6163
const xc = 3.0 * a;
6264
const xb = 3.0 * (c - a) - xc;
6365
const xa = 1.0 - xc - xb;
6466
const yc = 3.0 * b;
6567
const yb = 3.0 * (d - b) - yc;
6668
const ya = 1.0 - yc - yb;
6769

68-
return function (time: number): number | undefined {
70+
return function (time) {
6971
if (time >= 1.0) {
7072
return 1;
7173
}
@@ -116,11 +118,13 @@ const getTimingBezier = (
116118
minT = t;
117119
}
118120
}
121+
122+
return time;
119123
};
120124
};
121125

122126
interface TimingFunctionMap {
123-
[key: string]: (time: number) => number | undefined;
127+
[key: string]: TimingFunction;
124128
}
125129

126130
type TimingLookupArray = number[];
@@ -174,9 +178,7 @@ const parseCubicBezier = (str: string) => {
174178
return defaultTiming;
175179
};
176180

177-
export const getTimingFunction = (
178-
str: string,
179-
): ((time: number) => number | undefined) => {
181+
export const getTimingFunction = (str: string): TimingFunction => {
180182
if (str === 'linear') {
181183
return defaultTiming;
182184
}
@@ -202,7 +204,7 @@ export const getTimingFunction = (
202204
const [a, b, c, d] = lookup;
203205
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
204206
// @ts-ignore - TS doesn't understand that we've checked for undefined
205-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
207+
206208
const timing = getTimingBezier(a, b, c, d);
207209
timingMapping[str] = timing;
208210
return timing;

0 commit comments

Comments
 (0)