@twick/visualizer 0.0.1 → 0.14.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/.turbo/turbo-build.log +19 -0
- package/.turbo/turbo-docs.log +7 -0
- package/LICENSE +197 -0
- package/README.md +1 -1
- package/dist/mp4.wasm +0 -0
- package/dist/project.css +1 -0
- package/dist/project.js +145 -0
- package/docs/.nojekyll +1 -0
- package/docs/README.md +13 -0
- package/docs/interfaces/Animation.md +47 -0
- package/docs/interfaces/Element.md +47 -0
- package/docs/interfaces/FrameEffectPlugin.md +47 -0
- package/docs/interfaces/TextEffect.md +47 -0
- package/docs/modules.md +535 -0
- package/package.json +34 -31
- package/src/animations/blur.tsx +60 -0
- package/src/animations/breathe.tsx +60 -0
- package/src/animations/fade.tsx +60 -0
- package/src/animations/photo-rise.tsx +66 -0
- package/src/animations/photo-zoom.tsx +73 -0
- package/src/animations/rise.tsx +118 -0
- package/src/animations/succession.tsx +77 -0
- package/src/components/frame-effects.tsx +2 -4
- package/src/components/track.tsx +232 -0
- package/src/controllers/animation.controller.ts +39 -0
- package/src/controllers/element.controller.ts +43 -0
- package/src/controllers/frame-effect.controller.tsx +30 -0
- package/src/controllers/text-effect.controller.ts +33 -0
- package/src/elements/audio.element.tsx +17 -0
- package/src/elements/caption.element.tsx +87 -0
- package/src/elements/circle.element.tsx +20 -0
- package/src/elements/icon.element.tsx +20 -0
- package/src/elements/image.element.tsx +55 -0
- package/src/elements/rect.element.tsx +22 -0
- package/src/elements/scene.element.tsx +29 -0
- package/src/elements/text.element.tsx +28 -0
- package/src/elements/video.element.tsx +56 -0
- package/src/frame-effects/circle.frame.tsx +103 -0
- package/src/frame-effects/rect.frame.tsx +103 -0
- package/src/helpers/caption.utils.ts +4 -14
- package/src/helpers/constants.ts +1 -1
- package/src/helpers/element.utils.ts +222 -68
- package/src/helpers/filters.ts +1 -1
- package/src/helpers/log.utils.ts +0 -3
- package/src/helpers/timing.utils.ts +2 -21
- package/src/helpers/types.ts +103 -8
- package/src/helpers/utils.ts +20 -0
- package/src/index.ts +4 -2
- package/src/live.tsx +1 -1
- package/src/project.ts +1 -1
- package/src/sample.ts +16 -218
- package/src/text-effects/elastic.tsx +39 -0
- package/src/text-effects/erase.tsx +58 -0
- package/src/text-effects/stream-word.tsx +60 -0
- package/src/text-effects/typewriter.tsx +59 -0
- package/src/visualizer.tsx +27 -27
- package/tsconfig.json +3 -2
- package/vite.config.ts +1 -1
- package/src/components/animation.tsx +0 -7
- package/src/components/element.tsx +0 -344
- package/src/components/timeline.tsx +0 -225
package/src/helpers/types.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import { View2D } from "@twick/2d";
|
|
2
|
+
import { Reference, ThreadGenerator, Vector2 } from "@twick/core";
|
|
3
|
+
|
|
1
4
|
export type VideoInput = {
|
|
2
5
|
backgroundColor: string;
|
|
3
6
|
properties: {
|
|
4
7
|
width: number;
|
|
5
8
|
height: number;
|
|
6
9
|
};
|
|
7
|
-
|
|
10
|
+
tracks: VisualizerTrack[];
|
|
8
11
|
};
|
|
9
12
|
|
|
10
13
|
export type MediaType = "video" | "image";
|
|
@@ -29,12 +32,12 @@ export type Position = {
|
|
|
29
32
|
};
|
|
30
33
|
|
|
31
34
|
export type FrameEffect = {
|
|
35
|
+
name: string;
|
|
32
36
|
s: number;
|
|
33
37
|
e: number;
|
|
34
38
|
props: FrameEffectProps;
|
|
35
39
|
};
|
|
36
40
|
|
|
37
|
-
|
|
38
41
|
export type FrameEffectProps = {
|
|
39
42
|
frameSize: SizeArray;
|
|
40
43
|
frameShape: "circle" | "rect";
|
|
@@ -75,8 +78,9 @@ export type Caption = {
|
|
|
75
78
|
t: string;
|
|
76
79
|
s: number;
|
|
77
80
|
e: number;
|
|
81
|
+
capStyle?: string;
|
|
78
82
|
props?: CaptionProps;
|
|
79
|
-
}
|
|
83
|
+
};
|
|
80
84
|
|
|
81
85
|
export type CaptionProps = {
|
|
82
86
|
colors: CaptionColors;
|
|
@@ -106,25 +110,26 @@ export type CaptionFont = {
|
|
|
106
110
|
|
|
107
111
|
export type VisualizerElement = {
|
|
108
112
|
id: string;
|
|
113
|
+
trackId?: string;
|
|
109
114
|
frame?: any;
|
|
110
115
|
props?: any;
|
|
111
|
-
objectFit?:
|
|
116
|
+
objectFit?: "contain" | "cover" | "fill";
|
|
112
117
|
type?: string;
|
|
113
118
|
s: number;
|
|
114
119
|
e: number;
|
|
115
120
|
backgroundColor?: string;
|
|
116
|
-
|
|
117
|
-
|
|
121
|
+
animation?: AnimationProps;
|
|
122
|
+
textEffect: TextEffectProps;
|
|
123
|
+
frameEffects?: FrameEffect[];
|
|
118
124
|
scale?: number;
|
|
119
125
|
t?: string;
|
|
120
126
|
hWords?: any;
|
|
121
127
|
};
|
|
122
128
|
|
|
123
|
-
export type
|
|
129
|
+
export type VisualizerTrack = {
|
|
124
130
|
id: string;
|
|
125
131
|
type: string;
|
|
126
132
|
elements: VisualizerElement[];
|
|
127
|
-
captions?: Caption[];
|
|
128
133
|
props?: {
|
|
129
134
|
capStyle?: string;
|
|
130
135
|
bgOpacity?: number;
|
|
@@ -144,3 +149,93 @@ export type VisualizerTimeline = {
|
|
|
144
149
|
captionProps?: CaptionProps;
|
|
145
150
|
};
|
|
146
151
|
};
|
|
152
|
+
|
|
153
|
+
export type ElementParams = {
|
|
154
|
+
view: View2D;
|
|
155
|
+
containerRef: Reference<any>;
|
|
156
|
+
element?: VisualizerElement;
|
|
157
|
+
caption?: Caption;
|
|
158
|
+
waitOnStart?: boolean;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export interface Element<Params = ElementParams> {
|
|
162
|
+
name: string;
|
|
163
|
+
create(params: Params): ThreadGenerator;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export type TextEffectParams = {
|
|
167
|
+
elementRef: Reference<any>;
|
|
168
|
+
interval?: number;
|
|
169
|
+
duration?: number;
|
|
170
|
+
bufferTime?: number;
|
|
171
|
+
delay?: number;
|
|
172
|
+
direction?: "left" | "right" | "center";
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export type TextEffectProps = {
|
|
176
|
+
name: string;
|
|
177
|
+
interval?: number;
|
|
178
|
+
duration?: number;
|
|
179
|
+
bufferTime?: number;
|
|
180
|
+
delay?: number;
|
|
181
|
+
direction?: "left" | "right" | "center";
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
export interface TextEffect<Params = TextEffectParams> {
|
|
185
|
+
name: string;
|
|
186
|
+
run(params: Params): Generator;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export type AnimationParams = {
|
|
190
|
+
elementRef: Reference<any>;
|
|
191
|
+
containerRef?: Reference<any>;
|
|
192
|
+
view: View2D;
|
|
193
|
+
interval?: number;
|
|
194
|
+
duration?: number;
|
|
195
|
+
intensity?: number;
|
|
196
|
+
mode?: "in" | "out";
|
|
197
|
+
animate?: "enter" | "exit" | "both";
|
|
198
|
+
direction?: "left" | "right" | "center" | "up" | "down";
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
export type AnimationProps = {
|
|
202
|
+
name: string;
|
|
203
|
+
interval?: number;
|
|
204
|
+
duration?: number;
|
|
205
|
+
intensity?: number;
|
|
206
|
+
mode?: "in" | "out";
|
|
207
|
+
animate?: "enter" | "exit" | "both";
|
|
208
|
+
direction?: "left" | "right" | "center" | "up" | "down";
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
export interface Animation<Params = AnimationParams> {
|
|
212
|
+
name: string;
|
|
213
|
+
run(params: Params): ThreadGenerator;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export type FrameEffectParams = {
|
|
217
|
+
elementRef: Reference<any>;
|
|
218
|
+
containerRef?: Reference<any>;
|
|
219
|
+
initFrameState: FrameState;
|
|
220
|
+
frameEffect: FrameEffect;
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
export interface FrameEffectPlugin<Params = FrameEffectParams> {
|
|
224
|
+
name: string;
|
|
225
|
+
run(params: Params): ThreadGenerator;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export type FrameState = {
|
|
229
|
+
frame: {
|
|
230
|
+
size: Vector2;
|
|
231
|
+
pos: Vector2;
|
|
232
|
+
radius: number;
|
|
233
|
+
scale: Vector2;
|
|
234
|
+
rotation: number;
|
|
235
|
+
};
|
|
236
|
+
element: {
|
|
237
|
+
size: Vector2;
|
|
238
|
+
pos: Vector2;
|
|
239
|
+
scale: Vector2;
|
|
240
|
+
};
|
|
241
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const hexToRGB = (color: string) => {
|
|
2
|
+
// Remove leading '#' if present
|
|
3
|
+
let hex = color.replace(/^#/, '');
|
|
4
|
+
|
|
5
|
+
// Handle shorthand hex (e.g., #abc)
|
|
6
|
+
if (hex.length === 3) {
|
|
7
|
+
hex = hex.split('').map(c => c + c).join('');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (hex.length !== 6) {
|
|
11
|
+
throw new Error('Invalid hex color');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const num = parseInt(hex, 16);
|
|
15
|
+
const r = (num >> 16) & 255;
|
|
16
|
+
const g = (num >> 8) & 255;
|
|
17
|
+
const b = num & 255;
|
|
18
|
+
|
|
19
|
+
return { r, g, b };
|
|
20
|
+
}
|
package/src/index.ts
CHANGED
package/src/live.tsx
CHANGED
package/src/project.ts
CHANGED
package/src/sample.ts
CHANGED
|
@@ -26,7 +26,7 @@ export const sample = {
|
|
|
26
26
|
"orgId": "a251d9971a55"
|
|
27
27
|
},
|
|
28
28
|
"basePath": "carlyn",
|
|
29
|
-
"
|
|
29
|
+
"tracks": [
|
|
30
30
|
{
|
|
31
31
|
"id": "t-scene",
|
|
32
32
|
"type": "scene",
|
|
@@ -45,6 +45,13 @@ export const sample = {
|
|
|
45
45
|
"volume": 1,
|
|
46
46
|
"playbackRate": 2.4
|
|
47
47
|
},
|
|
48
|
+
"animation": {
|
|
49
|
+
"name": "photo-zoom",
|
|
50
|
+
"interval": 1,
|
|
51
|
+
"mode": "out",
|
|
52
|
+
"animate": "both",
|
|
53
|
+
"direction": "top"
|
|
54
|
+
},
|
|
48
55
|
"frame": {
|
|
49
56
|
"size": [
|
|
50
57
|
720,
|
|
@@ -102,130 +109,6 @@ export const sample = {
|
|
|
102
109
|
"x": 269.48206359326264,
|
|
103
110
|
"y": 6.293648227413769
|
|
104
111
|
}
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
"id": "e-8e7713322971",
|
|
108
|
-
"type": "video",
|
|
109
|
-
"s": 9.395,
|
|
110
|
-
"e": 11.045625,
|
|
111
|
-
"objectFit": "cover",
|
|
112
|
-
"props": {
|
|
113
|
-
"time": 0,
|
|
114
|
-
"src": "https://static-assets.kifferai.com/developmen/a251d9971a55/785c23ac-46b9-412e-afa6-a2cb56359409/video_1746791788884-91cb1098-d5f0-4528-83ee-40396774362e-normalized-1747225092682.mp4",
|
|
115
|
-
"play": true,
|
|
116
|
-
"decoder": "web",
|
|
117
|
-
"volume": 1,
|
|
118
|
-
"playbackRate": 1.6
|
|
119
|
-
},
|
|
120
|
-
"frame": {
|
|
121
|
-
"size": [
|
|
122
|
-
2308.2048543926235,
|
|
123
|
-
1295.1593905203056
|
|
124
|
-
],
|
|
125
|
-
"layout": false,
|
|
126
|
-
"clip": "true",
|
|
127
|
-
"rotation": 0,
|
|
128
|
-
"x": -13.276787982361498,
|
|
129
|
-
"y": 7.751279402190107
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
"id": "e-7531e6fa1e0d",
|
|
134
|
-
"type": "video",
|
|
135
|
-
"s": 11.046,
|
|
136
|
-
"e": 12.07,
|
|
137
|
-
"objectFit": "cover",
|
|
138
|
-
"props": {
|
|
139
|
-
"time": 7.721,
|
|
140
|
-
"src": "https://static-assets.kifferai.com/developmen/a251d9971a55/9e75d848-ee72-417c-a0ab-9b03cbe8a8e8/video_1746791788884-91cb1098-d5f0-4528-83ee-40396774362e-normalized-1747225094687.mp4",
|
|
141
|
-
"play": true,
|
|
142
|
-
"decoder": "web",
|
|
143
|
-
"volume": 1,
|
|
144
|
-
"playbackRate": 1.25
|
|
145
|
-
},
|
|
146
|
-
"frame": {
|
|
147
|
-
"size": [
|
|
148
|
-
2311.1861840816873,
|
|
149
|
-
1296.8322477347244
|
|
150
|
-
],
|
|
151
|
-
"layout": false,
|
|
152
|
-
"clip": "true",
|
|
153
|
-
"rotation": 0,
|
|
154
|
-
"x": 449.37247349257666,
|
|
155
|
-
"y": 9.38850926628038
|
|
156
|
-
}
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
"id": "e-3b7b7272a680",
|
|
160
|
-
"type": "video",
|
|
161
|
-
"s": 12.07,
|
|
162
|
-
"e": 12.65,
|
|
163
|
-
"objectFit": "cover",
|
|
164
|
-
"props": {
|
|
165
|
-
"time": 15.761000000000001,
|
|
166
|
-
"src": "https://static-assets.kifferai.com/developmen/a251d9971a55/c8411732-b0c8-45a9-808d-e5adf6396cc0/video_1746791788884-91cb1098-d5f0-4528-83ee-40396774362e-normalized-1747225095328.mp4",
|
|
167
|
-
"play": true,
|
|
168
|
-
"decoder": "web"
|
|
169
|
-
},
|
|
170
|
-
"frame": {
|
|
171
|
-
"size": [
|
|
172
|
-
2350.1907312156336,
|
|
173
|
-
1318.718132515439
|
|
174
|
-
],
|
|
175
|
-
"layout": false,
|
|
176
|
-
"clip": "true",
|
|
177
|
-
"rotation": 0,
|
|
178
|
-
"x": 172.4452216608522,
|
|
179
|
-
"y": 15.93068052161425
|
|
180
|
-
}
|
|
181
|
-
},
|
|
182
|
-
{
|
|
183
|
-
"id": "e-6121e86e1529",
|
|
184
|
-
"type": "video",
|
|
185
|
-
"s": 12.65,
|
|
186
|
-
"e": 12.97,
|
|
187
|
-
"objectFit": "cover",
|
|
188
|
-
"props": {
|
|
189
|
-
"time": 16.467,
|
|
190
|
-
"src": "https://static-assets.kifferai.com/developmen/a251d9971a55/ebdd6f60-5750-4228-a5cd-f321f60664c2/video_1746791788884-91cb1098-d5f0-4528-83ee-40396774362e-normalized-1747225093091.mp4",
|
|
191
|
-
"play": true,
|
|
192
|
-
"decoder": "web"
|
|
193
|
-
},
|
|
194
|
-
"frame": {
|
|
195
|
-
"size": [
|
|
196
|
-
2250.6441550073,
|
|
197
|
-
1262.861442531874
|
|
198
|
-
],
|
|
199
|
-
"layout": false,
|
|
200
|
-
"clip": "true",
|
|
201
|
-
"rotation": 0,
|
|
202
|
-
"x": 349.31120819483976,
|
|
203
|
-
"y": 10.893313528412591
|
|
204
|
-
}
|
|
205
|
-
},
|
|
206
|
-
{
|
|
207
|
-
"id": "e-1b1fe6998ebd",
|
|
208
|
-
"type": "video",
|
|
209
|
-
"s": 12.97,
|
|
210
|
-
"e": 16.644000000000002,
|
|
211
|
-
"objectFit": "cover",
|
|
212
|
-
"props": {
|
|
213
|
-
"time": 23.286,
|
|
214
|
-
"src": "https://static-assets.kifferai.com/developmen/a251d9971a55/d0156321-eb31-4168-adc0-9c4ad1fd3963/video_1746791788884-91cb1098-d5f0-4528-83ee-40396774362e-normalized-1747225092630.mp4",
|
|
215
|
-
"play": true,
|
|
216
|
-
"decoder": "web"
|
|
217
|
-
},
|
|
218
|
-
"frame": {
|
|
219
|
-
"size": [
|
|
220
|
-
2338.697802344495,
|
|
221
|
-
1312.2693224266334
|
|
222
|
-
],
|
|
223
|
-
"layout": false,
|
|
224
|
-
"clip": "true",
|
|
225
|
-
"rotation": 0,
|
|
226
|
-
"x": 105.53672564636827,
|
|
227
|
-
"y": 12.299877537072007
|
|
228
|
-
}
|
|
229
112
|
}
|
|
230
113
|
],
|
|
231
114
|
"name": "scene"
|
|
@@ -239,7 +122,7 @@ export const sample = {
|
|
|
239
122
|
"id": "e-50848c2d8271",
|
|
240
123
|
"type": "audio",
|
|
241
124
|
"s": 0,
|
|
242
|
-
"e":
|
|
125
|
+
"e": 10,
|
|
243
126
|
"props": {
|
|
244
127
|
"time": 0,
|
|
245
128
|
"src": "https://static-assets.kifferai.com/developmen/a251d9971a55/brand-music/Unstoppable-Reprise-909549aa-6807-482b-ab92-bce7e6834fe7.mp3",
|
|
@@ -267,6 +150,10 @@ export const sample = {
|
|
|
267
150
|
"s": 0.009,
|
|
268
151
|
"e": 4.691000000000001,
|
|
269
152
|
"t": "GOD WILL BREAK YOU",
|
|
153
|
+
"textEffect": {
|
|
154
|
+
"name": "elastic",
|
|
155
|
+
"duration": 1,
|
|
156
|
+
},
|
|
270
157
|
"props": {
|
|
271
158
|
"fill": "#ffd700",
|
|
272
159
|
"font": {
|
|
@@ -352,99 +239,10 @@ export const sample = {
|
|
|
352
239
|
"x": 4.522605909576328,
|
|
353
240
|
"y": 81.35589157062759
|
|
354
241
|
}
|
|
355
|
-
},
|
|
356
|
-
{
|
|
357
|
-
"id": "e-7eada9ab2394",
|
|
358
|
-
"type": "text",
|
|
359
|
-
"s": 9.521,
|
|
360
|
-
"e": 10.993,
|
|
361
|
-
"t": "BUT",
|
|
362
|
-
"props": {
|
|
363
|
-
"fill": "#ffd700",
|
|
364
|
-
"font": {
|
|
365
|
-
"size": 48,
|
|
366
|
-
"family": "Poppins"
|
|
367
|
-
},
|
|
368
|
-
"fontSize": 54,
|
|
369
|
-
"fontFamily": "Impact",
|
|
370
|
-
"stroke": "#000000",
|
|
371
|
-
"lineWidth": 0.25,
|
|
372
|
-
"fontWeight": "bold",
|
|
373
|
-
"fontStyle": "Italic"
|
|
374
|
-
}
|
|
375
|
-
},
|
|
376
|
-
{
|
|
377
|
-
"id": "e-03039d49039b",
|
|
378
|
-
"type": "text",
|
|
379
|
-
"s": 11.171,
|
|
380
|
-
"e": 12.831999999999999,
|
|
381
|
-
"t": "IN THE END",
|
|
382
|
-
"props": {
|
|
383
|
-
"fill": "#ffd700",
|
|
384
|
-
"font": {
|
|
385
|
-
"size": 48,
|
|
386
|
-
"family": "Poppins"
|
|
387
|
-
},
|
|
388
|
-
"fontSize": 54,
|
|
389
|
-
"fontFamily": "Impact",
|
|
390
|
-
"stroke": "#000000",
|
|
391
|
-
"lineWidth": 0.25,
|
|
392
|
-
"fontWeight": "bold",
|
|
393
|
-
"fontStyle": "Italic",
|
|
394
|
-
"rotation": 0,
|
|
395
|
-
"x": 2.2613029547882206,
|
|
396
|
-
"y": 108.4745220941702
|
|
397
|
-
}
|
|
398
|
-
},
|
|
399
|
-
{
|
|
400
|
-
"id": "e-91d44d6180f9",
|
|
401
|
-
"type": "text",
|
|
402
|
-
"s": 13.077,
|
|
403
|
-
"e": 16.543,
|
|
404
|
-
"t": "HE WILL MAKE YOU",
|
|
405
|
-
"props": {
|
|
406
|
-
"fill": "#FFFFFF",
|
|
407
|
-
"font": {
|
|
408
|
-
"size": 48,
|
|
409
|
-
"family": "Poppins"
|
|
410
|
-
},
|
|
411
|
-
"fontSize": 54,
|
|
412
|
-
"fontFamily": "Impact",
|
|
413
|
-
"stroke": "#000000",
|
|
414
|
-
"lineWidth": 0.25,
|
|
415
|
-
"fontWeight": "bold",
|
|
416
|
-
"fontStyle": "normal",
|
|
417
|
-
"rotation": 0,
|
|
418
|
-
"x": 0,
|
|
419
|
-
"y": 27.118630523542492
|
|
420
|
-
}
|
|
421
|
-
},
|
|
422
|
-
{
|
|
423
|
-
"id": "e-ebf12eac0207",
|
|
424
|
-
"type": "text",
|
|
425
|
-
"s": 13.811,
|
|
426
|
-
"e": 16.534,
|
|
427
|
-
"t": "UNSTOPPABLE",
|
|
428
|
-
"props": {
|
|
429
|
-
"fill": "#ffd700",
|
|
430
|
-
"font": {
|
|
431
|
-
"size": 48,
|
|
432
|
-
"family": "Poppins"
|
|
433
|
-
},
|
|
434
|
-
"rotation": 0,
|
|
435
|
-
"x": -6.783908864364491,
|
|
436
|
-
"y": 99.43497858632281,
|
|
437
|
-
"fontSize": 58,
|
|
438
|
-
"fontFamily": "Impact",
|
|
439
|
-
"stroke": "#000000",
|
|
440
|
-
"lineWidth": 0.25,
|
|
441
|
-
"fontWeight": "bold",
|
|
442
|
-
"fontStyle": "Italic"
|
|
443
|
-
}
|
|
444
242
|
}
|
|
445
243
|
]
|
|
446
244
|
}
|
|
447
|
-
]
|
|
448
|
-
|
|
449
|
-
|
|
245
|
+
],
|
|
246
|
+
"version": 1
|
|
247
|
+
}
|
|
450
248
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { easeOutElastic, waitFor } from "@twick/core";
|
|
2
|
+
import { TextEffectParams } from "../helpers/types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ElasticEffect applies a scaling animation to text elements
|
|
6
|
+
* with an elastic easing curve for a "pop" or "bounce" effect.
|
|
7
|
+
*
|
|
8
|
+
* Behavior:
|
|
9
|
+
* - Optionally waits for a delay.
|
|
10
|
+
* - Starts at zero scale (invisible).
|
|
11
|
+
* - Scales up to full size with an elastic bounce.
|
|
12
|
+
*
|
|
13
|
+
* @param elementRef - Reference to the text element to animate.
|
|
14
|
+
* @param duration - Duration of the scaling animation.
|
|
15
|
+
* @param delay - Optional delay before the animation starts.
|
|
16
|
+
*/
|
|
17
|
+
export const ElasticEffect = {
|
|
18
|
+
name: "elastic",
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Generator function controlling the elastic text scaling effect.
|
|
22
|
+
*/
|
|
23
|
+
*run({
|
|
24
|
+
elementRef,
|
|
25
|
+
duration,
|
|
26
|
+
delay,
|
|
27
|
+
}: TextEffectParams) {
|
|
28
|
+
// If a delay is specified, wait before starting the animation
|
|
29
|
+
if (delay) {
|
|
30
|
+
yield* waitFor(delay);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Instantly set scale to 0 (invisible)
|
|
34
|
+
elementRef().scale(0);
|
|
35
|
+
|
|
36
|
+
// Animate scaling up to full size using elastic easing
|
|
37
|
+
yield* elementRef().scale(1, duration, easeOutElastic);
|
|
38
|
+
},
|
|
39
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { waitFor } from "@twick/core";
|
|
2
|
+
import { TextEffectParams } from "../helpers/types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* EraseEffect animates text disappearing letter by letter,
|
|
6
|
+
* simulating an "erasing" or backspace effect.
|
|
7
|
+
*
|
|
8
|
+
* Behavior:
|
|
9
|
+
* - Optionally waits for a delay before starting.
|
|
10
|
+
* - Preserves the original element size.
|
|
11
|
+
* - Animates removing one character at a time from the end.
|
|
12
|
+
*
|
|
13
|
+
* @param elementRef - Reference to the text element to animate.
|
|
14
|
+
* @param duration - Total duration of the erasing animation.
|
|
15
|
+
* @param delay - Optional delay before starting.
|
|
16
|
+
* @param bufferTime - Time reserved at the end of animation (default: 0.1).
|
|
17
|
+
*/
|
|
18
|
+
export const EraseEffect = {
|
|
19
|
+
name: "erase",
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Generator function controlling the erase text effect.
|
|
23
|
+
*/
|
|
24
|
+
*run({
|
|
25
|
+
elementRef,
|
|
26
|
+
duration,
|
|
27
|
+
delay,
|
|
28
|
+
bufferTime = 0.1,
|
|
29
|
+
}: TextEffectParams) {
|
|
30
|
+
// Get the full original text
|
|
31
|
+
const fullText = elementRef().text();
|
|
32
|
+
|
|
33
|
+
// Store the original size to avoid resizing during animation
|
|
34
|
+
const size = elementRef().size();
|
|
35
|
+
|
|
36
|
+
// Initialize element: clear text, set fixed size, align left
|
|
37
|
+
elementRef().setText("");
|
|
38
|
+
elementRef().size(size);
|
|
39
|
+
elementRef().textAlign("left");
|
|
40
|
+
|
|
41
|
+
// Wait for the optional initial delay
|
|
42
|
+
if (delay) {
|
|
43
|
+
yield* waitFor(delay);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Compute the time interval between each character removal
|
|
47
|
+
let timeInterval = (duration - bufferTime) / fullText.length;
|
|
48
|
+
|
|
49
|
+
// Optionally wait a bit before starting erasing
|
|
50
|
+
yield* waitFor(timeInterval);
|
|
51
|
+
|
|
52
|
+
// Loop backwards through the text and erase one character at a time
|
|
53
|
+
for (let i = fullText.length; i >= 0; i--) {
|
|
54
|
+
yield* waitFor(timeInterval);
|
|
55
|
+
elementRef().setText(fullText.substring(0, i));
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { waitFor } from "@twick/core";
|
|
2
|
+
import { TextEffectParams } from "../helpers/types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* StreamWordEffect animates text appearing word by word,
|
|
6
|
+
* creating a smooth "typing" or "streaming" effect.
|
|
7
|
+
*
|
|
8
|
+
* Behavior:
|
|
9
|
+
* - Optionally waits for a delay before starting.
|
|
10
|
+
* - Clears the text initially and preserves the original size.
|
|
11
|
+
* - Reveals one word at a time with a consistent interval.
|
|
12
|
+
*
|
|
13
|
+
* @param elementRef - Reference to the text element to animate.
|
|
14
|
+
* @param duration - Total duration of the animation.
|
|
15
|
+
* @param delay - Optional delay before starting.
|
|
16
|
+
* @param bufferTime - Time reserved at the end of animation (default: 0.1).
|
|
17
|
+
*/
|
|
18
|
+
export const StreamWordEffect = {
|
|
19
|
+
name: "stream-word",
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Generator function controlling the word streaming effect.
|
|
23
|
+
*/
|
|
24
|
+
*run({
|
|
25
|
+
elementRef,
|
|
26
|
+
duration,
|
|
27
|
+
delay,
|
|
28
|
+
bufferTime = 0.1,
|
|
29
|
+
}: TextEffectParams) {
|
|
30
|
+
// Retrieve the full text content
|
|
31
|
+
const fullText = elementRef().text();
|
|
32
|
+
|
|
33
|
+
// Store the element's size to avoid resizing during animation
|
|
34
|
+
const size = elementRef().size();
|
|
35
|
+
|
|
36
|
+
// Split the text into words
|
|
37
|
+
const words = fullText.split(" ");
|
|
38
|
+
|
|
39
|
+
// Clear the text and set fixed size
|
|
40
|
+
elementRef().setText("");
|
|
41
|
+
elementRef().size(size);
|
|
42
|
+
|
|
43
|
+
// Wait for optional delay before starting
|
|
44
|
+
if (delay) {
|
|
45
|
+
yield* waitFor(delay);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Align text to the left
|
|
49
|
+
elementRef().textAlign("left");
|
|
50
|
+
|
|
51
|
+
// Calculate the interval between words
|
|
52
|
+
let timeInterval =(duration - bufferTime) / words.length;
|
|
53
|
+
|
|
54
|
+
// Reveal each word one at a time
|
|
55
|
+
for (let i = 0; i < words.length; i++) {
|
|
56
|
+
yield* waitFor(timeInterval);
|
|
57
|
+
elementRef().setText(words.slice(0, i + 1).join(" "));
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
};
|