Skip to content

Commit 3446659

Browse files
committed
Add support for steps easing
1 parent ac0a66b commit 3446659

File tree

4 files changed

+207
-5
lines changed

4 files changed

+207
-5
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,6 @@ It's perfectly usable with fake timers, except for the [issue with promises](htt
368368

369369
### Current issues
370370

371-
- No support for `steps` easings
372371
- Needs more tests
373372

374373
<!-- prettier-ignore-start -->

src/mocks/web-animations-api/__tests__/commitStyles.test.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,6 @@ describe('Animation', () => {
4444
// -> active |
4545
jest.advanceTimersByTime(DURATION - 1);
4646

47-
// console.log('-> active |', animation.currentTime, performance.now());
48-
4947
expect(element.style.transform).toBe('translateX(100px)');
5048

5149
// | finished
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import { StepsEasing } from '../easingFunctions';
2+
3+
// Test values from:
4+
// https://codesandbox.io/s/staging-frog-hbtyhn?file=/index.html
5+
6+
describe('StepsEasing', () => {
7+
it('should throw error when invalid steps() function is provided', () => {
8+
expect(() => StepsEasing('steps(0.5, jump-end)')).toThrowError(
9+
'Invalid easing function: steps(0.5, jump-end)'
10+
);
11+
});
12+
13+
describe('jumpterms', () => {
14+
it('should return correct values for jump-start', () => {
15+
const easing1 = StepsEasing('steps(5, jump-start)');
16+
const easing2 = StepsEasing('steps(5, start)');
17+
18+
function tests(easing: (value: number) => number) {
19+
expect(easing(0)).toBe(0);
20+
expect(easing(0.09158299999999997)).toBe(0.2);
21+
expect(easing(0.19157899999999994)).toBe(0.2);
22+
expect(easing(0.29990799999999984)).toBe(0.4);
23+
expect(easing(0.3999039999999998)).toBe(0.4);
24+
expect(easing(0.4998999999999998)).toBe(0.6);
25+
expect(easing(0.5998959999999998)).toBe(0.6);
26+
expect(easing(0.6998919999999997)).toBe(0.8);
27+
expect(easing(0.7998879999999997)).toBe(0.8);
28+
expect(easing(0.8998839999999997)).toBe(1);
29+
expect(easing(0.9915469999999997)).toBe(1);
30+
expect(easing(1)).toBe(1);
31+
}
32+
33+
tests(easing1);
34+
tests(easing2);
35+
});
36+
37+
it('should return correct values for jump-end', () => {
38+
const easing1 = StepsEasing('steps(5, jump-end)');
39+
const easing2 = StepsEasing('steps(5, end)');
40+
41+
function tests(easing: (value: number) => number) {
42+
expect(easing(0)).toBe(0);
43+
expect(easing(0.00022300000000008424)).toBe(0);
44+
expect(easing(0.10021900000000006)).toBe(0);
45+
expect(easing(0.20021500000000003)).toBe(0.2);
46+
expect(easing(0.29187799999999964)).toBe(0.2);
47+
expect(easing(0.400207)).toBe(0.4);
48+
expect(easing(0.500203)).toBe(0.4);
49+
expect(easing(0.5918659999999996)).toBe(0.4);
50+
expect(easing(0.7001949999999999)).toBe(0.6);
51+
expect(easing(0.8001909999999999)).toBe(0.8);
52+
expect(easing(0.9001869999999998)).toBe(0.8);
53+
expect(easing(1)).toBe(1);
54+
}
55+
56+
tests(easing1);
57+
tests(easing2);
58+
});
59+
60+
it('should return correct values for jump-none', () => {
61+
const easing = StepsEasing('steps(5, jump-none)');
62+
63+
expect(easing(0)).toBe(0);
64+
expect(easing(0.09995599999999993)).toBe(0);
65+
expect(easing(0.19161899999999998)).toBe(0);
66+
expect(easing(0.29161499999999996)).toBe(0.25);
67+
expect(easing(0.3999440000000001)).toBe(0.25);
68+
expect(easing(0.49994000000000005)).toBe(0.5);
69+
expect(easing(0.5916030000000001)).toBe(0.5);
70+
expect(easing(0.699932)).toBe(0.75);
71+
expect(easing(0.7999280000000002)).toBe(0.75);
72+
expect(easing(0.8999240000000002)).toBe(1);
73+
expect(easing(0.9999200000000001)).toBe(1);
74+
expect(easing(1)).toBe(1);
75+
});
76+
77+
it('should return correct values for jump-both', () => {
78+
const easing = StepsEasing('steps(4, jump-both)');
79+
80+
expect(easing(0)).toBe(0);
81+
expect(easing(0.09979599999999955)).toBe(0.2);
82+
expect(easing(0.19979199999999953)).toBe(0.2);
83+
expect(easing(0.2997879999999995)).toBe(0.4);
84+
expect(easing(0.3997839999999995)).toBe(0.4);
85+
expect(easing(0.49977999999999945)).toBe(0.4);
86+
expect(easing(0.5997759999999994)).toBe(0.6);
87+
expect(easing(0.6997719999999994)).toBe(0.6);
88+
expect(easing(0.7997679999999994)).toBe(0.8);
89+
expect(easing(0.8997639999999993)).toBe(0.8);
90+
expect(easing(0.9997600000000002)).toBe(0.8);
91+
expect(easing(1)).toBe(1);
92+
});
93+
94+
it('should return correct values for step-start', () => {
95+
const easing = StepsEasing('step-start');
96+
97+
expect(easing(0)).toBe(0);
98+
expect(easing(0.09979599999999955)).toBe(1);
99+
expect(easing(0.19979199999999953)).toBe(1);
100+
expect(easing(0.2997879999999995)).toBe(1);
101+
expect(easing(0.3997839999999995)).toBe(1);
102+
expect(easing(0.49977999999999945)).toBe(1);
103+
expect(easing(0.5997759999999994)).toBe(1);
104+
expect(easing(0.6997719999999994)).toBe(1);
105+
expect(easing(0.7997679999999994)).toBe(1);
106+
expect(easing(0.8997639999999993)).toBe(1);
107+
expect(easing(0.9997600000000002)).toBe(1);
108+
expect(easing(1)).toBe(1);
109+
});
110+
111+
it('should return correct values for step-end', () => {
112+
const easing = StepsEasing('step-end');
113+
114+
expect(easing(0)).toBe(0);
115+
expect(easing(0.09979599999999955)).toBe(0);
116+
expect(easing(0.19979199999999953)).toBe(0);
117+
expect(easing(0.2997879999999995)).toBe(0);
118+
expect(easing(0.3997839999999995)).toBe(0);
119+
expect(easing(0.49977999999999945)).toBe(0);
120+
expect(easing(0.5997759999999994)).toBe(0);
121+
expect(easing(0.6997719999999994)).toBe(0);
122+
expect(easing(0.7997679999999994)).toBe(0);
123+
expect(easing(0.8997639999999993)).toBe(0);
124+
expect(easing(0.9997600000000002)).toBe(0);
125+
expect(easing(1)).toBe(1);
126+
});
127+
});
128+
});

src/mocks/web-animations-api/easingFunctions.ts

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,87 @@ const easeIn = BezierEasing(0.42, 0.0, 1.0, 1.0);
55
const easeOut = BezierEasing(0.0, 0.0, 0.58, 1.0);
66
const easeInOut = BezierEasing(0.42, 0.0, 0.58, 1.0);
77

8+
const VALID_JUMPTERMS = [
9+
'jump-start',
10+
'jump-end',
11+
'jump-none',
12+
'jump-both',
13+
'start',
14+
'end',
15+
];
16+
17+
const clamp = (value: number) => Math.min(Math.max(value, 0), 1);
18+
19+
/**
20+
* @param easing - string like "steps(4, end)"
21+
* @returns easing function
22+
*/
23+
export const StepsEasing = (easing: string) => {
24+
switch (easing) {
25+
case 'step-start':
26+
easing = 'steps(1, jump-start)';
27+
break;
28+
case 'step-end':
29+
easing = 'steps(1, jump-end)';
30+
break;
31+
}
32+
33+
const easingString = easing.replace('steps(', '').replace(')', '');
34+
const easingArray = easingString.split(',').map((step) => step.trim());
35+
const [nString, jumpterm = 'jump-end'] = easingArray;
36+
37+
const n = Number(nString);
38+
39+
if (
40+
isNaN(n) ||
41+
!Number.isInteger(n) ||
42+
n <= 0 ||
43+
!VALID_JUMPTERMS.includes(jumpterm)
44+
) {
45+
throw new Error(`Invalid easing function: ${easing}`);
46+
}
47+
48+
switch (jumpterm) {
49+
case 'start':
50+
case 'jump-start':
51+
return (value: number) => {
52+
const step = Math.ceil(value * n);
53+
return clamp(step / n);
54+
};
55+
56+
case 'end':
57+
case 'jump-end':
58+
return (value: number) => {
59+
const step = Math.floor(value * n);
60+
return clamp(step / n);
61+
};
62+
63+
case 'jump-none':
64+
return (value: number) => {
65+
const step = Math.floor(value * n);
66+
return clamp(step / (n - 1));
67+
};
68+
69+
case 'jump-both':
70+
return (value: number) => {
71+
if (value === 1) {
72+
return 1;
73+
}
74+
75+
const step = Math.ceil(value * n);
76+
return clamp(step / (n + 1));
77+
};
78+
}
79+
80+
throw new Error(`Invalid easing function: ${easing}`);
81+
};
82+
883
// easing functions
984
const easingFunctions: {
1085
[key: string]: (value: number, before: boolean) => number;
1186
} = {
1287
linear: (value) => value,
13-
ease: ease,
88+
ease,
1489
'ease-in': easeIn,
1590
'ease-out': easeOut,
1691
'ease-in-out': easeInOut,
@@ -37,7 +112,9 @@ function getEasingFunctionFromString(easing: string) {
37112

38113
// convert "steps(x)" string
39114
if (easing.indexOf('steps(') === 0) {
40-
throw new Error('steps() is not implemented yet');
115+
easingFunctions[easing] = StepsEasing(easing);
116+
117+
return easingFunctions[easing];
41118
}
42119

43120
throw new Error(`Unknown easing function "${easing}"`);

0 commit comments

Comments
 (0)