select-animation 1.2.5 → 1.4.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/package.json +1 -1
- package/select-animation.js +124 -76
package/package.json
CHANGED
package/select-animation.js
CHANGED
|
@@ -100,13 +100,37 @@
|
|
|
100
100
|
// Returns flat array of matched elements given one or more selectors
|
|
101
101
|
// Usage: select('.class', '#id')
|
|
102
102
|
// -----------------------------
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
103
|
+
// --- Enhanced DOM Selection Utility with Explicit Error Reporting ---
|
|
104
|
+
const selectDom = function (selector) {
|
|
105
|
+
// 1. Check if the input is a DOM element directly
|
|
106
|
+
if (isElement(selector)) return [selector];
|
|
107
|
+
|
|
108
|
+
// 2. Handle string selectors (e.g., ".class", "#id")
|
|
109
|
+
if (typeof selector === "string") {
|
|
110
|
+
const nodes = document.querySelectorAll(selector);
|
|
111
|
+
if (nodes.length === 0) {
|
|
112
|
+
// Warning if the selector is valid string but matches nothing
|
|
113
|
+
console.warn(`Select-Animation: No elements found matching the selector "${selector}".`);
|
|
114
|
+
}
|
|
115
|
+
return Array.from(nodes);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 3. Handle arrays or collections
|
|
119
|
+
if (Array.isArray(selector) || (selector && typeof selector.length === "number")) {
|
|
120
|
+
return Array.from(selector).filter(isElement);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 4. CRITICAL: Handle invalid types (like numbers, null, undefined)
|
|
124
|
+
if (selector !== undefined && selector !== null) {
|
|
125
|
+
// Throwing a console error to stop the developer and force a fix
|
|
126
|
+
console.error(
|
|
127
|
+
`Select-Animation ERROR: Invalid input passed to selectDom().\n` +
|
|
128
|
+
`Expected: String (selector), HTMLElement, or Array.\n` +
|
|
129
|
+
`Received: ${typeof selector} (${selector})`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return [];
|
|
110
134
|
};
|
|
111
135
|
|
|
112
136
|
// -----------------------------
|
|
@@ -117,82 +141,87 @@
|
|
|
117
141
|
const definitions = arguments;
|
|
118
142
|
const defsCopy = copyObj(definitions);
|
|
119
143
|
|
|
120
|
-
return function (
|
|
121
|
-
let
|
|
144
|
+
return function () {
|
|
145
|
+
let tmp, i, propIndex, fromItem, toItem, valFrom, valTo, t;
|
|
122
146
|
let grouped = [];
|
|
123
|
-
let runtimeGrouped = [];
|
|
124
147
|
let expectNextGroup = false;
|
|
125
148
|
let staging = { color: {}, transform: {}, from: {}, to: {} };
|
|
126
149
|
|
|
150
|
+
// Process the definitions to map elements to their animation settings
|
|
127
151
|
buildPlan();
|
|
128
152
|
|
|
129
153
|
function buildPlan() {
|
|
130
|
-
if (targets !== undefined) {
|
|
131
|
-
if (!Array.isArray(targets)) targets = [targets];
|
|
132
|
-
runtimeGrouped = targets;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
154
|
let secondPass = false;
|
|
136
155
|
|
|
137
156
|
for (let c = 0, total = definitions.length; c < total; c++) {
|
|
138
157
|
propIndex = 0;
|
|
158
|
+
// If current item is an element or an array of elements
|
|
139
159
|
if (expectNextGroup || Array.isArray(defsCopy[c]) || isElement(defsCopy[c])) {
|
|
140
160
|
if (secondPass) {
|
|
141
161
|
Array.isArray(defsCopy[c]) || (defsCopy[c] = [defsCopy[c]]);
|
|
142
162
|
Array.prototype.push.apply(grouped, defsCopy[c]);
|
|
143
163
|
}
|
|
144
164
|
expectNextGroup = false;
|
|
145
|
-
}
|
|
165
|
+
}
|
|
166
|
+
// --- Handle Configuration Object with Validation ---
|
|
167
|
+
else if (typeof defsCopy[c] === 'object' && defsCopy[c] !== null) {
|
|
146
168
|
if (secondPass) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (typeof targets[u] !== "number") grouped.push(targets[u]);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
if (targets !== undefined) {
|
|
153
|
-
for (let j = 0; void 0 !== runtimeGrouped[j]; j++) {
|
|
154
|
-
if (typeof runtimeGrouped[j] !== "number") {
|
|
155
|
-
eventCheckResult = checkValEvent(runtimeContext, primaryParam, currentTargets, runtimeGrouped[j + 1]);
|
|
156
|
-
if (eventCheckResult[0]) currentTargets = eventCheckResult[0];
|
|
157
|
-
bindEvent(runtimeGrouped[j], runner(grouped, definitions, defsCopy, c), eventCheckResult[1]);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
} else {
|
|
161
|
-
runner(grouped, definitions, defsCopy, c)();
|
|
162
|
-
}
|
|
169
|
+
// Execute the animation runner for the current group
|
|
170
|
+
runner(grouped, definitions, defsCopy, c)();
|
|
163
171
|
} else {
|
|
164
172
|
expectNextGroup = false;
|
|
165
|
-
const fromIsObject = typeof defsCopy[c].from === "object";
|
|
166
|
-
const toIsObject = typeof defsCopy[c].to === "object";
|
|
167
173
|
|
|
168
|
-
|
|
174
|
+
// 1. Validate Duration and Animation Type
|
|
175
|
+
defsCopy[c].duration = (typeof defsCopy[c].duration === 'number' && defsCopy[c].duration > 0) ? defsCopy[c].duration : 1000;
|
|
176
|
+
|
|
177
|
+
let requestedType = defsCopy[c].typeAnimation;
|
|
178
|
+
|
|
179
|
+
if (requestedType === "vibration" && defsCopy[c].vibrationStep === undefined) {
|
|
169
180
|
defsCopy[c].vibrationStep = 6;
|
|
170
|
-
} else {
|
|
171
|
-
t = getNumber(
|
|
181
|
+
} else if (requestedType) {
|
|
182
|
+
t = getNumber(requestedType);
|
|
172
183
|
if (t && t.length === 4) {
|
|
173
184
|
defsCopy[c].cubicbezier = t;
|
|
174
185
|
defsCopy[c].typeAnimation = "cubicbezier";
|
|
186
|
+
} else {
|
|
187
|
+
// STOP EXECUTION: If easing is not found, do not fall back to linear
|
|
188
|
+
if (requestedType !== "linear" && requestedType !== "vibration" && requestedType !== "cubicbezier" && (!global.selectAnimationEase || !global.selectAnimationEase[requestedType])) {
|
|
189
|
+
// Use Error instead of warn to make it impossible to ignore
|
|
190
|
+
throw new Error(`Select-Animation ERROR: The easing function "${requestedType}" does not exist. Please check your spelling or definitions.`);
|
|
191
|
+
}
|
|
175
192
|
}
|
|
176
193
|
}
|
|
177
|
-
|
|
178
|
-
if
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
194
|
+
|
|
195
|
+
// Double-check if the animation type is valid before processing
|
|
196
|
+
if (!defsCopy[c].typeAnimation) return;
|
|
197
|
+
|
|
198
|
+
// 2. Helper to sanitize input values (convert "100px" to 100)
|
|
199
|
+
const parseVal = (v) => {
|
|
200
|
+
if (typeof v === "number") return v;
|
|
201
|
+
let p = parseFloat(v);
|
|
202
|
+
return isNaN(p) ? 0 : p;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// Process properties and prepare "from" and "to" values
|
|
206
|
+
if (!defsCopy[c].callback &&
|
|
183
207
|
(Array.isArray(defsCopy[c].property) || (defsCopy[c].property !== undefined && (defsCopy[c].property = [defsCopy[c].property])))) {
|
|
184
208
|
|
|
185
209
|
defsCopy[c].property.forEach(function (propItem) {
|
|
186
|
-
|
|
187
|
-
|
|
210
|
+
const fromIsObject = typeof defsCopy[c].from === "object";
|
|
211
|
+
const toIsObject = typeof defsCopy[c].to === "object";
|
|
212
|
+
|
|
213
|
+
// Safely extract from/to values based on their types
|
|
214
|
+
if (!fromIsObject) valFrom = parseVal(defsCopy[c].from);
|
|
215
|
+
else fromItem = defsCopy[c]["from"][propIndex] || 0;
|
|
188
216
|
|
|
189
|
-
if (!toIsObject) valTo = defsCopy[c].to;
|
|
190
|
-
else toItem = defsCopy[c]["to"][propIndex];
|
|
217
|
+
if (!toIsObject) valTo = parseVal(defsCopy[c].to);
|
|
218
|
+
else toItem = defsCopy[c]["to"][propIndex] || 0;
|
|
191
219
|
|
|
192
220
|
if (typeof propItem === "object") {
|
|
193
221
|
let propName = Object.keys(propItem)[0];
|
|
194
222
|
if (!Array.isArray(propItem[propName])) propItem[propName] = [propItem[propName]];
|
|
195
223
|
|
|
224
|
+
// Handle Colors and Transform objects
|
|
196
225
|
if ((propName.toLowerCase().indexOf("color") !== -1 && (staging.color[propName] = COLOR_PROPERTIES[propName])) ||
|
|
197
226
|
propName.toLowerCase().indexOf("transform") !== -1) {
|
|
198
227
|
|
|
@@ -204,65 +233,62 @@
|
|
|
204
233
|
if (propName.toLowerCase() === "transform") staging[propName][innerProp] = 0;
|
|
205
234
|
else staging.color[propName][innerProp] = 0;
|
|
206
235
|
|
|
236
|
+
// Validate and assign nested properties
|
|
207
237
|
if (fromIsObject) {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
valFrom = staging["from"][propName][innerProp] = fromItem[propName];
|
|
211
|
-
} else if (Array.isArray(fromItem[propName])) {
|
|
212
|
-
valFrom = staging["from"][propName][innerProp] = fromItem[propName][inner] !== undefined ? fromItem[propName][inner] : valFrom;
|
|
213
|
-
} else if (fromItem[propName][innerProp] !== undefined) {
|
|
214
|
-
valFrom = staging["from"][propName][innerProp] = fromItem[propName][innerProp];
|
|
215
|
-
}
|
|
216
|
-
} else {
|
|
217
|
-
staging["from"][propName][innerProp] = valFrom;
|
|
218
|
-
}
|
|
238
|
+
let raw = (fromItem[propName] !== undefined) ? (Array.isArray(fromItem[propName]) ? fromItem[propName][inner] : fromItem[propName][innerProp]) : valFrom;
|
|
239
|
+
staging["from"][propName][innerProp] = parseVal(raw);
|
|
219
240
|
} else {
|
|
220
|
-
staging["from"][propName][innerProp] = defsCopy[c].from;
|
|
241
|
+
staging["from"][propName][innerProp] = parseVal(defsCopy[c].from);
|
|
221
242
|
}
|
|
222
243
|
|
|
223
244
|
if (toIsObject) {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
valTo = staging["to"][propName][innerProp] = toItem[propName];
|
|
227
|
-
} else if (Array.isArray(toItem[propName])) {
|
|
228
|
-
valTo = staging["to"][propName][innerProp] = toItem[propName][inner] !== undefined ? toItem[propName][inner] : valTo;
|
|
229
|
-
} else if (toItem[propName][innerProp] !== undefined) {
|
|
230
|
-
valTo = staging["to"][propName][innerProp] = toItem[propName][innerProp];
|
|
231
|
-
}
|
|
232
|
-
} else {
|
|
233
|
-
staging["to"][propName][innerProp] = valTo;
|
|
234
|
-
}
|
|
245
|
+
let raw = (toItem[propName] !== undefined) ? (Array.isArray(toItem[propName]) ? toItem[propName][inner] : toItem[propName][innerProp]) : valTo;
|
|
246
|
+
staging["to"][propName][innerProp] = parseVal(raw);
|
|
235
247
|
} else {
|
|
236
|
-
staging["to"][propName][innerProp] = defsCopy[c].to;
|
|
248
|
+
staging["to"][propName][innerProp] = parseVal(defsCopy[c].to);
|
|
237
249
|
}
|
|
238
250
|
inner++;
|
|
239
251
|
});
|
|
240
252
|
propIndex++;
|
|
241
253
|
}
|
|
242
254
|
} else {
|
|
255
|
+
// Handle simple numeric CSS properties (width, height, etc.)
|
|
243
256
|
if (fromIsObject) {
|
|
244
|
-
|
|
257
|
+
let raw = fromItem[propItem] !== undefined ? fromItem[propItem] : (fromItem !== undefined ? fromItem : valFrom);
|
|
258
|
+
staging["from"][propItem] = parseVal(raw);
|
|
245
259
|
} else {
|
|
246
|
-
staging["from"][propItem] = defsCopy[c].from;
|
|
260
|
+
staging["from"][propItem] = parseVal(defsCopy[c].from);
|
|
247
261
|
}
|
|
248
262
|
|
|
249
263
|
if (toIsObject) {
|
|
250
|
-
|
|
264
|
+
let raw = toItem[propItem] !== undefined ? toItem[propItem] : (toItem !== undefined ? toItem : valTo);
|
|
265
|
+
staging["to"][propItem] = parseVal(raw);
|
|
251
266
|
} else {
|
|
252
|
-
staging["to"][propItem] = defsCopy[c].to;
|
|
267
|
+
staging["to"][propItem] = parseVal(defsCopy[c].to);
|
|
253
268
|
}
|
|
254
269
|
propIndex++;
|
|
255
270
|
}
|
|
256
271
|
});
|
|
257
272
|
}
|
|
273
|
+
|
|
274
|
+
// 3. Callback Safety Validation
|
|
275
|
+
// Ensure hooks are actual functions to prevent execution errors
|
|
276
|
+
if (defsCopy[c].onStep && typeof defsCopy[c].onStep !== 'function') defsCopy[c].onStep = null;
|
|
277
|
+
if (defsCopy[c].onComplete && typeof defsCopy[c].onComplete !== 'function') defsCopy[c].onComplete = null;
|
|
278
|
+
|
|
279
|
+
// Finalize staging data and store it
|
|
258
280
|
defsCopy[c].storeValueAnim = copyObj(staging);
|
|
259
281
|
staging = { color: {}, transform: {}, from: {}, to: {} };
|
|
260
282
|
}
|
|
283
|
+
|
|
284
|
+
// Determine if the next item starts a new sequence group
|
|
261
285
|
if (definitions[c + 1] !== undefined && (Array.isArray(definitions[c + 1]) || isElement(definitions[c + 1]))) {
|
|
262
286
|
expectNextGroup = true;
|
|
263
287
|
grouped = [];
|
|
264
288
|
}
|
|
265
289
|
}
|
|
290
|
+
|
|
291
|
+
// Switch to second pass to execute the runners
|
|
266
292
|
if (c === total - 1 && !secondPass) {
|
|
267
293
|
expectNextGroup = false;
|
|
268
294
|
secondPass = true;
|
|
@@ -276,9 +302,11 @@
|
|
|
276
302
|
const declaredAnim = defsCopyLocal[configIndex].typeAnimation;
|
|
277
303
|
let alternateAnim = declaredAnim;
|
|
278
304
|
|
|
305
|
+
// Sanitize timing inputs
|
|
279
306
|
conf.timeline = !isNaN(Number(conf.timeline)) ? Number(conf.timeline) : 0;
|
|
280
307
|
conf.startafter = !isNaN(Number(conf.startafter)) ? Number(conf.startafter) : 0;
|
|
281
308
|
|
|
309
|
+
// Prepare looping and easing alternates
|
|
282
310
|
if (conf.boucle) {
|
|
283
311
|
conf.delay = !isNaN(Number(conf.delay)) ? Number(conf.delay) : undefined;
|
|
284
312
|
if (conf.boucleType === "returnRepeat" || conf.boucleType === "repeatReturn") {
|
|
@@ -291,6 +319,7 @@
|
|
|
291
319
|
let isPaused = false;
|
|
292
320
|
let pauseStart;
|
|
293
321
|
|
|
322
|
+
// Initialize event-based pause/resume logic if configured
|
|
294
323
|
if (conf.pause && Array.isArray(conf.pause)) {
|
|
295
324
|
const eventCfg = conf.pause[1] || "e:click|false";
|
|
296
325
|
const parts = eventCfg.replace('e:', '').split('|');
|
|
@@ -315,6 +344,7 @@
|
|
|
315
344
|
|
|
316
345
|
const startedAt = Date.now();
|
|
317
346
|
|
|
347
|
+
// Initialize state storage for each element in the group
|
|
318
348
|
group.forEach(function (el, idx) {
|
|
319
349
|
if (!el.storeTransform) el.storeTransform = copyObj(defsCopyLocal[configIndex].storeValueAnim.transform);
|
|
320
350
|
if (!el.storeColor) {
|
|
@@ -325,16 +355,19 @@
|
|
|
325
355
|
});
|
|
326
356
|
}
|
|
327
357
|
|
|
358
|
+
// Start staggered animations if timeline offset exists
|
|
328
359
|
if (conf.timeline !== 0) {
|
|
329
360
|
frameRunner([el], idx, conf.timeline * idx + conf.startafter, conf.startafter);
|
|
330
361
|
}
|
|
331
362
|
});
|
|
332
363
|
|
|
364
|
+
// Start simultaneous animations
|
|
333
365
|
if (conf.timeline === 0) {
|
|
334
366
|
frameRunner(group, 0, 0 + conf.startafter, conf.startafter);
|
|
335
367
|
}
|
|
336
368
|
|
|
337
369
|
function frameRunner(targetArray, idx, timeOffset, startAfter) {
|
|
370
|
+
// Manage animation frame cycles
|
|
338
371
|
if (conf.animFram) cancelAnimationFrame(conf.animFram[idx]);
|
|
339
372
|
else conf.animFram = {};
|
|
340
373
|
|
|
@@ -346,6 +379,7 @@
|
|
|
346
379
|
let skipCounter;
|
|
347
380
|
const sv = iterConf.storeValueAnim;
|
|
348
381
|
|
|
382
|
+
// The main animation loop using requestAnimationFrame
|
|
349
383
|
function loop() {
|
|
350
384
|
if (isPaused) {
|
|
351
385
|
conf.animFram[idx] = requestAnimationFrame(loop);
|
|
@@ -357,6 +391,7 @@
|
|
|
357
391
|
const elapsed = Date.now() - (startedAt + timeOffset + pausedAccum);
|
|
358
392
|
|
|
359
393
|
if (elapsed >= 0) {
|
|
394
|
+
// Logic for handling loops, delays, and reversing (yoyo) animations
|
|
360
395
|
if (iterConf.boucle) {
|
|
361
396
|
if (iterConf.delay !== undefined) {
|
|
362
397
|
delay = iterConf.delay;
|
|
@@ -398,7 +433,9 @@
|
|
|
398
433
|
iterConf.timeEasing = elapsed < iterConf.duration ? elapsed : iterConf.duration;
|
|
399
434
|
}
|
|
400
435
|
|
|
436
|
+
// Update properties if not in delay phase
|
|
401
437
|
if (!iterConf.skipDelay || iterConf.skip) {
|
|
438
|
+
// Calculate ease factor (0 to 1)
|
|
402
439
|
eased = Easing[iterConf.changetypeAnim][0](iterConf.timeEasing, 0, 1, iterConf.duration, iterConf, idx);
|
|
403
440
|
|
|
404
441
|
if (iterConf.callback) {
|
|
@@ -406,6 +443,7 @@
|
|
|
406
443
|
iterConf.callback(el, eased, iterConf, idx !== index ? idx : index);
|
|
407
444
|
});
|
|
408
445
|
} else {
|
|
446
|
+
// Apply styles: Transform, Color, or standard Numeric properties
|
|
409
447
|
iterConf.property.forEach(function (prop) {
|
|
410
448
|
css = "";
|
|
411
449
|
let key = typeof prop === "string" ? prop : Object.keys(prop)[0];
|
|
@@ -444,6 +482,7 @@
|
|
|
444
482
|
}
|
|
445
483
|
}
|
|
446
484
|
|
|
485
|
+
// Continue the loop if animation is still active or looping
|
|
447
486
|
if (iterConf.boucle || elapsed < iterConf.duration) {
|
|
448
487
|
conf.animFram[idx] = requestAnimationFrame(loop);
|
|
449
488
|
}
|
|
@@ -757,8 +796,17 @@
|
|
|
757
796
|
else return el.style[cssprop];
|
|
758
797
|
}
|
|
759
798
|
|
|
799
|
+
// --- Robust DOM Element Validation ---
|
|
760
800
|
function isElement(element) {
|
|
761
|
-
|
|
801
|
+
try {
|
|
802
|
+
// Check for standard DOM Element or HTMLDocument
|
|
803
|
+
return element instanceof Element || element instanceof HTMLDocument;
|
|
804
|
+
} catch (e) {
|
|
805
|
+
// Fallback for environments where Element is not defined
|
|
806
|
+
return (typeof element === "object") &&
|
|
807
|
+
(element.nodeType === 1) &&
|
|
808
|
+
(typeof element.nodeName === "string");
|
|
809
|
+
}
|
|
762
810
|
}
|
|
763
811
|
|
|
764
812
|
const copyObj = function(obj) {
|