@efxlab/motion-canvas-responsive 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/ParticleGroup.d.ts +9 -0
- package/lib/components/ParticleGroup.d.ts.map +1 -0
- package/lib/components/ParticleGroup.js +20 -0
- package/lib/components/ParticleGroup.js.map +1 -0
- package/lib/components/RatioLayer.d.ts +7 -0
- package/lib/components/RatioLayer.d.ts.map +1 -0
- package/lib/components/RatioLayer.js +8 -0
- package/lib/components/RatioLayer.js.map +1 -0
- package/lib/components/index.d.ts +3 -0
- package/lib/components/index.d.ts.map +1 -0
- package/lib/components/index.js +3 -0
- package/lib/components/index.js.map +1 -0
- package/lib/context/ResponsiveContext.d.ts +45 -0
- package/lib/context/ResponsiveContext.d.ts.map +1 -0
- package/lib/context/ResponsiveContext.js +118 -0
- package/lib/context/ResponsiveContext.js.map +1 -0
- package/lib/context/ResponsiveContext.test.d.ts +2 -0
- package/lib/context/ResponsiveContext.test.d.ts.map +1 -0
- package/lib/context/ResponsiveContext.test.js +74 -0
- package/lib/helpers/color.d.ts +26 -0
- package/lib/helpers/color.d.ts.map +1 -0
- package/lib/helpers/color.js +10 -0
- package/lib/helpers/color.js.map +1 -0
- package/lib/helpers/filter.d.ts +25 -0
- package/lib/helpers/filter.d.ts.map +1 -0
- package/lib/helpers/filter.js +8 -0
- package/lib/helpers/filter.js.map +1 -0
- package/lib/helpers/helpers.test.d.ts +2 -0
- package/lib/helpers/helpers.test.d.ts.map +1 -0
- package/lib/helpers/helpers.test.js +224 -0
- package/lib/helpers/index.d.ts +8 -0
- package/lib/helpers/index.d.ts.map +1 -0
- package/lib/helpers/index.js +8 -0
- package/lib/helpers/index.js.map +1 -0
- package/lib/helpers/path.d.ts +32 -0
- package/lib/helpers/path.d.ts.map +1 -0
- package/lib/helpers/path.js +28 -0
- package/lib/helpers/path.js.map +1 -0
- package/lib/helpers/position.d.ts +20 -0
- package/lib/helpers/position.d.ts.map +1 -0
- package/lib/helpers/position.js +52 -0
- package/lib/helpers/position.js.map +1 -0
- package/lib/helpers/rotation.d.ts +33 -0
- package/lib/helpers/rotation.d.ts.map +1 -0
- package/lib/helpers/rotation.js +14 -0
- package/lib/helpers/rotation.js.map +1 -0
- package/lib/helpers/scale.d.ts +31 -0
- package/lib/helpers/scale.d.ts.map +1 -0
- package/lib/helpers/scale.js +24 -0
- package/lib/helpers/scale.js.map +1 -0
- package/lib/helpers/visibility.d.ts +21 -0
- package/lib/helpers/visibility.d.ts.map +1 -0
- package/lib/helpers/visibility.js +7 -0
- package/lib/helpers/visibility.js.map +1 -0
- package/lib/hooks/index.d.ts +5 -0
- package/lib/hooks/index.d.ts.map +1 -0
- package/lib/hooks/index.js +5 -0
- package/lib/hooks/index.js.map +1 -0
- package/lib/hooks/useRatio.d.ts +2 -0
- package/lib/hooks/useRatio.d.ts.map +1 -0
- package/lib/hooks/useRatio.js +2 -0
- package/lib/hooks/useRatio.js.map +1 -0
- package/lib/hooks/useRatioEffect.d.ts +5 -0
- package/lib/hooks/useRatioEffect.d.ts.map +1 -0
- package/lib/hooks/useRatioEffect.js +29 -0
- package/lib/hooks/useRatioEffect.js.map +1 -0
- package/lib/hooks/useRatioElement.d.ts +11 -0
- package/lib/hooks/useRatioElement.d.ts.map +1 -0
- package/lib/hooks/useRatioElement.js +47 -0
- package/lib/hooks/useRatioElement.js.map +1 -0
- package/lib/hooks/useResponsive.d.ts +3 -0
- package/lib/hooks/useResponsive.d.ts.map +1 -0
- package/lib/hooks/useResponsive.js +29 -0
- package/lib/hooks/useResponsive.js.map +1 -0
- package/lib/hooks/useResponsiveAnimation.d.ts +3 -0
- package/lib/hooks/useResponsiveAnimation.d.ts.map +1 -0
- package/lib/hooks/useResponsiveAnimation.js +5 -0
- package/lib/hooks/useResponsiveAnimation.js.map +1 -0
- package/lib/index.d.ts +20 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +12 -0
- package/lib/index.js.map +1 -0
- package/lib/layout/ResponsiveLayout.d.ts +39 -0
- package/lib/layout/ResponsiveLayout.d.ts.map +1 -0
- package/lib/layout/ResponsiveLayout.js +48 -0
- package/lib/layout/ResponsiveLayout.js.map +1 -0
- package/lib/layout/ResponsiveLayout.test.d.ts +2 -0
- package/lib/layout/ResponsiveLayout.test.d.ts.map +1 -0
- package/lib/layout/ResponsiveLayout.test.js +65 -0
- package/lib/scene/index.d.ts +3 -0
- package/lib/scene/index.d.ts.map +1 -0
- package/lib/scene/index.js +3 -0
- package/lib/scene/index.js.map +1 -0
- package/lib/scene/integration.d.ts +7 -0
- package/lib/scene/integration.d.ts.map +1 -0
- package/lib/scene/integration.js +7 -0
- package/lib/scene/integration.js.map +1 -0
- package/lib/scene/makeResponsiveScene.d.ts +19 -0
- package/lib/scene/makeResponsiveScene.d.ts.map +1 -0
- package/lib/scene/makeResponsiveScene.js +19 -0
- package/lib/scene/makeResponsiveScene.js.map +1 -0
- package/lib/types.d.ts +35 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +2 -0
- package/lib/types.js.map +1 -0
- package/package.json +42 -0
- package/src/components/ParticleGroup.tsx +36 -0
- package/src/components/RatioLayer.tsx +19 -0
- package/src/components/index.ts +2 -0
- package/src/context/ResponsiveContext.test.ts +112 -0
- package/src/context/ResponsiveContext.ts +146 -0
- package/src/helpers/color.ts +21 -0
- package/src/helpers/filter.ts +18 -0
- package/src/helpers/helpers.test.ts +252 -0
- package/src/helpers/index.ts +7 -0
- package/src/helpers/path.ts +49 -0
- package/src/helpers/position.ts +75 -0
- package/src/helpers/rotation.ts +27 -0
- package/src/helpers/scale.ts +42 -0
- package/src/helpers/visibility.ts +13 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/useRatio.ts +1 -0
- package/src/hooks/useRatioEffect.ts +42 -0
- package/src/hooks/useRatioElement.ts +61 -0
- package/src/hooks/useResponsive.ts +36 -0
- package/src/hooks/useResponsiveAnimation.ts +8 -0
- package/src/index.ts +17 -0
- package/src/layout/ResponsiveLayout.test.ts +82 -0
- package/src/layout/ResponsiveLayout.ts +89 -0
- package/src/scene/index.ts +2 -0
- package/src/scene/integration.ts +13 -0
- package/src/scene/makeResponsiveScene.ts +49 -0
- package/src/types.ts +44 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import {describe, expect, it} from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
detectRatioClass,
|
|
4
|
+
endResponsive,
|
|
5
|
+
getGlobalRatio,
|
|
6
|
+
parseRatioString,
|
|
7
|
+
setGlobalRatio,
|
|
8
|
+
startResponsive,
|
|
9
|
+
useResponsiveContext,
|
|
10
|
+
} from '../context/ResponsiveContext';
|
|
11
|
+
import type {RatioClass} from '../types';
|
|
12
|
+
|
|
13
|
+
describe('ResponsiveContext', () => {
|
|
14
|
+
describe('detectRatioClass', () => {
|
|
15
|
+
it('should detect landscape ratios (ar > 1)', () => {
|
|
16
|
+
expect(detectRatioClass(16 / 9)).toBe('landscape');
|
|
17
|
+
expect(detectRatioClass(4 / 3)).toBe('landscape');
|
|
18
|
+
expect(detectRatioClass(2)).toBe('landscape');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should detect portrait ratios (ar < 1)', () => {
|
|
22
|
+
expect(detectRatioClass(9 / 16)).toBe('portrait');
|
|
23
|
+
expect(detectRatioClass(3 / 4)).toBe('portrait');
|
|
24
|
+
expect(detectRatioClass(0.5)).toBe('portrait');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should detect square ratios (ar === 1)', () => {
|
|
28
|
+
expect(detectRatioClass(1)).toBe('square');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should detect ultrawide ratios (ar > 2)', () => {
|
|
32
|
+
expect(detectRatioClass(21 / 9)).toBe('ultrawide');
|
|
33
|
+
expect(detectRatioClass(3)).toBe('ultrawide');
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('parseRatioString', () => {
|
|
38
|
+
it('should parse colon-separated ratios', () => {
|
|
39
|
+
expect(parseRatioString('16:9')).toBe(16 / 9);
|
|
40
|
+
expect(parseRatioString('9:16')).toBe(9 / 16);
|
|
41
|
+
expect(parseRatioString('4:3')).toBe(4 / 3);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should parse x-separated ratios', () => {
|
|
45
|
+
expect(parseRatioString('16x9')).toBe(16 / 9);
|
|
46
|
+
expect(parseRatioString('9x16')).toBe(9 / 16);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should default to 16:9 for invalid input', () => {
|
|
50
|
+
expect(parseRatioString('invalid')).toBe(16 / 9);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('global state', () => {
|
|
55
|
+
it('should set and get global ratio', () => {
|
|
56
|
+
setGlobalRatio('9x16', 1080, 1920);
|
|
57
|
+
const state = getGlobalRatio();
|
|
58
|
+
|
|
59
|
+
expect(state.ratio).toBe('9x16');
|
|
60
|
+
expect(state.width).toBe(1080);
|
|
61
|
+
expect(state.height).toBe(1920);
|
|
62
|
+
expect(state.ratioClass).toBe('portrait');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should update ratio class when width/height changes', () => {
|
|
66
|
+
setGlobalRatio('16x9', 1920, 1080);
|
|
67
|
+
let state = getGlobalRatio();
|
|
68
|
+
expect(state.ratioClass).toBe('landscape');
|
|
69
|
+
|
|
70
|
+
setGlobalRatio('9x16', 1080, 1920);
|
|
71
|
+
state = getGlobalRatio();
|
|
72
|
+
expect(state.ratioClass).toBe('portrait');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('stack operations', () => {
|
|
77
|
+
it('should push and pop responsive state', () => {
|
|
78
|
+
const state = {
|
|
79
|
+
ratio: '16x9',
|
|
80
|
+
ratioClass: 'landscape' as RatioClass,
|
|
81
|
+
width: 1920,
|
|
82
|
+
height: 1080,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
startResponsive(state);
|
|
86
|
+
const result = useResponsiveContext();
|
|
87
|
+
expect(result.ratio).toBe('16x9');
|
|
88
|
+
|
|
89
|
+
endResponsive(state);
|
|
90
|
+
const afterPop = useResponsiveContext();
|
|
91
|
+
expect(afterPop.ratio).toBe('9x16'); // falls back to global
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should throw on mismatched endResponsive', () => {
|
|
95
|
+
const state1 = {
|
|
96
|
+
ratio: '16x9',
|
|
97
|
+
ratioClass: 'landscape' as RatioClass,
|
|
98
|
+
width: 1920,
|
|
99
|
+
height: 1080,
|
|
100
|
+
};
|
|
101
|
+
const state2 = {
|
|
102
|
+
ratio: '9x16',
|
|
103
|
+
ratioClass: 'portrait' as RatioClass,
|
|
104
|
+
width: 1080,
|
|
105
|
+
height: 1920,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
startResponsive(state1);
|
|
109
|
+
expect(() => endResponsive(state2)).toThrow();
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type {Vector2} from '@efxlab/motion-canvas-core';
|
|
2
|
+
import type {
|
|
3
|
+
RatioClass,
|
|
4
|
+
RatioId,
|
|
5
|
+
ResponsiveRatioConfig,
|
|
6
|
+
ResponsiveRatiosConfig,
|
|
7
|
+
ResponsiveState,
|
|
8
|
+
} from '../types';
|
|
9
|
+
|
|
10
|
+
const ResponsiveStack: ResponsiveState[] = [];
|
|
11
|
+
|
|
12
|
+
// Custom ratio registry
|
|
13
|
+
const CustomRatios: ResponsiveRatiosConfig = {};
|
|
14
|
+
|
|
15
|
+
// Default built-in ratios
|
|
16
|
+
const RATIO_16X9 = '16x9';
|
|
17
|
+
const RATIO_9X16 = '9x16';
|
|
18
|
+
const RATIO_4X3 = '4x3';
|
|
19
|
+
const RATIO_1X1 = '1x1';
|
|
20
|
+
|
|
21
|
+
const DefaultRatios: ResponsiveRatiosConfig = {
|
|
22
|
+
[RATIO_16X9]: {aspect: '16:9'},
|
|
23
|
+
[RATIO_9X16]: {aspect: '9:16'},
|
|
24
|
+
[RATIO_4X3]: {aspect: '4:3'},
|
|
25
|
+
[RATIO_1X1]: {aspect: '1:1'},
|
|
26
|
+
fullwindow: {aspect: 'auto'},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
30
|
+
let globalResponsiveState: ResponsiveState = {
|
|
31
|
+
ratio: '16x9',
|
|
32
|
+
ratioClass: 'landscape',
|
|
33
|
+
width: 1920,
|
|
34
|
+
height: 1080,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export function startResponsive(state: ResponsiveState) {
|
|
38
|
+
ResponsiveStack.push(state);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function endResponsive(state: ResponsiveState) {
|
|
42
|
+
if (ResponsiveStack.pop() !== state) {
|
|
43
|
+
throw new Error('startResponsive/endResponsive called out of order.');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function useResponsiveContext(): ResponsiveState {
|
|
48
|
+
const state = ResponsiveStack.at(-1);
|
|
49
|
+
if (!state) {
|
|
50
|
+
return globalResponsiveState;
|
|
51
|
+
}
|
|
52
|
+
return state;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function getResponsiveState(): ResponsiveState | undefined {
|
|
56
|
+
return ResponsiveStack.at(-1) ?? globalResponsiveState;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function setGlobalRatio(ratio: RatioId, width: number, height: number) {
|
|
60
|
+
const aspectRatio = width / height;
|
|
61
|
+
const ratioClass = detectRatioClass(aspectRatio);
|
|
62
|
+
globalResponsiveState = {ratio, ratioClass, width, height};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function getGlobalRatio(): ResponsiveState {
|
|
66
|
+
return globalResponsiveState;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function initResponsiveFromSize(size: Vector2, ratio: RatioId = '16x9') {
|
|
70
|
+
setGlobalRatio(ratio, size.x, size.y);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function detectRatioClass(aspectRatio: number): RatioClass {
|
|
74
|
+
if (aspectRatio > 2) {
|
|
75
|
+
return 'ultrawide';
|
|
76
|
+
}
|
|
77
|
+
if (aspectRatio > 1) {
|
|
78
|
+
return 'landscape';
|
|
79
|
+
}
|
|
80
|
+
if (aspectRatio === 1) {
|
|
81
|
+
return 'square';
|
|
82
|
+
}
|
|
83
|
+
return 'portrait';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function parseRatioString(ratio: string): number {
|
|
87
|
+
const [w, h] = ratio.split(':').map(Number);
|
|
88
|
+
if (w && h) {
|
|
89
|
+
return w / h;
|
|
90
|
+
}
|
|
91
|
+
const parts = ratio.split('x').map(Number);
|
|
92
|
+
if (parts.length === 2 && parts[0] && parts[1]) {
|
|
93
|
+
return parts[0] / parts[1];
|
|
94
|
+
}
|
|
95
|
+
return 16 / 9;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Register custom ratios for the responsive system.
|
|
100
|
+
* This allows templates to define custom aspect ratios beyond the defaults.
|
|
101
|
+
*
|
|
102
|
+
* @param ratios - Object mapping ratio IDs to their configuration
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* registerCustomRatios({
|
|
107
|
+
* 'cinema': { aspect: '21:9' },
|
|
108
|
+
* 'story': { aspect: '9:18' },
|
|
109
|
+
* 'portrait2': { aspect: '3:4' },
|
|
110
|
+
* });
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export function registerCustomRatios(ratios: ResponsiveRatiosConfig): void {
|
|
114
|
+
Object.assign(CustomRatios, ratios);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get all registered custom ratios.
|
|
119
|
+
*/
|
|
120
|
+
export function getCustomRatios(): ResponsiveRatiosConfig {
|
|
121
|
+
return {...CustomRatios};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get ratio configuration for a specific ratio ID.
|
|
126
|
+
* Checks custom ratios first, then falls back to defaults.
|
|
127
|
+
*/
|
|
128
|
+
export function getRatioConfig(
|
|
129
|
+
ratioId: string,
|
|
130
|
+
): ResponsiveRatioConfig | undefined {
|
|
131
|
+
return CustomRatios[ratioId] ?? DefaultRatios[ratioId];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get all available ratio configurations (custom + default).
|
|
136
|
+
*/
|
|
137
|
+
export function getAllRatioConfigs(): ResponsiveRatiosConfig {
|
|
138
|
+
return {...DefaultRatios, ...CustomRatios};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Clear all custom ratios.
|
|
143
|
+
*/
|
|
144
|
+
export function clearCustomRatios(): void {
|
|
145
|
+
Object.keys(CustomRatios).forEach(key => delete CustomRatios[key]);
|
|
146
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface ColorHelpers {
|
|
2
|
+
tint: (from: string, to: string) => {from: string; to: string};
|
|
3
|
+
highlight: (color: string) => {color: string; duration: number};
|
|
4
|
+
gradient: (colors: string[]) => {colors: string[]; duration: number};
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface StrokeHelpers {
|
|
8
|
+
draw: (duration: number) => {duration: number};
|
|
9
|
+
dash: (length: number, gap: number) => {length: number; gap: number};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const color: ColorHelpers = {
|
|
13
|
+
tint: (from: string, to: string) => ({from, to}),
|
|
14
|
+
highlight: (color: string) => ({color, duration: 0.3}),
|
|
15
|
+
gradient: (colors: string[]) => ({colors, duration: colors.length * 0.5}),
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const stroke: StrokeHelpers = {
|
|
19
|
+
draw: (duration: number) => ({duration}),
|
|
20
|
+
dash: (length: number, gap: number) => ({length, gap}),
|
|
21
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface FilterHelpers {
|
|
2
|
+
blur: (
|
|
3
|
+
from: number,
|
|
4
|
+
to: number,
|
|
5
|
+
) => {from: number; to: number; duration: number};
|
|
6
|
+
brightness: (value: number) => {value: number; duration: number};
|
|
7
|
+
contrast: (value: number) => {value: number; duration: number};
|
|
8
|
+
saturate: (value: number) => {value: number; duration: number};
|
|
9
|
+
grayscale: (value: number) => {value: number; duration: number};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const filter: FilterHelpers = {
|
|
13
|
+
blur: (from: number, to: number) => ({from, to, duration: 1}),
|
|
14
|
+
brightness: (value: number) => ({value, duration: 0.5}),
|
|
15
|
+
contrast: (value: number) => ({value, duration: 0.5}),
|
|
16
|
+
saturate: (value: number) => ({value, duration: 0.5}),
|
|
17
|
+
grayscale: (value: number) => ({value, duration: 0.5}),
|
|
18
|
+
};
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import {describe, expect, it} from 'vitest';
|
|
2
|
+
import {color, stroke} from './color';
|
|
3
|
+
import {filter} from './filter';
|
|
4
|
+
import {path} from './path';
|
|
5
|
+
import {rotate} from './rotation';
|
|
6
|
+
import {scale, transform} from './scale';
|
|
7
|
+
import {opacity} from './visibility';
|
|
8
|
+
|
|
9
|
+
describe('Rotation Helpers', () => {
|
|
10
|
+
describe('rotate.spin', () => {
|
|
11
|
+
it('should convert turns to degrees', () => {
|
|
12
|
+
expect(rotate.spin(1)).toBe(360);
|
|
13
|
+
expect(rotate.spin(2)).toBe(720);
|
|
14
|
+
expect(rotate.spin(0.5)).toBe(180);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('rotate.spinCW', () => {
|
|
19
|
+
it('should return positive degrees', () => {
|
|
20
|
+
expect(rotate.spinCW(1)).toBe(360);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('rotate.spinCCW', () => {
|
|
25
|
+
it('should return negative degrees', () => {
|
|
26
|
+
expect(rotate.spinCCW(1)).toBe(-360);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('rotate.pivot', () => {
|
|
31
|
+
it('should return center pivot', () => {
|
|
32
|
+
expect(rotate.pivot.center()).toEqual({x: 0, y: 0});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should return corner pivots', () => {
|
|
36
|
+
expect(rotate.pivot.topLeft()).toEqual({x: -0.5, y: -0.5});
|
|
37
|
+
expect(rotate.pivot.topRight()).toEqual({x: 0.5, y: -0.5});
|
|
38
|
+
expect(rotate.pivot.bottomLeft()).toEqual({x: -0.5, y: 0.5});
|
|
39
|
+
expect(rotate.pivot.bottomRight()).toEqual({x: 0.5, y: 0.5});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should return custom pivot', () => {
|
|
43
|
+
expect(rotate.pivot.custom(100, 50)).toEqual({x: 100, y: 50});
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('Scale Helpers', () => {
|
|
49
|
+
describe('scale.from', () => {
|
|
50
|
+
it('should create from-to config', () => {
|
|
51
|
+
const result = scale.from(0);
|
|
52
|
+
expect(result.from).toBe(0);
|
|
53
|
+
expect(result.to).toBe(1);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('scale.to', () => {
|
|
58
|
+
it('should create from-to config', () => {
|
|
59
|
+
const result = scale.to(2);
|
|
60
|
+
expect(result.from).toBe(1);
|
|
61
|
+
expect(result.to).toBe(2);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe('scale.pop', () => {
|
|
66
|
+
it('should create bounce keyframes', () => {
|
|
67
|
+
const result = scale.pop();
|
|
68
|
+
expect(result.from).toBe(1);
|
|
69
|
+
expect(result.to).toBe(1);
|
|
70
|
+
expect(result.keyframes).toHaveLength(3);
|
|
71
|
+
expect(result.keyframes[1].scale).toBe(1.1);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('scale.pulse', () => {
|
|
76
|
+
it('should create pulse range', () => {
|
|
77
|
+
const result = scale.pulse(5);
|
|
78
|
+
expect(result.from).toBe(0.5);
|
|
79
|
+
expect(result.to).toBe(1.5);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('transform', () => {
|
|
84
|
+
it('should return transform values', () => {
|
|
85
|
+
expect(transform.scaleX(2)).toBe(2);
|
|
86
|
+
expect(transform.scaleY(2)).toBe(2);
|
|
87
|
+
expect(transform.skewX(30)).toBe(30);
|
|
88
|
+
expect(transform.skewY(30)).toBe(30);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('Visibility Helpers', () => {
|
|
94
|
+
describe('opacity.fadeIn', () => {
|
|
95
|
+
it('should create fade in config', () => {
|
|
96
|
+
const result = opacity.fadeIn(1);
|
|
97
|
+
expect(result.from).toBe(0);
|
|
98
|
+
expect(result.to).toBe(1);
|
|
99
|
+
expect(result.duration).toBe(1);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('opacity.fadeOut', () => {
|
|
104
|
+
it('should create fade out config', () => {
|
|
105
|
+
const result = opacity.fadeOut(1);
|
|
106
|
+
expect(result.from).toBe(1);
|
|
107
|
+
expect(result.to).toBe(0);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('opacity.flash', () => {
|
|
112
|
+
it('should create flash config', () => {
|
|
113
|
+
const result = opacity.flash(3);
|
|
114
|
+
expect(result.times).toBe(3);
|
|
115
|
+
expect(result.duration).toBeCloseTo(0.6);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('opacity.blink', () => {
|
|
120
|
+
it('should create blink config', () => {
|
|
121
|
+
const result = opacity.blink(0.5);
|
|
122
|
+
expect(result.interval).toBe(0.5);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('Color Helpers', () => {
|
|
128
|
+
describe('color.tint', () => {
|
|
129
|
+
it('should create tint config', () => {
|
|
130
|
+
const result = color.tint('red', 'blue');
|
|
131
|
+
expect(result.from).toBe('red');
|
|
132
|
+
expect(result.to).toBe('blue');
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe('color.highlight', () => {
|
|
137
|
+
it('should create highlight config', () => {
|
|
138
|
+
const result = color.highlight('yellow');
|
|
139
|
+
expect(result.color).toBe('yellow');
|
|
140
|
+
expect(result.duration).toBe(0.3);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe('color.gradient', () => {
|
|
145
|
+
it('should create gradient config', () => {
|
|
146
|
+
const result = color.gradient(['red', 'green', 'blue']);
|
|
147
|
+
expect(result.colors).toEqual(['red', 'green', 'blue']);
|
|
148
|
+
expect(result.duration).toBe(1.5);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe('stroke.draw', () => {
|
|
153
|
+
it('should create draw config', () => {
|
|
154
|
+
const result = stroke.draw(2);
|
|
155
|
+
expect(result.duration).toBe(2);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe('stroke.dash', () => {
|
|
160
|
+
it('should create dash config', () => {
|
|
161
|
+
const result = stroke.dash(10, 5);
|
|
162
|
+
expect(result.length).toBe(10);
|
|
163
|
+
expect(result.gap).toBe(5);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe('Path Helpers', () => {
|
|
169
|
+
describe('path.arc', () => {
|
|
170
|
+
it('should create arc config', () => {
|
|
171
|
+
const result = path.arc(0, 180, 100);
|
|
172
|
+
expect(result.startAngle).toBe(0);
|
|
173
|
+
expect(result.endAngle).toBe(180);
|
|
174
|
+
expect(result.radius).toBe(100);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe('path.orbit', () => {
|
|
179
|
+
it('should create orbit config', () => {
|
|
180
|
+
const result = path.orbit(0, 0, 200);
|
|
181
|
+
expect(result.centerX).toBe(0);
|
|
182
|
+
expect(result.centerY).toBe(0);
|
|
183
|
+
expect(result.radius).toBe(200);
|
|
184
|
+
expect(result.duration).toBe(2);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe('path.wave', () => {
|
|
189
|
+
it('should create wave config', () => {
|
|
190
|
+
const result = path.wave(50, 2);
|
|
191
|
+
expect(result.amplitude).toBe(50);
|
|
192
|
+
expect(result.frequency).toBe(2);
|
|
193
|
+
expect(result.duration).toBe(1);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('path.shake', () => {
|
|
198
|
+
it('should create shake config', () => {
|
|
199
|
+
const result = path.shake(10);
|
|
200
|
+
expect(result.intensity).toBe(10);
|
|
201
|
+
expect(result.duration).toBe(0.5);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe('path.wobble', () => {
|
|
206
|
+
it('should create wobble config', () => {
|
|
207
|
+
const result = path.wobble(5);
|
|
208
|
+
expect(result.intensity).toBe(5);
|
|
209
|
+
expect(result.duration).toBe(1);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
describe('Filter Helpers', () => {
|
|
215
|
+
describe('filter.blur', () => {
|
|
216
|
+
it('should create blur config', () => {
|
|
217
|
+
const result = filter.blur(0, 10);
|
|
218
|
+
expect(result.from).toBe(0);
|
|
219
|
+
expect(result.to).toBe(10);
|
|
220
|
+
expect(result.duration).toBe(1);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe('filter.brightness', () => {
|
|
225
|
+
it('should create brightness config', () => {
|
|
226
|
+
const result = filter.brightness(1.5);
|
|
227
|
+
expect(result.value).toBe(1.5);
|
|
228
|
+
expect(result.duration).toBe(0.5);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe('filter.contrast', () => {
|
|
233
|
+
it('should create contrast config', () => {
|
|
234
|
+
const result = filter.contrast(1.2);
|
|
235
|
+
expect(result.value).toBe(1.2);
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
describe('filter.saturate', () => {
|
|
240
|
+
it('should create saturate config', () => {
|
|
241
|
+
const result = filter.saturate(2);
|
|
242
|
+
expect(result.value).toBe(2);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
describe('filter.grayscale', () => {
|
|
247
|
+
it('should create grayscale config', () => {
|
|
248
|
+
const result = filter.grayscale(1);
|
|
249
|
+
expect(result.value).toBe(1);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {Vector2} from '@efxlab/motion-canvas-core';
|
|
2
|
+
|
|
3
|
+
export interface PathHelpers {
|
|
4
|
+
arc: (
|
|
5
|
+
startAngle: number,
|
|
6
|
+
endAngle: number,
|
|
7
|
+
radius: number,
|
|
8
|
+
) => {startAngle: number; endAngle: number; radius: number};
|
|
9
|
+
bezier: (controlPoints: Vector2[]) => {controlPoints: Vector2[]};
|
|
10
|
+
orbit: (
|
|
11
|
+
centerX: number,
|
|
12
|
+
centerY: number,
|
|
13
|
+
radius: number,
|
|
14
|
+
) => {centerX: number; centerY: number; radius: number; duration: number};
|
|
15
|
+
wave: (
|
|
16
|
+
amplitude: number,
|
|
17
|
+
frequency: number,
|
|
18
|
+
) => {amplitude: number; frequency: number; duration: number};
|
|
19
|
+
shake: (intensity: number) => {intensity: number; duration: number};
|
|
20
|
+
wobble: (intensity: number) => {intensity: number; duration: number};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const path: PathHelpers = {
|
|
24
|
+
arc: (startAngle: number, endAngle: number, radius: number) => ({
|
|
25
|
+
startAngle,
|
|
26
|
+
endAngle,
|
|
27
|
+
radius,
|
|
28
|
+
}),
|
|
29
|
+
bezier: (controlPoints: Vector2[]) => ({controlPoints}),
|
|
30
|
+
orbit: (centerX: number, centerY: number, radius: number) => ({
|
|
31
|
+
centerX,
|
|
32
|
+
centerY,
|
|
33
|
+
radius,
|
|
34
|
+
duration: 2,
|
|
35
|
+
}),
|
|
36
|
+
wave: (amplitude: number, frequency: number) => ({
|
|
37
|
+
amplitude,
|
|
38
|
+
frequency,
|
|
39
|
+
duration: 1,
|
|
40
|
+
}),
|
|
41
|
+
shake: (intensity: number) => ({
|
|
42
|
+
intensity,
|
|
43
|
+
duration: 0.5,
|
|
44
|
+
}),
|
|
45
|
+
wobble: (intensity: number) => ({
|
|
46
|
+
intensity,
|
|
47
|
+
duration: 1,
|
|
48
|
+
}),
|
|
49
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {useResponsiveContext} from '../context/ResponsiveContext';
|
|
2
|
+
|
|
3
|
+
export interface PositionHelpers {
|
|
4
|
+
offLeft: (margin?: number) => number;
|
|
5
|
+
offRight: (margin?: number) => number;
|
|
6
|
+
offTop: (margin?: number) => number;
|
|
7
|
+
offBottom: (margin?: number) => number;
|
|
8
|
+
fromLeft: (margin?: number) => number;
|
|
9
|
+
fromRight: (margin?: number) => number;
|
|
10
|
+
fromTop: (margin?: number) => number;
|
|
11
|
+
fromBottom: (margin?: number) => number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function createPositionHelpers(
|
|
15
|
+
elementWidth: number = 0,
|
|
16
|
+
elementHeight: number = 0,
|
|
17
|
+
): PositionHelpers {
|
|
18
|
+
const state = useResponsiveContext();
|
|
19
|
+
const {width, height} = state;
|
|
20
|
+
const hw = width / 2;
|
|
21
|
+
const hh = height / 2;
|
|
22
|
+
const elHalfW = elementWidth / 2;
|
|
23
|
+
const elHalfH = elementHeight / 2;
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
offLeft: (margin = 0) => -hw - elHalfW - margin,
|
|
27
|
+
offRight: (margin = 0) => hw + elHalfW + margin,
|
|
28
|
+
offTop: (margin = 0) => -hh - elHalfH - margin,
|
|
29
|
+
offBottom: (margin = 0) => hh + elHalfH + margin,
|
|
30
|
+
fromLeft: (margin = 0) => -hw + elHalfW + margin,
|
|
31
|
+
fromRight: (margin = 0) => hw - elHalfW - margin,
|
|
32
|
+
fromTop: (margin = 0) => -hh + elHalfH + margin,
|
|
33
|
+
fromBottom: (margin = 0) => hh - elHalfH - margin,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const offLeft = (margin = 0, elementWidth = 0): number => {
|
|
38
|
+
const state = useResponsiveContext();
|
|
39
|
+
return -state.width / 2 - elementWidth / 2 - margin;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const offRight = (margin = 0, elementWidth = 0): number => {
|
|
43
|
+
const state = useResponsiveContext();
|
|
44
|
+
return state.width / 2 + elementWidth / 2 + margin;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const offTop = (margin = 0, elementHeight = 0): number => {
|
|
48
|
+
const state = useResponsiveContext();
|
|
49
|
+
return -state.height / 2 - elementHeight / 2 - margin;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const offBottom = (margin = 0, elementHeight = 0): number => {
|
|
53
|
+
const state = useResponsiveContext();
|
|
54
|
+
return state.height / 2 + elementHeight / 2 + margin;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const fromLeft = (margin = 0, elementWidth = 0): number => {
|
|
58
|
+
const state = useResponsiveContext();
|
|
59
|
+
return -state.width / 2 + elementWidth / 2 + margin;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const fromRight = (margin = 0, elementWidth = 0): number => {
|
|
63
|
+
const state = useResponsiveContext();
|
|
64
|
+
return state.width / 2 - elementWidth / 2 - margin;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const fromTop = (margin = 0, elementHeight = 0): number => {
|
|
68
|
+
const state = useResponsiveContext();
|
|
69
|
+
return -state.height / 2 + elementHeight / 2 + margin;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const fromBottom = (margin = 0, elementHeight = 0): number => {
|
|
73
|
+
const state = useResponsiveContext();
|
|
74
|
+
return state.height / 2 - elementHeight / 2 - margin;
|
|
75
|
+
};
|