@sc4rfurryx/proteusjs 1.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/API.md +438 -0
- package/FEATURES.md +286 -0
- package/LICENSE +21 -0
- package/README.md +645 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/proteus.cjs.js +16014 -0
- package/dist/proteus.cjs.js.map +1 -0
- package/dist/proteus.d.ts +3018 -0
- package/dist/proteus.esm.js +16005 -0
- package/dist/proteus.esm.js.map +1 -0
- package/dist/proteus.esm.min.js +8 -0
- package/dist/proteus.esm.min.js.map +1 -0
- package/dist/proteus.js +16020 -0
- package/dist/proteus.js.map +1 -0
- package/dist/proteus.min.js +8 -0
- package/dist/proteus.min.js.map +1 -0
- package/package.json +98 -0
- package/src/__tests__/mvp-integration.test.ts +518 -0
- package/src/accessibility/AccessibilityEngine.ts +2106 -0
- package/src/accessibility/ScreenReaderSupport.ts +444 -0
- package/src/accessibility/__tests__/ScreenReaderSupport.test.ts +435 -0
- package/src/animations/FLIPAnimationSystem.ts +491 -0
- package/src/compatibility/BrowserCompatibility.ts +1076 -0
- package/src/containers/BreakpointSystem.ts +347 -0
- package/src/containers/ContainerBreakpoints.ts +726 -0
- package/src/containers/ContainerManager.ts +370 -0
- package/src/containers/ContainerUnits.ts +336 -0
- package/src/containers/ContextIsolation.ts +394 -0
- package/src/containers/ElementQueries.ts +411 -0
- package/src/containers/SmartContainer.ts +536 -0
- package/src/containers/SmartContainers.ts +376 -0
- package/src/containers/__tests__/ContainerBreakpoints.test.ts +411 -0
- package/src/containers/__tests__/SmartContainers.test.ts +281 -0
- package/src/content/ResponsiveImages.ts +570 -0
- package/src/core/EventSystem.ts +147 -0
- package/src/core/MemoryManager.ts +321 -0
- package/src/core/PerformanceMonitor.ts +238 -0
- package/src/core/PluginSystem.ts +275 -0
- package/src/core/ProteusJS.test.ts +164 -0
- package/src/core/ProteusJS.ts +962 -0
- package/src/developer/PerformanceProfiler.ts +567 -0
- package/src/developer/VisualDebuggingTools.ts +656 -0
- package/src/developer/ZeroConfigSystem.ts +593 -0
- package/src/index.ts +35 -0
- package/src/integration.test.ts +227 -0
- package/src/layout/AdaptiveGrid.ts +429 -0
- package/src/layout/ContentReordering.ts +532 -0
- package/src/layout/FlexboxEnhancer.ts +406 -0
- package/src/layout/FlowLayout.ts +545 -0
- package/src/layout/SpacingSystem.ts +512 -0
- package/src/observers/IntersectionObserverPolyfill.ts +289 -0
- package/src/observers/ObserverManager.ts +299 -0
- package/src/observers/ResizeObserverPolyfill.ts +179 -0
- package/src/performance/BatchDOMOperations.ts +519 -0
- package/src/performance/CSSOptimizationEngine.ts +646 -0
- package/src/performance/CacheOptimizationSystem.ts +601 -0
- package/src/performance/EfficientEventHandler.ts +740 -0
- package/src/performance/LazyEvaluationSystem.ts +532 -0
- package/src/performance/MemoryManagementSystem.ts +497 -0
- package/src/performance/PerformanceMonitor.ts +931 -0
- package/src/performance/__tests__/BatchDOMOperations.test.ts +309 -0
- package/src/performance/__tests__/EfficientEventHandler.test.ts +268 -0
- package/src/performance/__tests__/PerformanceMonitor.test.ts +422 -0
- package/src/polyfills/BrowserPolyfills.ts +586 -0
- package/src/polyfills/__tests__/BrowserPolyfills.test.ts +328 -0
- package/src/test/setup.ts +115 -0
- package/src/theming/SmartThemeSystem.ts +591 -0
- package/src/types/index.ts +134 -0
- package/src/typography/ClampScaling.ts +356 -0
- package/src/typography/FluidTypography.ts +759 -0
- package/src/typography/LineHeightOptimization.ts +430 -0
- package/src/typography/LineHeightOptimizer.ts +326 -0
- package/src/typography/TextFitting.ts +355 -0
- package/src/typography/TypographicScale.ts +428 -0
- package/src/typography/VerticalRhythm.ts +369 -0
- package/src/typography/__tests__/FluidTypography.test.ts +432 -0
- package/src/typography/__tests__/LineHeightOptimization.test.ts +436 -0
- package/src/utils/Logger.ts +173 -0
- package/src/utils/debounce.ts +259 -0
- package/src/utils/performance.ts +371 -0
- package/src/utils/support.ts +106 -0
- package/src/utils/version.ts +24 -0
@@ -0,0 +1,491 @@
|
|
1
|
+
/**
|
2
|
+
* FLIP Animation System for ProteusJS
|
3
|
+
* First, Last, Invert, Play animations for smooth layout transitions
|
4
|
+
*/
|
5
|
+
|
6
|
+
export interface FLIPConfig {
|
7
|
+
duration: number;
|
8
|
+
easing: string;
|
9
|
+
respectMotionPreference: boolean;
|
10
|
+
batchAnimations: boolean;
|
11
|
+
performanceMode: 'smooth' | 'performance' | 'auto';
|
12
|
+
maxConcurrentAnimations: number;
|
13
|
+
}
|
14
|
+
|
15
|
+
export interface FLIPState {
|
16
|
+
element: Element;
|
17
|
+
first: DOMRect;
|
18
|
+
last: DOMRect;
|
19
|
+
invert: { x: number; y: number; scaleX: number; scaleY: number };
|
20
|
+
player: Animation | null;
|
21
|
+
}
|
22
|
+
|
23
|
+
export class FLIPAnimationSystem {
|
24
|
+
private config: Required<FLIPConfig>;
|
25
|
+
private activeAnimations: Map<Element, FLIPState> = new Map();
|
26
|
+
private animationQueue: (() => Promise<void>)[] = [];
|
27
|
+
private isProcessing: boolean = false;
|
28
|
+
|
29
|
+
constructor(config: Partial<FLIPConfig> = {}) {
|
30
|
+
this.config = {
|
31
|
+
duration: 300,
|
32
|
+
easing: 'cubic-bezier(0.4, 0.0, 0.2, 1)',
|
33
|
+
respectMotionPreference: true,
|
34
|
+
batchAnimations: true,
|
35
|
+
performanceMode: 'auto',
|
36
|
+
maxConcurrentAnimations: 10,
|
37
|
+
...config
|
38
|
+
};
|
39
|
+
}
|
40
|
+
|
41
|
+
/**
|
42
|
+
* Animate element from current position to new position
|
43
|
+
*/
|
44
|
+
public async animate(
|
45
|
+
element: Element,
|
46
|
+
newPosition: () => void,
|
47
|
+
options: Partial<FLIPConfig> = {}
|
48
|
+
): Promise<void> {
|
49
|
+
const config = { ...this.config, ...options };
|
50
|
+
|
51
|
+
if (this.shouldSkipAnimation()) {
|
52
|
+
newPosition();
|
53
|
+
return;
|
54
|
+
}
|
55
|
+
|
56
|
+
// FLIP: First - record initial position
|
57
|
+
const first = element.getBoundingClientRect();
|
58
|
+
|
59
|
+
// FLIP: Last - apply changes and record final position
|
60
|
+
newPosition();
|
61
|
+
const last = element.getBoundingClientRect();
|
62
|
+
|
63
|
+
// FLIP: Invert - calculate the difference
|
64
|
+
const invert = this.calculateInvert(first, last);
|
65
|
+
|
66
|
+
// Skip if no change
|
67
|
+
if (invert.x === 0 && invert.y === 0 && invert.scaleX === 1 && invert.scaleY === 1) {
|
68
|
+
return;
|
69
|
+
}
|
70
|
+
|
71
|
+
// FLIP: Play - animate to final position
|
72
|
+
return this.playAnimation(element, invert, config);
|
73
|
+
}
|
74
|
+
|
75
|
+
/**
|
76
|
+
* Animate multiple elements in batch
|
77
|
+
*/
|
78
|
+
public async animateBatch(
|
79
|
+
animations: Array<{
|
80
|
+
element: Element;
|
81
|
+
newPosition: () => void;
|
82
|
+
options?: Partial<FLIPConfig>;
|
83
|
+
}>
|
84
|
+
): Promise<void> {
|
85
|
+
if (!this.config.batchAnimations) {
|
86
|
+
// Run animations sequentially
|
87
|
+
for (const anim of animations) {
|
88
|
+
await this.animate(anim.element, anim.newPosition, anim.options);
|
89
|
+
}
|
90
|
+
return;
|
91
|
+
}
|
92
|
+
|
93
|
+
// Batch FLIP: First - record all initial positions
|
94
|
+
const flipStates = animations.map(anim => ({
|
95
|
+
element: anim.element,
|
96
|
+
first: anim.element.getBoundingClientRect(),
|
97
|
+
newPosition: anim.newPosition,
|
98
|
+
options: anim.options || {}
|
99
|
+
}));
|
100
|
+
|
101
|
+
// Batch FLIP: Last - apply all changes
|
102
|
+
flipStates.forEach(state => state.newPosition());
|
103
|
+
|
104
|
+
// Batch FLIP: Invert and Play
|
105
|
+
const playPromises = flipStates.map(state => {
|
106
|
+
const last = state.element.getBoundingClientRect();
|
107
|
+
const invert = this.calculateInvert(state.first, last);
|
108
|
+
|
109
|
+
if (invert.x === 0 && invert.y === 0 && invert.scaleX === 1 && invert.scaleY === 1) {
|
110
|
+
return Promise.resolve();
|
111
|
+
}
|
112
|
+
|
113
|
+
const config = { ...this.config, ...state.options };
|
114
|
+
return this.playAnimation(state.element, invert, config);
|
115
|
+
});
|
116
|
+
|
117
|
+
await Promise.all(playPromises);
|
118
|
+
}
|
119
|
+
|
120
|
+
/**
|
121
|
+
* Cancel animation for element
|
122
|
+
*/
|
123
|
+
public cancel(element: Element): void {
|
124
|
+
const flipState = this.activeAnimations.get(element);
|
125
|
+
if (flipState?.player) {
|
126
|
+
flipState.player.cancel();
|
127
|
+
this.activeAnimations.delete(element);
|
128
|
+
}
|
129
|
+
}
|
130
|
+
|
131
|
+
/**
|
132
|
+
* Cancel all active animations
|
133
|
+
*/
|
134
|
+
public cancelAll(): void {
|
135
|
+
this.activeAnimations.forEach((flipState, element) => {
|
136
|
+
this.cancel(element);
|
137
|
+
});
|
138
|
+
}
|
139
|
+
|
140
|
+
/**
|
141
|
+
* Get active animation count
|
142
|
+
*/
|
143
|
+
public getActiveCount(): number {
|
144
|
+
return this.activeAnimations.size;
|
145
|
+
}
|
146
|
+
|
147
|
+
/**
|
148
|
+
* Check if element is animating
|
149
|
+
*/
|
150
|
+
public isAnimating(element: Element): boolean {
|
151
|
+
return this.activeAnimations.has(element);
|
152
|
+
}
|
153
|
+
|
154
|
+
/**
|
155
|
+
* Destroy animation system
|
156
|
+
*/
|
157
|
+
public destroy(): void {
|
158
|
+
this.cancelAll();
|
159
|
+
this.animationQueue = [];
|
160
|
+
}
|
161
|
+
|
162
|
+
/**
|
163
|
+
* Calculate invert values
|
164
|
+
*/
|
165
|
+
private calculateInvert(first: DOMRect, last: DOMRect): {
|
166
|
+
x: number;
|
167
|
+
y: number;
|
168
|
+
scaleX: number;
|
169
|
+
scaleY: number;
|
170
|
+
} {
|
171
|
+
return {
|
172
|
+
x: first.left - last.left,
|
173
|
+
y: first.top - last.top,
|
174
|
+
scaleX: first.width / last.width,
|
175
|
+
scaleY: first.height / last.height
|
176
|
+
};
|
177
|
+
}
|
178
|
+
|
179
|
+
/**
|
180
|
+
* Play FLIP animation
|
181
|
+
*/
|
182
|
+
private async playAnimation(
|
183
|
+
element: Element,
|
184
|
+
invert: { x: number; y: number; scaleX: number; scaleY: number },
|
185
|
+
config: Required<FLIPConfig>
|
186
|
+
): Promise<void> {
|
187
|
+
return new Promise((resolve, reject) => {
|
188
|
+
const htmlElement = element as HTMLElement;
|
189
|
+
|
190
|
+
// Apply initial transform (inverted position)
|
191
|
+
const initialTransform = `translate(${invert.x}px, ${invert.y}px) scale(${invert.scaleX}, ${invert.scaleY})`;
|
192
|
+
htmlElement.style.transform = initialTransform;
|
193
|
+
|
194
|
+
// Create animation
|
195
|
+
const animation = htmlElement.animate([
|
196
|
+
{ transform: initialTransform },
|
197
|
+
{ transform: 'translate(0, 0) scale(1, 1)' }
|
198
|
+
], {
|
199
|
+
duration: config.duration,
|
200
|
+
easing: config.easing,
|
201
|
+
fill: 'forwards'
|
202
|
+
});
|
203
|
+
|
204
|
+
// Store animation state
|
205
|
+
const flipState: FLIPState = {
|
206
|
+
element,
|
207
|
+
first: { x: 0, y: 0, width: 0, height: 0 } as DOMRect,
|
208
|
+
last: { x: 0, y: 0, width: 0, height: 0 } as DOMRect,
|
209
|
+
invert,
|
210
|
+
player: animation
|
211
|
+
};
|
212
|
+
|
213
|
+
this.activeAnimations.set(element, flipState);
|
214
|
+
|
215
|
+
// Handle animation completion
|
216
|
+
animation.addEventListener('finish', () => {
|
217
|
+
htmlElement.style.transform = '';
|
218
|
+
this.activeAnimations.delete(element);
|
219
|
+
resolve();
|
220
|
+
});
|
221
|
+
|
222
|
+
animation.addEventListener('cancel', () => {
|
223
|
+
htmlElement.style.transform = '';
|
224
|
+
this.activeAnimations.delete(element);
|
225
|
+
reject(new Error('Animation cancelled'));
|
226
|
+
});
|
227
|
+
});
|
228
|
+
}
|
229
|
+
|
230
|
+
/**
|
231
|
+
* Check if animations should be skipped
|
232
|
+
*/
|
233
|
+
private shouldSkipAnimation(): boolean {
|
234
|
+
if (this.config.respectMotionPreference) {
|
235
|
+
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
236
|
+
if (prefersReducedMotion) {
|
237
|
+
return true;
|
238
|
+
}
|
239
|
+
}
|
240
|
+
|
241
|
+
if (this.config.performanceMode === 'performance') {
|
242
|
+
return this.activeAnimations.size >= this.config.maxConcurrentAnimations;
|
243
|
+
}
|
244
|
+
|
245
|
+
return false;
|
246
|
+
}
|
247
|
+
}
|
248
|
+
|
249
|
+
/**
|
250
|
+
* Micro-interactions system for subtle UI feedback
|
251
|
+
*/
|
252
|
+
export class MicroInteractions {
|
253
|
+
private static readonly INTERACTIONS = {
|
254
|
+
hover: {
|
255
|
+
scale: 1.05,
|
256
|
+
duration: 200,
|
257
|
+
easing: 'ease-out'
|
258
|
+
},
|
259
|
+
press: {
|
260
|
+
scale: 0.95,
|
261
|
+
duration: 100,
|
262
|
+
easing: 'ease-in'
|
263
|
+
},
|
264
|
+
focus: {
|
265
|
+
scale: 1.02,
|
266
|
+
duration: 150,
|
267
|
+
easing: 'ease-out'
|
268
|
+
}
|
269
|
+
};
|
270
|
+
|
271
|
+
/**
|
272
|
+
* Add hover micro-interaction
|
273
|
+
*/
|
274
|
+
public static addHover(element: Element, options: Partial<typeof MicroInteractions.INTERACTIONS.hover> = {}): void {
|
275
|
+
const config = { ...MicroInteractions.INTERACTIONS.hover, ...options };
|
276
|
+
const htmlElement = element as HTMLElement;
|
277
|
+
|
278
|
+
htmlElement.addEventListener('mouseenter', () => {
|
279
|
+
htmlElement.style.transition = `transform ${config.duration}ms ${config.easing}`;
|
280
|
+
htmlElement.style.transform = `scale(${config.scale})`;
|
281
|
+
});
|
282
|
+
|
283
|
+
htmlElement.addEventListener('mouseleave', () => {
|
284
|
+
htmlElement.style.transform = 'scale(1)';
|
285
|
+
});
|
286
|
+
}
|
287
|
+
|
288
|
+
/**
|
289
|
+
* Add press micro-interaction
|
290
|
+
*/
|
291
|
+
public static addPress(element: Element, options: Partial<typeof MicroInteractions.INTERACTIONS.press> = {}): void {
|
292
|
+
const config = { ...MicroInteractions.INTERACTIONS.press, ...options };
|
293
|
+
const htmlElement = element as HTMLElement;
|
294
|
+
|
295
|
+
htmlElement.addEventListener('mousedown', () => {
|
296
|
+
htmlElement.style.transition = `transform ${config.duration}ms ${config.easing}`;
|
297
|
+
htmlElement.style.transform = `scale(${config.scale})`;
|
298
|
+
});
|
299
|
+
|
300
|
+
htmlElement.addEventListener('mouseup', () => {
|
301
|
+
htmlElement.style.transform = 'scale(1)';
|
302
|
+
});
|
303
|
+
|
304
|
+
htmlElement.addEventListener('mouseleave', () => {
|
305
|
+
htmlElement.style.transform = 'scale(1)';
|
306
|
+
});
|
307
|
+
}
|
308
|
+
|
309
|
+
/**
|
310
|
+
* Add focus micro-interaction
|
311
|
+
*/
|
312
|
+
public static addFocus(element: Element, options: Partial<typeof MicroInteractions.INTERACTIONS.focus> = {}): void {
|
313
|
+
const config = { ...MicroInteractions.INTERACTIONS.focus, ...options };
|
314
|
+
const htmlElement = element as HTMLElement;
|
315
|
+
|
316
|
+
htmlElement.addEventListener('focus', () => {
|
317
|
+
htmlElement.style.transition = `transform ${config.duration}ms ${config.easing}`;
|
318
|
+
htmlElement.style.transform = `scale(${config.scale})`;
|
319
|
+
});
|
320
|
+
|
321
|
+
htmlElement.addEventListener('blur', () => {
|
322
|
+
htmlElement.style.transform = 'scale(1)';
|
323
|
+
});
|
324
|
+
}
|
325
|
+
|
326
|
+
/**
|
327
|
+
* Add ripple effect
|
328
|
+
*/
|
329
|
+
public static addRipple(element: Element, color: string = 'rgba(255, 255, 255, 0.3)'): void {
|
330
|
+
const htmlElement = element as HTMLElement;
|
331
|
+
htmlElement.style.position = 'relative';
|
332
|
+
htmlElement.style.overflow = 'hidden';
|
333
|
+
|
334
|
+
htmlElement.addEventListener('click', (event) => {
|
335
|
+
const rect = htmlElement.getBoundingClientRect();
|
336
|
+
const size = Math.max(rect.width, rect.height);
|
337
|
+
const x = event.clientX - rect.left - size / 2;
|
338
|
+
const y = event.clientY - rect.top - size / 2;
|
339
|
+
|
340
|
+
const ripple = document.createElement('div');
|
341
|
+
ripple.style.cssText = `
|
342
|
+
position: absolute;
|
343
|
+
width: ${size}px;
|
344
|
+
height: ${size}px;
|
345
|
+
left: ${x}px;
|
346
|
+
top: ${y}px;
|
347
|
+
background: ${color};
|
348
|
+
border-radius: 50%;
|
349
|
+
transform: scale(0);
|
350
|
+
animation: ripple 600ms ease-out;
|
351
|
+
pointer-events: none;
|
352
|
+
`;
|
353
|
+
|
354
|
+
// Add ripple animation CSS if not exists
|
355
|
+
if (!document.getElementById('ripple-animation')) {
|
356
|
+
const style = document.createElement('style');
|
357
|
+
style.id = 'ripple-animation';
|
358
|
+
style.textContent = `
|
359
|
+
@keyframes ripple {
|
360
|
+
to {
|
361
|
+
transform: scale(4);
|
362
|
+
opacity: 0;
|
363
|
+
}
|
364
|
+
}
|
365
|
+
`;
|
366
|
+
document.head.appendChild(style);
|
367
|
+
}
|
368
|
+
|
369
|
+
htmlElement.appendChild(ripple);
|
370
|
+
|
371
|
+
setTimeout(() => {
|
372
|
+
ripple.remove();
|
373
|
+
}, 600);
|
374
|
+
});
|
375
|
+
}
|
376
|
+
}
|
377
|
+
|
378
|
+
/**
|
379
|
+
* Scroll-based animations system
|
380
|
+
*/
|
381
|
+
export class ScrollAnimations {
|
382
|
+
private observer: IntersectionObserver | null = null;
|
383
|
+
private animations: Map<Element, () => void> = new Map();
|
384
|
+
|
385
|
+
constructor() {
|
386
|
+
this.setupIntersectionObserver();
|
387
|
+
}
|
388
|
+
|
389
|
+
/**
|
390
|
+
* Add scroll-triggered animation
|
391
|
+
*/
|
392
|
+
public addAnimation(
|
393
|
+
element: Element,
|
394
|
+
animation: () => void,
|
395
|
+
options: IntersectionObserverInit = {}
|
396
|
+
): void {
|
397
|
+
this.animations.set(element, animation);
|
398
|
+
|
399
|
+
if (this.observer) {
|
400
|
+
this.observer.observe(element);
|
401
|
+
}
|
402
|
+
}
|
403
|
+
|
404
|
+
/**
|
405
|
+
* Remove scroll animation
|
406
|
+
*/
|
407
|
+
public removeAnimation(element: Element): void {
|
408
|
+
this.animations.delete(element);
|
409
|
+
|
410
|
+
if (this.observer) {
|
411
|
+
this.observer.unobserve(element);
|
412
|
+
}
|
413
|
+
}
|
414
|
+
|
415
|
+
/**
|
416
|
+
* Destroy scroll animations
|
417
|
+
*/
|
418
|
+
public destroy(): void {
|
419
|
+
this.observer?.disconnect();
|
420
|
+
this.animations.clear();
|
421
|
+
}
|
422
|
+
|
423
|
+
/**
|
424
|
+
* Setup intersection observer
|
425
|
+
*/
|
426
|
+
private setupIntersectionObserver(): void {
|
427
|
+
if (!window.IntersectionObserver) {
|
428
|
+
console.warn('IntersectionObserver not supported');
|
429
|
+
return;
|
430
|
+
}
|
431
|
+
|
432
|
+
this.observer = new IntersectionObserver((entries) => {
|
433
|
+
entries.forEach(entry => {
|
434
|
+
if (entry.isIntersecting) {
|
435
|
+
const animation = this.animations.get(entry.target);
|
436
|
+
if (animation) {
|
437
|
+
animation();
|
438
|
+
// Remove one-time animations
|
439
|
+
this.removeAnimation(entry.target);
|
440
|
+
}
|
441
|
+
}
|
442
|
+
});
|
443
|
+
}, {
|
444
|
+
threshold: 0.1,
|
445
|
+
rootMargin: '50px'
|
446
|
+
});
|
447
|
+
}
|
448
|
+
|
449
|
+
/**
|
450
|
+
* Fade in animation
|
451
|
+
*/
|
452
|
+
public static fadeIn(element: Element, duration: number = 600): void {
|
453
|
+
const htmlElement = element as HTMLElement;
|
454
|
+
htmlElement.style.opacity = '0';
|
455
|
+
htmlElement.style.transition = `opacity ${duration}ms ease-in-out`;
|
456
|
+
|
457
|
+
requestAnimationFrame(() => {
|
458
|
+
htmlElement.style.opacity = '1';
|
459
|
+
});
|
460
|
+
}
|
461
|
+
|
462
|
+
/**
|
463
|
+
* Slide up animation
|
464
|
+
*/
|
465
|
+
public static slideUp(element: Element, duration: number = 600): void {
|
466
|
+
const htmlElement = element as HTMLElement;
|
467
|
+
htmlElement.style.transform = 'translateY(50px)';
|
468
|
+
htmlElement.style.opacity = '0';
|
469
|
+
htmlElement.style.transition = `transform ${duration}ms ease-out, opacity ${duration}ms ease-out`;
|
470
|
+
|
471
|
+
requestAnimationFrame(() => {
|
472
|
+
htmlElement.style.transform = 'translateY(0)';
|
473
|
+
htmlElement.style.opacity = '1';
|
474
|
+
});
|
475
|
+
}
|
476
|
+
|
477
|
+
/**
|
478
|
+
* Scale in animation
|
479
|
+
*/
|
480
|
+
public static scaleIn(element: Element, duration: number = 600): void {
|
481
|
+
const htmlElement = element as HTMLElement;
|
482
|
+
htmlElement.style.transform = 'scale(0.8)';
|
483
|
+
htmlElement.style.opacity = '0';
|
484
|
+
htmlElement.style.transition = `transform ${duration}ms ease-out, opacity ${duration}ms ease-out`;
|
485
|
+
|
486
|
+
requestAnimationFrame(() => {
|
487
|
+
htmlElement.style.transform = 'scale(1)';
|
488
|
+
htmlElement.style.opacity = '1';
|
489
|
+
});
|
490
|
+
}
|
491
|
+
}
|