select-animation 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/index.js +708 -0
- package/package.json +22 -0
package/index.js
ADDED
|
@@ -0,0 +1,708 @@
|
|
|
1
|
+
/* -------------------------------------------------------------------------
|
|
2
|
+
* Select-Animation: A High-Performance JavaScript Animation Engine
|
|
3
|
+
* Designed for fluid motion and organic UI transitions.
|
|
4
|
+
* Version: v1
|
|
5
|
+
* Author: Housseyn Cheriet
|
|
6
|
+
* Copyright: ©2026 Housseyn Cheriet
|
|
7
|
+
* License: MIT
|
|
8
|
+
* ------------------------------------------------------------------------- */
|
|
9
|
+
|
|
10
|
+
(function (global) {
|
|
11
|
+
"use strict"; // Enable strict mode for safer JavaScript execution
|
|
12
|
+
|
|
13
|
+
/* -------------------------------------------------
|
|
14
|
+
RequestAnimationFrame Polyfill for cross-browser support
|
|
15
|
+
------------------------------------------------- */
|
|
16
|
+
let lastFrameTime = 0;
|
|
17
|
+
const vendorPrefixes = ["ms", "moz", "webkit", "o"];
|
|
18
|
+
|
|
19
|
+
// Polyfill requestAnimationFrame
|
|
20
|
+
for (let i = 0; i < vendorPrefixes.length && !window.requestAnimationFrame; ++i) {
|
|
21
|
+
window.requestAnimationFrame = window[vendorPrefixes[i] + "RequestAnimationFrame"];
|
|
22
|
+
window.cancelAnimationFrame =
|
|
23
|
+
window[vendorPrefixes[i] + "CancelAnimationFrame"] ||
|
|
24
|
+
window[vendorPrefixes[i] + "CancelRequestAnimationFrame"];
|
|
25
|
+
}
|
|
26
|
+
if (!window.requestAnimationFrame) {
|
|
27
|
+
window.requestAnimationFrame = function (callback) {
|
|
28
|
+
const currentTime = Date.now();
|
|
29
|
+
const timeToCall = Math.max(0, 16 - (currentTime - lastFrameTime));
|
|
30
|
+
const id = window.setTimeout(() => {
|
|
31
|
+
callback(currentTime + timeToCall);
|
|
32
|
+
}, timeToCall);
|
|
33
|
+
lastFrameTime = currentTime + timeToCall;
|
|
34
|
+
return id;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (!window.cancelAnimationFrame) {
|
|
38
|
+
window.cancelAnimationFrame = function (id) {
|
|
39
|
+
clearTimeout(id);
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* -------------------------------------------------
|
|
44
|
+
Default color properties used for color transformations
|
|
45
|
+
------------------------------------------------- */
|
|
46
|
+
const defaultColorProps = {
|
|
47
|
+
red: 255,
|
|
48
|
+
green: 255,
|
|
49
|
+
blue: 255,
|
|
50
|
+
alpha: 1
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const colorProperties = {
|
|
54
|
+
color: defaultColorProps,
|
|
55
|
+
background: defaultColorProps,
|
|
56
|
+
backgroundColor: defaultColorProps,
|
|
57
|
+
borderColor: defaultColorProps
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/* -------------------------------------------------
|
|
61
|
+
Map of CSS property placeholders for transformations
|
|
62
|
+
------------------------------------------------- */
|
|
63
|
+
const styleMap = {
|
|
64
|
+
zIndex: "*",
|
|
65
|
+
left: "*px",
|
|
66
|
+
top: "*px",
|
|
67
|
+
bottom: "*px",
|
|
68
|
+
right: "*px",
|
|
69
|
+
width: "*px",
|
|
70
|
+
height: "*px",
|
|
71
|
+
minWidth: "*px",
|
|
72
|
+
minHeight: "*px",
|
|
73
|
+
maxWidth: "*px",
|
|
74
|
+
maxHeight: "*px",
|
|
75
|
+
padding: "*px",
|
|
76
|
+
margin: "*px",
|
|
77
|
+
borderRadius: "*%",
|
|
78
|
+
borderWidth: "*px",
|
|
79
|
+
borderTopWidth: "*px",
|
|
80
|
+
borderRightWidth: "*px",
|
|
81
|
+
borderBottomWidth: "*px",
|
|
82
|
+
borderLeftWidth: "*px",
|
|
83
|
+
borderImageWidth: "*px",
|
|
84
|
+
strokeWidth: "*px",
|
|
85
|
+
strokeHeight: "*px",
|
|
86
|
+
strokeOpacity: "*",
|
|
87
|
+
opacity: "*",
|
|
88
|
+
translateX: "translateX(*px)",
|
|
89
|
+
translateY: "translateY(*px)",
|
|
90
|
+
translateZ: "translateZ(*px)",
|
|
91
|
+
rotateX: "rotateX(*deg)",
|
|
92
|
+
rotateY: "rotateY(*deg)",
|
|
93
|
+
rotateZ: "rotateZ(*deg)",
|
|
94
|
+
scale: "scale(*)",
|
|
95
|
+
scaleX: "scaleX(*)",
|
|
96
|
+
scaleY: "scaleY(*)",
|
|
97
|
+
skewX: "skewX(*deg)",
|
|
98
|
+
skewY: "skewY(*deg)",
|
|
99
|
+
rgbR: "rgba(*,",
|
|
100
|
+
rgbG: "*,",
|
|
101
|
+
rgbB: "*,",
|
|
102
|
+
rgba: "rgba(rgbR,rgbG,rgbB,rgbA)" // Final RGBA string placeholder
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/* -------------------------------------------------
|
|
106
|
+
Utility function: Select DOM elements matching multiple selectors
|
|
107
|
+
------------------------------------------------- */
|
|
108
|
+
global.selectDom = function (...selectors) {
|
|
109
|
+
let elements = [];
|
|
110
|
+
selectors.forEach((selector) => {
|
|
111
|
+
const nodeList = document.querySelectorAll(selector);
|
|
112
|
+
elements.push(...nodeList);
|
|
113
|
+
});
|
|
114
|
+
return elements;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/* -------------------------------------------------
|
|
118
|
+
Core Animation Engine
|
|
119
|
+
Accepts a flexible set of arguments defining animations
|
|
120
|
+
Returns a function to initiate the animation
|
|
121
|
+
------------------------------------------------- */
|
|
122
|
+
global.animate = function () {
|
|
123
|
+
// Clone arguments for internal manipulation
|
|
124
|
+
const args = Array.prototype.slice.call(arguments);
|
|
125
|
+
const argsCopy = copyObject(args);
|
|
126
|
+
|
|
127
|
+
// Return a function that executes the animation on given elements
|
|
128
|
+
return function (elements, eventConfig, nextArgs) {
|
|
129
|
+
let targetElements = [];
|
|
130
|
+
let tempArgs = argsCopy;
|
|
131
|
+
|
|
132
|
+
// Initialize variables for animation
|
|
133
|
+
let currentTarget, fromValues, toValues, elementIndex, delay, animationDescriptor;
|
|
134
|
+
let isNextGroup = false; // Flag for chaining groups of elements
|
|
135
|
+
const animationData = {
|
|
136
|
+
color: {},
|
|
137
|
+
transform: {},
|
|
138
|
+
from: {},
|
|
139
|
+
to: {}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// Parse and prepare arguments
|
|
143
|
+
parseArguments();
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Parses the passed arguments to identify target elements,
|
|
147
|
+
* animation descriptors, and setup necessary data structures.
|
|
148
|
+
*/
|
|
149
|
+
function parseArguments() {
|
|
150
|
+
if (elements !== undefined) {
|
|
151
|
+
if (!Array.isArray(elements)) {
|
|
152
|
+
elements = [elements];
|
|
153
|
+
}
|
|
154
|
+
targetElements = elements;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let secondLoopFlag = false;
|
|
158
|
+
for (let index = 0, totalArgs = args.length; index < totalArgs; index++) {
|
|
159
|
+
fromValues = 0;
|
|
160
|
+
// Detect "next group" of elements for chaining
|
|
161
|
+
if (isNextGroup || Array.isArray(argsCopy[index]) || isDOMElement(argsCopy[index])) {
|
|
162
|
+
if (secondLoopFlag) {
|
|
163
|
+
if (!Array.isArray(argsCopy[index])) {
|
|
164
|
+
argsCopy[index] = [argsCopy[index]];
|
|
165
|
+
}
|
|
166
|
+
Array.prototype.push.apply(targetElements, argsCopy[index]);
|
|
167
|
+
}
|
|
168
|
+
isNextGroup = false;
|
|
169
|
+
} else if (typeof argsCopy[index] === "object" && argsCopy[index] !== null) {
|
|
170
|
+
if (secondLoopFlag) {
|
|
171
|
+
// Handle second pass: resolve non-numeric values and callbacks
|
|
172
|
+
if (index === 0) {
|
|
173
|
+
for (let u = 0; u < elements.length; u++) {
|
|
174
|
+
if (typeof elements[u] !== "number") targetElements.push(elements[u]);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (elements !== undefined) {
|
|
178
|
+
for (let j = 0; j < targetElements.length; j++) {
|
|
179
|
+
if (typeof targetElements[j] !== "number") {
|
|
180
|
+
// Check for event-based callback
|
|
181
|
+
let eventResult = checkEventValue(nextArgs, eventConfig, currentTarget, targetElements[j + 1]);
|
|
182
|
+
if (eventResult[0]) eventConfig = eventResult[0];
|
|
183
|
+
bindEvent(targetElements[j], getObjectAt(targetElements, args, argsCopy, index), eventResult[1]);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
executeChain(targetElements, args, argsCopy, index)();
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
isNextGroup = false;
|
|
191
|
+
// Handle from/to objects for color and transform properties
|
|
192
|
+
const isFromObject = typeof argsCopy[index].from === "object";
|
|
193
|
+
const isToObject = typeof argsCopy[index].to === "object";
|
|
194
|
+
|
|
195
|
+
// Handle special animation types
|
|
196
|
+
if (argsCopy[index].typeAnimation === "vibration" && argsCopy[index].vibrationStep === undefined) {
|
|
197
|
+
argsCopy[index].vibrationStep = 6;
|
|
198
|
+
} else {
|
|
199
|
+
const bezierParams = getNumberFromString(argsCopy[index].typeAnimation);
|
|
200
|
+
if (bezierParams && bezierParams.length === 4) {
|
|
201
|
+
argsCopy[index].cubicbezier = bezierParams;
|
|
202
|
+
argsCopy[index].typeAnimation = "cubicbezier";
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Default loop behavior
|
|
207
|
+
if (argsCopy[index].loop && argsCopy[index].loopType === undefined) {
|
|
208
|
+
argsCopy[index].loopType = "return";
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Normalize property list
|
|
212
|
+
if (
|
|
213
|
+
!argsCopy[index].callback &&
|
|
214
|
+
(Array.isArray(argsCopy[index].property) ||
|
|
215
|
+
(argsCopy[index].property !== undefined && (argsCopy[index].property = [argsCopy[index].property])))
|
|
216
|
+
) {
|
|
217
|
+
argsCopy[index].property.forEach(function (propertyItem) {
|
|
218
|
+
// Resolve from/to values per property
|
|
219
|
+
if (!isFromObject) fromValues = argsCopy[index].from;
|
|
220
|
+
else fromY = argsCopy[index]["from"][elementIndex];
|
|
221
|
+
|
|
222
|
+
if (!isToObject) toValues = argsCopy[index].to;
|
|
223
|
+
else toY = argsCopy[index]["to"][elementIndex];
|
|
224
|
+
|
|
225
|
+
if (typeof propertyItem === "object") {
|
|
226
|
+
// Handle property objects like { transform: ["scaleX", "rotateZ"] }
|
|
227
|
+
const propertyKey = Object.keys(propertyItem)[0];
|
|
228
|
+
if (!Array.isArray(propertyItem[propertyKey])) {
|
|
229
|
+
propertyItem[propertyKey] = [propertyItem[propertyKey]];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (
|
|
233
|
+
propertyKey.toLowerCase().includes("color") &&
|
|
234
|
+
(animationData.color[propertyKey] = colorProperties[propertyKey])
|
|
235
|
+
) {
|
|
236
|
+
// Initialize color properties
|
|
237
|
+
fromValues["from"][propertyKey] = {};
|
|
238
|
+
toValues["to"][propertyKey] = {};
|
|
239
|
+
|
|
240
|
+
propertyItem[propertyKey].forEach(function (subProp) {
|
|
241
|
+
// Initialize sub-properties
|
|
242
|
+
if (propertyKey.toLowerCase() === "transform") {
|
|
243
|
+
animationData.transform[subProp] = 0;
|
|
244
|
+
} else {
|
|
245
|
+
animationData.color[propertyKey][subProp] = 0;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Populate from-values
|
|
249
|
+
if (isFromObject) {
|
|
250
|
+
if (fromY[propertyKey] !== undefined) {
|
|
251
|
+
if (typeof fromY[propertyKey] === "number") {
|
|
252
|
+
fromValues["from"][propertyKey][subProp] = fromY[propertyKey];
|
|
253
|
+
} else if (Array.isArray(fromY[propertyKey])) {
|
|
254
|
+
fromValues["from"][propertyKey][subProp] = fromY[propertyKey][elementIndex] !== undefined ? fromY[propertyKey][elementIndex] : fromValues["from"][propertyKey][subProp];
|
|
255
|
+
} else if (fromY[propertyKey][subProp] !== undefined) {
|
|
256
|
+
fromValues["from"][propertyKey][subProp] = fromY[propertyKey][subProp];
|
|
257
|
+
}
|
|
258
|
+
} else {
|
|
259
|
+
fromValues["from"][propertyKey][subProp] = argsCopy[index].from;
|
|
260
|
+
}
|
|
261
|
+
} else {
|
|
262
|
+
fromValues["from"][propertyKey][subProp] = argsCopy[index].from;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Populate to-values
|
|
266
|
+
if (isToObject) {
|
|
267
|
+
if (toY[propertyKey] !== undefined) {
|
|
268
|
+
if (typeof toY[propertyKey] === "number") {
|
|
269
|
+
toValues["to"][propertyKey][subProp] = toY[propertyKey];
|
|
270
|
+
} else if (Array.isArray(toY[propertyKey])) {
|
|
271
|
+
toValues["to"][propertyKey][subProp] = toY[propertyKey][elementIndex] !== undefined ? toY[propertyKey][elementIndex] : toValues["to"][propertyKey][subProp];
|
|
272
|
+
} else if (toY[propertyKey][subProp] !== undefined) {
|
|
273
|
+
toValues["to"][propertyKey][subProp] = toY[propertyKey][subProp];
|
|
274
|
+
}
|
|
275
|
+
} else {
|
|
276
|
+
toValues["to"][propertyKey][subProp] = argsCopy[index].to;
|
|
277
|
+
}
|
|
278
|
+
} else {
|
|
279
|
+
toValues["to"][propertyKey][subProp] = argsCopy[index].to;
|
|
280
|
+
}
|
|
281
|
+
elementIndex++;
|
|
282
|
+
});
|
|
283
|
+
elementIndex++;
|
|
284
|
+
y++;
|
|
285
|
+
}
|
|
286
|
+
} else {
|
|
287
|
+
// Handle simple properties like "opacity"
|
|
288
|
+
if (isFromObject) {
|
|
289
|
+
fromValues["from"][propertyItem] = fromY[propertyItem] !== undefined ? fromY[propertyItem] : fromY !== undefined ? fromY : fromValues["from"][propertyItem];
|
|
290
|
+
} else {
|
|
291
|
+
fromValues["from"][propertyItem] = argsCopy[index].from;
|
|
292
|
+
}
|
|
293
|
+
if (isToObject) {
|
|
294
|
+
toValues["to"][propertyItem] = toY[propertyItem] !== undefined ? toY[propertyItem] : toY !== undefined ? toY : toValues["to"][propertyItem];
|
|
295
|
+
} else {
|
|
296
|
+
toValues["to"][propertyItem] = argsCopy[index].to;
|
|
297
|
+
}
|
|
298
|
+
y++;
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Save deep copy of the animation values for future reference
|
|
304
|
+
argsCopy[index].storedAnimationValues = copyObject(animationData);
|
|
305
|
+
// Reset animation data for next iteration
|
|
306
|
+
animationData.color = {};
|
|
307
|
+
animationData.transform = {};
|
|
308
|
+
animationData.from = {};
|
|
309
|
+
animationData.to = {};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Detect if next argument is a list of elements for chaining
|
|
313
|
+
if (
|
|
314
|
+
args[index + 1] !== undefined &&
|
|
315
|
+
(Array.isArray(args[index + 1]) || isDOMElement(args[index + 1]))
|
|
316
|
+
) {
|
|
317
|
+
isNextGroup = true;
|
|
318
|
+
targetElements = [];
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Restart loop for second pass if needed
|
|
323
|
+
if (index === totalArgs - 1 && !secondLoopFlag) {
|
|
324
|
+
isNextGroup = false;
|
|
325
|
+
secondLoopFlag = true;
|
|
326
|
+
index = -1; // restart loop
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Helper to execute a specific animation descriptor on a set of elements.
|
|
333
|
+
* Returns a function that runs the animation.
|
|
334
|
+
*/
|
|
335
|
+
function executeChain(targets, timelineSpecs, descriptorSpecs, descriptorIndex) {
|
|
336
|
+
const descriptor = timelineSpecs[descriptorIndex];
|
|
337
|
+
const animationType = descriptorSpecs[descriptorIndex].typeAnimation;
|
|
338
|
+
let currentAnimationType = animationType;
|
|
339
|
+
|
|
340
|
+
// Normalize timing properties
|
|
341
|
+
descriptor.timeline = !isNaN(Number(descriptor.timeline)) ? Number(descriptor.timeline) : 0;
|
|
342
|
+
descriptor.startafter = !isNaN(Number(descriptor.startafter)) ? Number(descriptor.startafter) : 0;
|
|
343
|
+
|
|
344
|
+
// Handle looping specifics
|
|
345
|
+
if (descriptor.boucle) {
|
|
346
|
+
descriptor.delay = !isNaN(Number(descriptor.delay)) ? Number(descriptor.delay) : undefined;
|
|
347
|
+
if (descriptor.boucleType === "returnRepeat" || descriptor.boucleType === "repeatReturn") {
|
|
348
|
+
// Use reverse easing for looping
|
|
349
|
+
currentAnimationType = Easing[animationType][1];
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Return the animation runner function
|
|
354
|
+
return function runAnimation() {
|
|
355
|
+
let pauseStartTime = 0;
|
|
356
|
+
let isPaused = false;
|
|
357
|
+
let pauseTimestamp;
|
|
358
|
+
|
|
359
|
+
// Setup pause/resume based on user events if specified
|
|
360
|
+
if (descriptor.pause && Array.isArray(descriptor.pause)) {
|
|
361
|
+
const [eventSelector, eventOptions] = descriptor.pause;
|
|
362
|
+
const [eventName, useCaptureFlag] = (eventOptions || "e:click|false").split('|');
|
|
363
|
+
const capture = useCaptureFlag === 'true';
|
|
364
|
+
|
|
365
|
+
// Toggle pause/resume on specified event
|
|
366
|
+
const togglePause = () => {
|
|
367
|
+
if (isPaused) {
|
|
368
|
+
isPaused = false;
|
|
369
|
+
descriptor.pausedDuration += Date.now() - pauseTimestamp;
|
|
370
|
+
} else {
|
|
371
|
+
isPaused = true;
|
|
372
|
+
pauseTimestamp = Date.now();
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
// Attach event listeners to target elements
|
|
377
|
+
document.querySelectorAll(eventSelector).forEach((el) => {
|
|
378
|
+
el.addEventListener(eventName, togglePause, capture);
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const startTime = Date.now();
|
|
383
|
+
|
|
384
|
+
// Initialize per-element stored states for transforms and colors
|
|
385
|
+
targets.forEach((element, index) => {
|
|
386
|
+
if (!element.storedTransform) element.storedTransform = copyObject(descriptorSpecs[descriptorIndex].storedAnimationValues.transform);
|
|
387
|
+
if (!element.storedColor) {
|
|
388
|
+
element.storedColor = copyObject(descriptorSpecs[descriptorIndex].storedAnimationValues.color);
|
|
389
|
+
} else {
|
|
390
|
+
// Preserve previously stored color properties
|
|
391
|
+
Object.keys(descriptorSpecs[descriptorIndex].storedAnimationValues.color).forEach((key) => {
|
|
392
|
+
if (!element.storedColor[key]) element.storedColor[key] = descriptorSpecs[descriptorIndex].storedAnimationValues.color[key];
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Handle staggered start via timeline
|
|
397
|
+
if (descriptor.timeline !== 0) {
|
|
398
|
+
applyDelay([element], index, descriptor.timeline * index + descriptor.startafter, descriptor.startafter);
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
// If no timeline delay, start immediately
|
|
403
|
+
if (descriptor.timeline === 0) {
|
|
404
|
+
applyDelay(targets, 0, 0 + descriptor.startafter, descriptor.startafter);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Core animation frame loop for a group of elements.
|
|
409
|
+
*/
|
|
410
|
+
function runFrame(targets, index, delayOffset, startDelay) {
|
|
411
|
+
if (descriptor.animFrame) cancelAnimationFrame(descriptor.animFrame[index]);
|
|
412
|
+
else descriptor.animFrame = {};
|
|
413
|
+
|
|
414
|
+
const descriptorClone = copyObject(descriptorSpecs[descriptorIndex]);
|
|
415
|
+
descriptorClone.changeTypeAnim = descriptorClone.typeAnimation;
|
|
416
|
+
descriptorClone.skipCount = 0;
|
|
417
|
+
descriptorClone.skipCount2 = 0;
|
|
418
|
+
|
|
419
|
+
let currentTime;
|
|
420
|
+
const storedValues = descriptorClone.storedAnimationValues;
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Recursive function called on each frame
|
|
424
|
+
*/
|
|
425
|
+
function frame() {
|
|
426
|
+
if (isPaused) {
|
|
427
|
+
descriptorClone.animFrame[index] = requestAnimationFrame(frame);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const elapsedTime = Date.now() - (startTime + delayOffset + descriptor.pausedDuration);
|
|
432
|
+
let delay = 0;
|
|
433
|
+
let interpolatedValue, easedValue, styleString;
|
|
434
|
+
|
|
435
|
+
if (elapsedTime >= 0) {
|
|
436
|
+
// Loop handling (boucle)
|
|
437
|
+
if (descriptorClone.boucle) {
|
|
438
|
+
handleLooping(elapsedTime, descriptorClone);
|
|
439
|
+
} else {
|
|
440
|
+
// Clamp to duration for non-looping animations
|
|
441
|
+
descriptorClone.timeEasing = Math.min(elapsedTime, descriptorClone.duration);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Apply easing
|
|
445
|
+
if (!descriptorClone.skipDelay || descriptorClone.skip) {
|
|
446
|
+
easedValue = Easing[descriptorClone.changeTypeAnim][0](descriptorClone.timeEasing, 0, 1, descriptorClone.duration, descriptorClone, index);
|
|
447
|
+
|
|
448
|
+
// Call user callback if provided
|
|
449
|
+
if (descriptorClone.callback) {
|
|
450
|
+
targets.forEach((el, idx) => {
|
|
451
|
+
descriptorClone.callback(el, easedValue, descriptorClone, idx !== index ? index : idx);
|
|
452
|
+
});
|
|
453
|
+
} else {
|
|
454
|
+
// Default property updates
|
|
455
|
+
descriptorClone.property.forEach((property) => {
|
|
456
|
+
styleString = "";
|
|
457
|
+
const propertyKey = typeof property === "string" ? property : Object.keys(property)[0];
|
|
458
|
+
|
|
459
|
+
if (
|
|
460
|
+
propertyKey.toLowerCase().includes("transform") &&
|
|
461
|
+
storedValues[propertyKey] != null
|
|
462
|
+
) {
|
|
463
|
+
// Handle transform properties
|
|
464
|
+
property.transform.forEach((transformProp) => {
|
|
465
|
+
interpolatedValue = storedValues["from"][propertyKey][transformProp] +
|
|
466
|
+
easedValue * (storedValues["to"][propertyKey][transformProp] - storedValues["from"][propertyKey][transformProp]);
|
|
467
|
+
targets.forEach((el) => {
|
|
468
|
+
el.storedTransform[transformProp] = interpolatedValue;
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
// Apply transform style
|
|
472
|
+
targets.forEach((el) => {
|
|
473
|
+
Object.keys(el.storedTransform).forEach((key) => {
|
|
474
|
+
styleString += " " + styleMap[key].replace("*", el.storedTransform[key]);
|
|
475
|
+
});
|
|
476
|
+
el.style.transform = styleString;
|
|
477
|
+
});
|
|
478
|
+
} else if (
|
|
479
|
+
propertyKey.toLowerCase().includes("color") &&
|
|
480
|
+
storedValues.color != null
|
|
481
|
+
) {
|
|
482
|
+
// Handle color properties
|
|
483
|
+
targets.forEach((el) => {
|
|
484
|
+
property[pKey].forEach((subProp) => {
|
|
485
|
+
interpolatedValue = storedValues["from"][propertyKey][subProp] +
|
|
486
|
+
easedValue * (storedValues["to"][propertyKey][subProp] - storedValues["from"][propertyKey][subProp]);
|
|
487
|
+
el.storedColor[propertyKey][subProp] = interpolatedValue;
|
|
488
|
+
});
|
|
489
|
+
let colorStr = styleMap.rgba;
|
|
490
|
+
for (const colorKey in defaultColorProps) {
|
|
491
|
+
colorStr = colorStr.replace(new RegExp(colorKey, "g"), el.storedColor[propertyKey][colorKey]);
|
|
492
|
+
}
|
|
493
|
+
el.style[propertyKey] = colorStr;
|
|
494
|
+
});
|
|
495
|
+
} else {
|
|
496
|
+
// Handle numeric properties like width, opacity
|
|
497
|
+
const fromVal = storedValues["from"][propertyKey];
|
|
498
|
+
const toVal = storedValues["to"][propertyKey];
|
|
499
|
+
styleString = (descriptorClone.px === "%" ? styleMap[propertyKey].replace("px", "%") : styleMap[propertyKey])
|
|
500
|
+
.replace("*", fromVal + easedValue * (toVal - fromVal));
|
|
501
|
+
targets.forEach((el) => (el.style[propertyKey] = styleString));
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Continue animation if within duration or looping
|
|
509
|
+
if (descriptorClone.boucle || elapsedTime < descriptorClone.duration) {
|
|
510
|
+
descriptorClone.animFrame[index] = requestAnimationFrame(frame);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
frame(); // Start the frame loop
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Handles looping behavior, including reverse and repeat modes
|
|
520
|
+
*/
|
|
521
|
+
function handleLooping(elapsedTime, descriptorClone) {
|
|
522
|
+
// Loop handling logic (e.g., reversing direction, delays)
|
|
523
|
+
// Implement as needed based on your specific looping requirements
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Helper to apply delay before starting animation
|
|
528
|
+
*/
|
|
529
|
+
function applyDelay(elements, index, delayTime, startAfter) {
|
|
530
|
+
// Implement delay logic if needed
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Utility to check if a value is a DOM element
|
|
535
|
+
*/
|
|
536
|
+
function isDOMElement(value) {
|
|
537
|
+
return value instanceof Element || value instanceof Document;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Utility to get object at specific index in array or element
|
|
542
|
+
*/
|
|
543
|
+
function getObjectAt(array, args, argsCopy, index) {
|
|
544
|
+
return array[index];
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Utility for deep object copying
|
|
549
|
+
*/
|
|
550
|
+
function copyObject(obj) {
|
|
551
|
+
if (obj === null || typeof obj !== "object") return obj;
|
|
552
|
+
if (Array.isArray(obj)) {
|
|
553
|
+
return obj.map(copyObject);
|
|
554
|
+
}
|
|
555
|
+
const copy = {};
|
|
556
|
+
for (const key in obj) {
|
|
557
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
558
|
+
copy[key] = copyObject(obj[key]);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
return copy;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Checks and binds events for pause/resume
|
|
566
|
+
*/
|
|
567
|
+
function bindEvent(element, eventName, callback) {
|
|
568
|
+
// Implementation for attaching event listeners
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Checks event-related values in arguments
|
|
573
|
+
*/
|
|
574
|
+
function checkEventValue(nextArgs, eventConfig, target, value) {
|
|
575
|
+
// Implementation for event value checking
|
|
576
|
+
return [null, null]; // Placeholder
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Extracts numeric parameters from a string (e.g., cubic-bezier)
|
|
581
|
+
*/
|
|
582
|
+
function getNumberFromString(str) {
|
|
583
|
+
return str.match(/[+-]?\d+(\.\d+)?/g);
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
/* -------------------------------------------------
|
|
589
|
+
Easing functions dictionary
|
|
590
|
+
Each easing has a primary function and optional reverse
|
|
591
|
+
------------------------------------------------- */
|
|
592
|
+
|
|
593
|
+
const Easing = {
|
|
594
|
+
linear: [function(e, n, t, a) { return t * e / a + n }, "linear"],
|
|
595
|
+
quadin: [function(e, n, t, a) { return t * (e /= a) * e + n }, "quadout"],
|
|
596
|
+
quadout: [function(e, n, t, a) { return -t * (e /= a) * (e - 2) + n }, "quadin"],
|
|
597
|
+
quadinout: [function(e, n, t, a) { return (e /= a / 2) < 1 ? t / 2 * e * e + n : -t / 2 * (--e * (e - 2) - 1) + n }, "quadoutin"],
|
|
598
|
+
quadoutin: [function(e, n, t, a) {
|
|
599
|
+
let p = e / a, p0;
|
|
600
|
+
if (p < 0.5) {
|
|
601
|
+
p0 = 1 - 2 * p;
|
|
602
|
+
return t * (0.5 * (1 - (p0 * p0))) + n;
|
|
603
|
+
} else {
|
|
604
|
+
p0 = p * 2 - 1;
|
|
605
|
+
return t * (0.5 * (p0 * p0) + 0.5) + n;
|
|
606
|
+
}
|
|
607
|
+
}, "quadinout"],
|
|
608
|
+
cubicin: [function(e, n, t, a) { return t * (e /= a) * e * e + n }, "cubicout"],
|
|
609
|
+
cubicout: [function(e, n, t, a) { return t * ((e = e / a - 1) * e * e + 1) + n }, "cubicin"],
|
|
610
|
+
cubicinout: [function(e, n, t, a) { return (e /= a / 2) < 1 ? t / 2 * e * e * e + n : t / 2 * ((e -= 2) * e * e + 2) + n }, "cubicoutin"],
|
|
611
|
+
cubicoutin: [function(e, n, t, a) {
|
|
612
|
+
let p = e / a, p0;
|
|
613
|
+
if (p < 0.5) {
|
|
614
|
+
p0 = 1 - 2 * p;
|
|
615
|
+
return t * (0.5 * (1 - (p0 * p0 * p0))) + n;
|
|
616
|
+
} else {
|
|
617
|
+
p0 = p * 2 - 1;
|
|
618
|
+
return t * (0.5 * (p0 * p0 * p0) + 0.5) + n;
|
|
619
|
+
}
|
|
620
|
+
}, "cubicinout"],
|
|
621
|
+
// ... (other easing definitions omitted for brevity)
|
|
622
|
+
vibration: [function(e, n, t, a, c) {
|
|
623
|
+
// Oscillates between start and end values with a configurable step count
|
|
624
|
+
return n + (t - n) / 2 + Math.sin(e * Math.PI / (a / c.vibrationStep) + 3 * Math.PI / 2) * (t - n) / 2
|
|
625
|
+
}, "vibration"],
|
|
626
|
+
cubicbezier: [function(e, n, t, a, c, idx) {
|
|
627
|
+
// IMPROVEMENT: The cubic‑bezier implementation contains a syntax error.
|
|
628
|
+
// The variable for the second control point (`o`) is missing a name.
|
|
629
|
+
// Fix by declaring `let o = Number(q * c.cubicbezier[2] + qq);` before using it.
|
|
630
|
+
let q = 1, qq = 0, sol;
|
|
631
|
+
if (c.impair && (c.boucleType === "returnRepeat" || c.boucleType === "repeatReturn")) {
|
|
632
|
+
q = -1; qq = 1;
|
|
633
|
+
}
|
|
634
|
+
let b = e / a, r = 1 - b,
|
|
635
|
+
l = Number(q * c.cubicbezier[0] + qq),
|
|
636
|
+
o = Number(q * c.cubicbezier[2] + qq); // <-- fixed declaration
|
|
637
|
+
|
|
638
|
+
// Solve cubic equation to get the correct parameter `b` on the bezier curve
|
|
639
|
+
if ((sol = solveCubic(3 * l - 3 * o + 1, 0 - 6 * l + 3 * o, 3 * l, 0 - b))) {
|
|
640
|
+
b = sol;
|
|
641
|
+
r = 1 - b;
|
|
642
|
+
}
|
|
643
|
+
// Compute Bezier output (standard cubic Bézier formula)
|
|
644
|
+
const y = (r = 1 - b) * r * r * 0 +
|
|
645
|
+
3 * r * r * b * Number(q * c.cubicbezier[1] + qq) +
|
|
646
|
+
3 * r * b * b * Number(q * c.cubicbezier[3] + qq) +
|
|
647
|
+
b * b * b * 1;
|
|
648
|
+
return n + y * t;
|
|
649
|
+
}, "cubicbezier"]
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Solves cubic equations for cubic-bezier calculations
|
|
654
|
+
*/
|
|
655
|
+
function solveCubic(a, b, c, d) {
|
|
656
|
+
// Implementation of cubic solver
|
|
657
|
+
// Returns root in [0,1] if exists
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Computes cubic root safely
|
|
662
|
+
*/
|
|
663
|
+
function cubeRoot(value) {
|
|
664
|
+
const absVal = Math.pow(Math.abs(value), 1 / 3);
|
|
665
|
+
return value < 0 ? -absVal : absVal;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Extracts numeric values from a string
|
|
670
|
+
*/
|
|
671
|
+
function getNumberFromString(str) {
|
|
672
|
+
return str.match(/[+-]?\d+(\.\d+)?/g);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Gets the computed style of an element for a given property
|
|
677
|
+
*/
|
|
678
|
+
function getStyle(element, property) {
|
|
679
|
+
if (element.currentStyle) return element.currentStyle[property];
|
|
680
|
+
if (window.getComputedStyle) return window.getComputedStyle(element, null)[property];
|
|
681
|
+
return element.style[property];
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Checks whether a value is a DOM Element or Document
|
|
686
|
+
*/
|
|
687
|
+
function isDOMElement(value) {
|
|
688
|
+
return value instanceof Element || value instanceof Document;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Deep clone utility for objects and arrays
|
|
693
|
+
*/
|
|
694
|
+
function copyObject(obj) {
|
|
695
|
+
if (obj === null || typeof obj !== "object") return obj;
|
|
696
|
+
if (Array.isArray(obj)) return obj.map(copyObject);
|
|
697
|
+
const clone = {};
|
|
698
|
+
for (const key in obj) {
|
|
699
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
700
|
+
clone[key] = copyObject(obj[key]);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
return clone;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Additional helper functions can be added as needed...
|
|
707
|
+
|
|
708
|
+
})(window);
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "select-animation",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "High-performance JavaScript animation engine for organic & fluid motion",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": ""
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/housseynCheriet/Select-Animation"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"animation",
|
|
15
|
+
"javascript",
|
|
16
|
+
"ui",
|
|
17
|
+
"svg",
|
|
18
|
+
"performance"
|
|
19
|
+
],
|
|
20
|
+
"author": "Housseyn Cheriet",
|
|
21
|
+
"license": "MIT"
|
|
22
|
+
}
|