anime-cursor 2.1.1 → 2.1.3
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/README.md +4 -3
- package/dist/anime-cursor.esm.js +94 -36
- package/dist/anime-cursor.umd.js +94 -36
- package/dist/anime-cursor.umd.min.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
[[简体中文]](#
|
|
9
|
+
[[简体中文]](#sc)
|
|
10
10
|
|
|
11
11
|
## [Visit the official website](https://shuninyu.github.io/anime-cursor/) for more informations
|
|
12
12
|
|
|
@@ -33,7 +33,7 @@ AnimeCursor has no dependencies on any frameworks, making it suitable for person
|
|
|
33
33
|
### CDN
|
|
34
34
|
|
|
35
35
|
```html
|
|
36
|
-
<script src="https://cdn.jsdelivr.net/npm/anime-cursor@v2.1.
|
|
36
|
+
<script src="https://cdn.jsdelivr.net/npm/anime-cursor@v2.1.2/dist/anime-cursor.umd.min.js"></script>
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
### npm
|
|
@@ -185,6 +185,7 @@ Because v2 uses native CSS `cursor` property and CSS animations, there is no Jav
|
|
|
185
185
|
|
|
186
186
|
---
|
|
187
187
|
|
|
188
|
+
<span id="sc"></span>
|
|
188
189
|
# AnimeCursor v2
|
|
189
190
|
|
|
190
191
|
<div align="center">
|
|
@@ -218,7 +219,7 @@ AnimeCursor 无需依赖任何框架,适合个人网站、创意作品集以
|
|
|
218
219
|
### CDN
|
|
219
220
|
|
|
220
221
|
```html
|
|
221
|
-
<script src="https://cdn.jsdelivr.net/npm/anime-cursor@v2.1.
|
|
222
|
+
<script src="https://cdn.jsdelivr.net/npm/anime-cursor@v2.1.2/dist/anime-cursor.umd.min.js"></script>
|
|
222
223
|
```
|
|
223
224
|
|
|
224
225
|
### npm
|
package/dist/anime-cursor.esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// AnimeCursor by github@ShuninYu
|
|
2
|
-
// v2.1.
|
|
2
|
+
// v2.1.3
|
|
3
3
|
|
|
4
4
|
let _instance = null;
|
|
5
5
|
|
|
@@ -51,16 +51,15 @@ class AnimeCursor {
|
|
|
51
51
|
enableTouch: false,
|
|
52
52
|
fallbackCursor: 'auto',
|
|
53
53
|
excludeSelectors: 'input, textarea, [contenteditable]',
|
|
54
|
-
combineAnimations: false,
|
|
54
|
+
combineAnimations: false,
|
|
55
55
|
...options
|
|
56
56
|
};
|
|
57
57
|
|
|
58
58
|
this.disabled = false;
|
|
59
59
|
this.cursors = this.options.cursors || {};
|
|
60
|
-
this.cursorAnimationStrings = {};
|
|
61
|
-
this.combinedRules = new Map();
|
|
60
|
+
this.cursorAnimationStrings = {};
|
|
61
|
+
this.combinedRules = new Map();
|
|
62
62
|
|
|
63
|
-
// 检查是否应启用(触摸设备且未强制启用则禁用)
|
|
64
63
|
if (!this.options.enableTouch && !this.isMouseLikeDevice()) {
|
|
65
64
|
this.disabled = true;
|
|
66
65
|
if (this.options.debug) {
|
|
@@ -71,6 +70,7 @@ class AnimeCursor {
|
|
|
71
70
|
|
|
72
71
|
this.styleEl = null;
|
|
73
72
|
this.debugEl = null;
|
|
73
|
+
this.crosshairEl = null;
|
|
74
74
|
this._onMouseMove = null;
|
|
75
75
|
|
|
76
76
|
this._validateOptions();
|
|
@@ -97,7 +97,6 @@ class AnimeCursor {
|
|
|
97
97
|
throw new Error(`[AnimeCursor] Cursor "${name}" missing required setting: image`);
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
// 处理 frames 和 duration
|
|
101
100
|
if (cfg.frames !== undefined && cfg.duration !== undefined) {
|
|
102
101
|
const framesType = typeof cfg.frames;
|
|
103
102
|
const durationType = typeof cfg.duration;
|
|
@@ -244,23 +243,20 @@ class AnimeCursor {
|
|
|
244
243
|
}
|
|
245
244
|
}
|
|
246
245
|
|
|
247
|
-
// 核心注入样式
|
|
248
246
|
_injectStyles() {
|
|
249
247
|
if (this.disabled) return;
|
|
250
|
-
this.combinedRules.clear();
|
|
248
|
+
this.combinedRules.clear();
|
|
251
249
|
|
|
252
250
|
const style = document.createElement('style');
|
|
253
251
|
style.id = 'animecursor-styles';
|
|
254
252
|
let css = '';
|
|
255
253
|
|
|
256
|
-
// 如果有默认光标,生成全局规则
|
|
257
254
|
if (this.defaultCursorName) {
|
|
258
255
|
const defaultCfg = this.cursors[this.defaultCursorName];
|
|
259
256
|
const defaultCursorDef = this._buildCursorCss(this.defaultCursorName, defaultCfg);
|
|
260
257
|
css += `* { ${defaultCursorDef} }\n`;
|
|
261
258
|
}
|
|
262
259
|
|
|
263
|
-
// 为每个光标生成独立的类和关键帧
|
|
264
260
|
for (const [name, cfg] of Object.entries(this.cursors)) {
|
|
265
261
|
const className = `.ac-cursor-${name}`;
|
|
266
262
|
const offset = cfg.offset || [0, 0];
|
|
@@ -292,12 +288,17 @@ class AnimeCursor {
|
|
|
292
288
|
cursorAnimation = animation;
|
|
293
289
|
css += `${className} { cursor: url("${frameUrls[0]}") ${offset[0]} ${offset[1]}, ${fallback}; animation: ${animation}; }\n`;
|
|
294
290
|
} else {
|
|
295
|
-
|
|
291
|
+
const staticKeyframeName = `ac_anim_${name}_static`;
|
|
292
|
+
css += `@keyframes ${staticKeyframeName} {\n`;
|
|
293
|
+
css += ` 0%, 100% { cursor: url("${frameUrls[0]}") ${offset[0]} ${offset[1]}, ${fallback}; }\n`;
|
|
294
|
+
css += `}\n`;
|
|
295
|
+
const staticAnimation = `${staticKeyframeName} 0.001s forwards steps(1)`;
|
|
296
|
+
cursorAnimation = staticAnimation;
|
|
297
|
+
css += `${className} { cursor: url("${frameUrls[0]}") ${offset[0]} ${offset[1]}, ${fallback}; animation: ${staticAnimation}; }\n`;
|
|
296
298
|
}
|
|
297
299
|
|
|
298
300
|
this.cursorAnimationStrings[name] = cursorAnimation;
|
|
299
301
|
|
|
300
|
-
// 标签和 data-cursor 规则
|
|
301
302
|
if (cfg.tags && cfg.tags.length) {
|
|
302
303
|
const selector = cfg.tags.join(', ');
|
|
303
304
|
css += `${selector} { ${this._buildCursorCss(name, cfg)} }\n`;
|
|
@@ -309,21 +310,18 @@ class AnimeCursor {
|
|
|
309
310
|
css += `${this.options.excludeSelectors} { cursor: text !important; animation: none !important; }\n`;
|
|
310
311
|
}
|
|
311
312
|
|
|
312
|
-
// 自动组合动画(新功能)
|
|
313
313
|
if (this.options.combineAnimations) {
|
|
314
314
|
const elements = document.querySelectorAll('[data-ac-animation]');
|
|
315
315
|
for (const el of elements) {
|
|
316
316
|
const userAnim = el.getAttribute('data-ac-animation');
|
|
317
317
|
if (!userAnim) continue;
|
|
318
318
|
|
|
319
|
-
// 确定该元素应该使用哪个光标
|
|
320
319
|
let cursorName = this._getCursorTypeForElement(el);
|
|
321
320
|
if (!cursorName) continue;
|
|
322
321
|
|
|
323
322
|
const cursorAnim = this.cursorAnimationStrings[cursorName];
|
|
324
323
|
if (!cursorAnim) continue;
|
|
325
324
|
|
|
326
|
-
// 生成唯一标识
|
|
327
325
|
const key = `${cursorName}:${userAnim}`;
|
|
328
326
|
if (!this.combinedRules.has(key)) {
|
|
329
327
|
const hash = this._simpleHash(key);
|
|
@@ -343,7 +341,6 @@ class AnimeCursor {
|
|
|
343
341
|
this.styleEl = style;
|
|
344
342
|
}
|
|
345
343
|
|
|
346
|
-
// 获取元素对应的光标类型(复用 debug 逻辑)
|
|
347
344
|
_getCursorTypeForElement(el) {
|
|
348
345
|
if (el.dataset.cursor && this.cursors[el.dataset.cursor]) {
|
|
349
346
|
return el.dataset.cursor;
|
|
@@ -353,7 +350,7 @@ class AnimeCursor {
|
|
|
353
350
|
return name;
|
|
354
351
|
}
|
|
355
352
|
}
|
|
356
|
-
return this.defaultCursorName;
|
|
353
|
+
return this.defaultCursorName;
|
|
357
354
|
}
|
|
358
355
|
|
|
359
356
|
_buildKeyframes(cfg, frameUrls) {
|
|
@@ -397,12 +394,16 @@ class AnimeCursor {
|
|
|
397
394
|
const offset = cfg.offset || [0, 0];
|
|
398
395
|
const fallback = cfg.fallback || this.options.fallbackCursor;
|
|
399
396
|
let css = `cursor: url("${frameUrls[0]}") ${offset[0]} ${offset[1]}, ${fallback};`;
|
|
397
|
+
|
|
400
398
|
const hasAnimation = cfg.frames !== undefined && cfg.duration !== undefined &&
|
|
401
399
|
((Array.isArray(cfg.frames) && Array.isArray(cfg.duration)) ||
|
|
402
400
|
(typeof cfg.frames === 'number' && typeof cfg.duration === 'number'));
|
|
401
|
+
|
|
403
402
|
if (hasAnimation && frameUrls.length > 1) {
|
|
404
403
|
const totalDuration = Array.isArray(cfg.duration) ? cfg.duration.reduce((a, b) => a + b, 0) : cfg.duration;
|
|
405
404
|
css += ` animation: ac_anim_${name} ${totalDuration}s steps(1) infinite ${cfg.pingpong ? 'alternate' : ''};`;
|
|
405
|
+
} else {
|
|
406
|
+
css += ` animation: ac_anim_${name}_static 0.001s forwards steps(1);`;
|
|
406
407
|
}
|
|
407
408
|
return css;
|
|
408
409
|
}
|
|
@@ -418,27 +419,82 @@ class AnimeCursor {
|
|
|
418
419
|
}
|
|
419
420
|
|
|
420
421
|
_initDebug() {
|
|
422
|
+
// 创建左上角信息浮层(现有 debug 面板)
|
|
421
423
|
const debugDiv = document.createElement('div');
|
|
422
424
|
debugDiv.className = 'animecursor-debug';
|
|
423
425
|
debugDiv.style.cssText = `
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
426
|
+
position: fixed;
|
|
427
|
+
top: 0;
|
|
428
|
+
left: 0;
|
|
429
|
+
background: rgba(0,0,0,0.7);
|
|
430
|
+
color: #0f0;
|
|
431
|
+
padding: 4px 8px;
|
|
432
|
+
font-family: monospace;
|
|
433
|
+
font-size: 12px;
|
|
434
|
+
z-index: 2147483647;
|
|
435
|
+
pointer-events: auto;
|
|
436
|
+
white-space: nowrap;
|
|
437
|
+
transition: opacity 0.2s ease;
|
|
438
|
+
`;
|
|
439
|
+
// hover 时半透明,便于查看被遮挡内容
|
|
440
|
+
debugDiv.addEventListener('mouseenter', () => { debugDiv.style.opacity = '0.5'; });
|
|
441
|
+
debugDiv.addEventListener('mouseleave', () => { debugDiv.style.opacity = '1'; });
|
|
436
442
|
document.body.appendChild(debugDiv);
|
|
437
443
|
this.debugEl = debugDiv;
|
|
438
444
|
|
|
445
|
+
// 创建跟随鼠标的十字辅助线层(横竖双色线)
|
|
446
|
+
const crosshair = document.createElement('div');
|
|
447
|
+
crosshair.className = 'animecursor-crosshair';
|
|
448
|
+
crosshair.style.cssText = `
|
|
449
|
+
position: fixed;
|
|
450
|
+
top: 0;
|
|
451
|
+
left: 0;
|
|
452
|
+
width: 0;
|
|
453
|
+
height: 0;
|
|
454
|
+
pointer-events: none;
|
|
455
|
+
z-index: 2147483646;
|
|
456
|
+
`;
|
|
457
|
+
// 横线
|
|
458
|
+
const hLine = document.createElement('div');
|
|
459
|
+
hLine.className = 'ac-crosshair-h';
|
|
460
|
+
hLine.style.cssText = `
|
|
461
|
+
position: fixed;
|
|
462
|
+
left: 0;
|
|
463
|
+
width: 100%;
|
|
464
|
+
height: 1px;
|
|
465
|
+
background-color: rgba(255,0,0,0.6);
|
|
466
|
+
pointer-events: none;
|
|
467
|
+
transform: translateY(-0.5px);
|
|
468
|
+
`;
|
|
469
|
+
// 竖线
|
|
470
|
+
const vLine = document.createElement('div');
|
|
471
|
+
vLine.className = 'ac-crosshair-v';
|
|
472
|
+
vLine.style.cssText = `
|
|
473
|
+
position: fixed;
|
|
474
|
+
top: 0;
|
|
475
|
+
width: 1px;
|
|
476
|
+
height: 100%;
|
|
477
|
+
background-color: rgba(255,0,0,0.6);
|
|
478
|
+
pointer-events: none;
|
|
479
|
+
transform: translateX(-0.5px);
|
|
480
|
+
`;
|
|
481
|
+
crosshair.appendChild(hLine);
|
|
482
|
+
crosshair.appendChild(vLine);
|
|
483
|
+
document.body.appendChild(crosshair);
|
|
484
|
+
this.crosshairEl = { container: crosshair, hLine, vLine };
|
|
485
|
+
|
|
439
486
|
let lastCursor = '';
|
|
440
487
|
this._onMouseMove = (e) => {
|
|
441
|
-
const
|
|
488
|
+
const x = e.clientX;
|
|
489
|
+
const y = e.clientY;
|
|
490
|
+
|
|
491
|
+
// 更新十字线位置
|
|
492
|
+
if (this.crosshairEl) {
|
|
493
|
+
this.crosshairEl.hLine.style.top = y + 'px';
|
|
494
|
+
this.crosshairEl.vLine.style.left = x + 'px';
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const target = document.elementFromPoint(x, y);
|
|
442
498
|
let cursorType = null;
|
|
443
499
|
if (target) {
|
|
444
500
|
if (target.dataset.cursor && this.cursors[target.dataset.cursor]) {
|
|
@@ -459,9 +515,9 @@ class AnimeCursor {
|
|
|
459
515
|
}
|
|
460
516
|
if (cursorType !== lastCursor) {
|
|
461
517
|
lastCursor = cursorType;
|
|
462
|
-
debugDiv.textContent =
|
|
518
|
+
debugDiv.textContent = `${cursorType} @ (${x}, ${y})`;
|
|
463
519
|
} else {
|
|
464
|
-
debugDiv.textContent =
|
|
520
|
+
debugDiv.textContent = `${cursorType} @ (${x}, ${y})`;
|
|
465
521
|
}
|
|
466
522
|
};
|
|
467
523
|
document.addEventListener('mousemove', this._onMouseMove);
|
|
@@ -474,6 +530,7 @@ class AnimeCursor {
|
|
|
474
530
|
this._injectStyles();
|
|
475
531
|
if (this.options.debug) {
|
|
476
532
|
if (this.debugEl) this.debugEl.remove();
|
|
533
|
+
if (this.crosshairEl) this.crosshairEl.container.remove();
|
|
477
534
|
this._initDebug();
|
|
478
535
|
}
|
|
479
536
|
console.log('[AnimeCursor] Refresh complete');
|
|
@@ -483,6 +540,7 @@ class AnimeCursor {
|
|
|
483
540
|
if (this.disabled) return;
|
|
484
541
|
if (this.styleEl) this.styleEl.remove();
|
|
485
542
|
if (this.debugEl) this.debugEl.remove();
|
|
543
|
+
if (this.crosshairEl) this.crosshairEl.container.remove();
|
|
486
544
|
if (this._onMouseMove) {
|
|
487
545
|
document.removeEventListener('mousemove', this._onMouseMove);
|
|
488
546
|
}
|
|
@@ -493,16 +551,18 @@ class AnimeCursor {
|
|
|
493
551
|
|
|
494
552
|
disable() {
|
|
495
553
|
if (this.disabled) return;
|
|
496
|
-
// 移除样式表
|
|
497
554
|
if (this.styleEl) {
|
|
498
555
|
this.styleEl.remove();
|
|
499
556
|
this.styleEl = null;
|
|
500
557
|
}
|
|
501
|
-
// 移除 debug 相关
|
|
502
558
|
if (this.debugEl) {
|
|
503
559
|
this.debugEl.remove();
|
|
504
560
|
this.debugEl = null;
|
|
505
561
|
}
|
|
562
|
+
if (this.crosshairEl) {
|
|
563
|
+
this.crosshairEl.container.remove();
|
|
564
|
+
this.crosshairEl = null;
|
|
565
|
+
}
|
|
506
566
|
if (this._onMouseMove) {
|
|
507
567
|
document.removeEventListener('mousemove', this._onMouseMove);
|
|
508
568
|
this._onMouseMove = null;
|
|
@@ -514,9 +574,7 @@ class AnimeCursor {
|
|
|
514
574
|
enable() {
|
|
515
575
|
if (!this.disabled) return;
|
|
516
576
|
this.disabled = false;
|
|
517
|
-
// 重新注入样式
|
|
518
577
|
this._injectStyles();
|
|
519
|
-
// 如果 debug 模式开启,重新初始化 debug
|
|
520
578
|
if (this.options.debug) {
|
|
521
579
|
this._initDebug();
|
|
522
580
|
}
|
package/dist/anime-cursor.umd.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
})(this, (function () { 'use strict';
|
|
6
6
|
|
|
7
7
|
// AnimeCursor by github@ShuninYu
|
|
8
|
-
// v2.1.
|
|
8
|
+
// v2.1.3
|
|
9
9
|
|
|
10
10
|
let _instance = null;
|
|
11
11
|
|
|
@@ -57,16 +57,15 @@
|
|
|
57
57
|
enableTouch: false,
|
|
58
58
|
fallbackCursor: 'auto',
|
|
59
59
|
excludeSelectors: 'input, textarea, [contenteditable]',
|
|
60
|
-
combineAnimations: false,
|
|
60
|
+
combineAnimations: false,
|
|
61
61
|
...options
|
|
62
62
|
};
|
|
63
63
|
|
|
64
64
|
this.disabled = false;
|
|
65
65
|
this.cursors = this.options.cursors || {};
|
|
66
|
-
this.cursorAnimationStrings = {};
|
|
67
|
-
this.combinedRules = new Map();
|
|
66
|
+
this.cursorAnimationStrings = {};
|
|
67
|
+
this.combinedRules = new Map();
|
|
68
68
|
|
|
69
|
-
// 检查是否应启用(触摸设备且未强制启用则禁用)
|
|
70
69
|
if (!this.options.enableTouch && !this.isMouseLikeDevice()) {
|
|
71
70
|
this.disabled = true;
|
|
72
71
|
if (this.options.debug) {
|
|
@@ -77,6 +76,7 @@
|
|
|
77
76
|
|
|
78
77
|
this.styleEl = null;
|
|
79
78
|
this.debugEl = null;
|
|
79
|
+
this.crosshairEl = null;
|
|
80
80
|
this._onMouseMove = null;
|
|
81
81
|
|
|
82
82
|
this._validateOptions();
|
|
@@ -103,7 +103,6 @@
|
|
|
103
103
|
throw new Error(`[AnimeCursor] Cursor "${name}" missing required setting: image`);
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
// 处理 frames 和 duration
|
|
107
106
|
if (cfg.frames !== undefined && cfg.duration !== undefined) {
|
|
108
107
|
const framesType = typeof cfg.frames;
|
|
109
108
|
const durationType = typeof cfg.duration;
|
|
@@ -250,23 +249,20 @@
|
|
|
250
249
|
}
|
|
251
250
|
}
|
|
252
251
|
|
|
253
|
-
// 核心注入样式
|
|
254
252
|
_injectStyles() {
|
|
255
253
|
if (this.disabled) return;
|
|
256
|
-
this.combinedRules.clear();
|
|
254
|
+
this.combinedRules.clear();
|
|
257
255
|
|
|
258
256
|
const style = document.createElement('style');
|
|
259
257
|
style.id = 'animecursor-styles';
|
|
260
258
|
let css = '';
|
|
261
259
|
|
|
262
|
-
// 如果有默认光标,生成全局规则
|
|
263
260
|
if (this.defaultCursorName) {
|
|
264
261
|
const defaultCfg = this.cursors[this.defaultCursorName];
|
|
265
262
|
const defaultCursorDef = this._buildCursorCss(this.defaultCursorName, defaultCfg);
|
|
266
263
|
css += `* { ${defaultCursorDef} }\n`;
|
|
267
264
|
}
|
|
268
265
|
|
|
269
|
-
// 为每个光标生成独立的类和关键帧
|
|
270
266
|
for (const [name, cfg] of Object.entries(this.cursors)) {
|
|
271
267
|
const className = `.ac-cursor-${name}`;
|
|
272
268
|
const offset = cfg.offset || [0, 0];
|
|
@@ -298,12 +294,17 @@
|
|
|
298
294
|
cursorAnimation = animation;
|
|
299
295
|
css += `${className} { cursor: url("${frameUrls[0]}") ${offset[0]} ${offset[1]}, ${fallback}; animation: ${animation}; }\n`;
|
|
300
296
|
} else {
|
|
301
|
-
|
|
297
|
+
const staticKeyframeName = `ac_anim_${name}_static`;
|
|
298
|
+
css += `@keyframes ${staticKeyframeName} {\n`;
|
|
299
|
+
css += ` 0%, 100% { cursor: url("${frameUrls[0]}") ${offset[0]} ${offset[1]}, ${fallback}; }\n`;
|
|
300
|
+
css += `}\n`;
|
|
301
|
+
const staticAnimation = `${staticKeyframeName} 0.001s forwards steps(1)`;
|
|
302
|
+
cursorAnimation = staticAnimation;
|
|
303
|
+
css += `${className} { cursor: url("${frameUrls[0]}") ${offset[0]} ${offset[1]}, ${fallback}; animation: ${staticAnimation}; }\n`;
|
|
302
304
|
}
|
|
303
305
|
|
|
304
306
|
this.cursorAnimationStrings[name] = cursorAnimation;
|
|
305
307
|
|
|
306
|
-
// 标签和 data-cursor 规则
|
|
307
308
|
if (cfg.tags && cfg.tags.length) {
|
|
308
309
|
const selector = cfg.tags.join(', ');
|
|
309
310
|
css += `${selector} { ${this._buildCursorCss(name, cfg)} }\n`;
|
|
@@ -315,21 +316,18 @@
|
|
|
315
316
|
css += `${this.options.excludeSelectors} { cursor: text !important; animation: none !important; }\n`;
|
|
316
317
|
}
|
|
317
318
|
|
|
318
|
-
// 自动组合动画(新功能)
|
|
319
319
|
if (this.options.combineAnimations) {
|
|
320
320
|
const elements = document.querySelectorAll('[data-ac-animation]');
|
|
321
321
|
for (const el of elements) {
|
|
322
322
|
const userAnim = el.getAttribute('data-ac-animation');
|
|
323
323
|
if (!userAnim) continue;
|
|
324
324
|
|
|
325
|
-
// 确定该元素应该使用哪个光标
|
|
326
325
|
let cursorName = this._getCursorTypeForElement(el);
|
|
327
326
|
if (!cursorName) continue;
|
|
328
327
|
|
|
329
328
|
const cursorAnim = this.cursorAnimationStrings[cursorName];
|
|
330
329
|
if (!cursorAnim) continue;
|
|
331
330
|
|
|
332
|
-
// 生成唯一标识
|
|
333
331
|
const key = `${cursorName}:${userAnim}`;
|
|
334
332
|
if (!this.combinedRules.has(key)) {
|
|
335
333
|
const hash = this._simpleHash(key);
|
|
@@ -349,7 +347,6 @@
|
|
|
349
347
|
this.styleEl = style;
|
|
350
348
|
}
|
|
351
349
|
|
|
352
|
-
// 获取元素对应的光标类型(复用 debug 逻辑)
|
|
353
350
|
_getCursorTypeForElement(el) {
|
|
354
351
|
if (el.dataset.cursor && this.cursors[el.dataset.cursor]) {
|
|
355
352
|
return el.dataset.cursor;
|
|
@@ -359,7 +356,7 @@
|
|
|
359
356
|
return name;
|
|
360
357
|
}
|
|
361
358
|
}
|
|
362
|
-
return this.defaultCursorName;
|
|
359
|
+
return this.defaultCursorName;
|
|
363
360
|
}
|
|
364
361
|
|
|
365
362
|
_buildKeyframes(cfg, frameUrls) {
|
|
@@ -403,12 +400,16 @@
|
|
|
403
400
|
const offset = cfg.offset || [0, 0];
|
|
404
401
|
const fallback = cfg.fallback || this.options.fallbackCursor;
|
|
405
402
|
let css = `cursor: url("${frameUrls[0]}") ${offset[0]} ${offset[1]}, ${fallback};`;
|
|
403
|
+
|
|
406
404
|
const hasAnimation = cfg.frames !== undefined && cfg.duration !== undefined &&
|
|
407
405
|
((Array.isArray(cfg.frames) && Array.isArray(cfg.duration)) ||
|
|
408
406
|
(typeof cfg.frames === 'number' && typeof cfg.duration === 'number'));
|
|
407
|
+
|
|
409
408
|
if (hasAnimation && frameUrls.length > 1) {
|
|
410
409
|
const totalDuration = Array.isArray(cfg.duration) ? cfg.duration.reduce((a, b) => a + b, 0) : cfg.duration;
|
|
411
410
|
css += ` animation: ac_anim_${name} ${totalDuration}s steps(1) infinite ${cfg.pingpong ? 'alternate' : ''};`;
|
|
411
|
+
} else {
|
|
412
|
+
css += ` animation: ac_anim_${name}_static 0.001s forwards steps(1);`;
|
|
412
413
|
}
|
|
413
414
|
return css;
|
|
414
415
|
}
|
|
@@ -424,27 +425,82 @@
|
|
|
424
425
|
}
|
|
425
426
|
|
|
426
427
|
_initDebug() {
|
|
428
|
+
// 创建左上角信息浮层(现有 debug 面板)
|
|
427
429
|
const debugDiv = document.createElement('div');
|
|
428
430
|
debugDiv.className = 'animecursor-debug';
|
|
429
431
|
debugDiv.style.cssText = `
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
432
|
+
position: fixed;
|
|
433
|
+
top: 0;
|
|
434
|
+
left: 0;
|
|
435
|
+
background: rgba(0,0,0,0.7);
|
|
436
|
+
color: #0f0;
|
|
437
|
+
padding: 4px 8px;
|
|
438
|
+
font-family: monospace;
|
|
439
|
+
font-size: 12px;
|
|
440
|
+
z-index: 2147483647;
|
|
441
|
+
pointer-events: auto;
|
|
442
|
+
white-space: nowrap;
|
|
443
|
+
transition: opacity 0.2s ease;
|
|
444
|
+
`;
|
|
445
|
+
// hover 时半透明,便于查看被遮挡内容
|
|
446
|
+
debugDiv.addEventListener('mouseenter', () => { debugDiv.style.opacity = '0.5'; });
|
|
447
|
+
debugDiv.addEventListener('mouseleave', () => { debugDiv.style.opacity = '1'; });
|
|
442
448
|
document.body.appendChild(debugDiv);
|
|
443
449
|
this.debugEl = debugDiv;
|
|
444
450
|
|
|
451
|
+
// 创建跟随鼠标的十字辅助线层(横竖双色线)
|
|
452
|
+
const crosshair = document.createElement('div');
|
|
453
|
+
crosshair.className = 'animecursor-crosshair';
|
|
454
|
+
crosshair.style.cssText = `
|
|
455
|
+
position: fixed;
|
|
456
|
+
top: 0;
|
|
457
|
+
left: 0;
|
|
458
|
+
width: 0;
|
|
459
|
+
height: 0;
|
|
460
|
+
pointer-events: none;
|
|
461
|
+
z-index: 2147483646;
|
|
462
|
+
`;
|
|
463
|
+
// 横线
|
|
464
|
+
const hLine = document.createElement('div');
|
|
465
|
+
hLine.className = 'ac-crosshair-h';
|
|
466
|
+
hLine.style.cssText = `
|
|
467
|
+
position: fixed;
|
|
468
|
+
left: 0;
|
|
469
|
+
width: 100%;
|
|
470
|
+
height: 1px;
|
|
471
|
+
background-color: rgba(255,0,0,0.6);
|
|
472
|
+
pointer-events: none;
|
|
473
|
+
transform: translateY(-0.5px);
|
|
474
|
+
`;
|
|
475
|
+
// 竖线
|
|
476
|
+
const vLine = document.createElement('div');
|
|
477
|
+
vLine.className = 'ac-crosshair-v';
|
|
478
|
+
vLine.style.cssText = `
|
|
479
|
+
position: fixed;
|
|
480
|
+
top: 0;
|
|
481
|
+
width: 1px;
|
|
482
|
+
height: 100%;
|
|
483
|
+
background-color: rgba(255,0,0,0.6);
|
|
484
|
+
pointer-events: none;
|
|
485
|
+
transform: translateX(-0.5px);
|
|
486
|
+
`;
|
|
487
|
+
crosshair.appendChild(hLine);
|
|
488
|
+
crosshair.appendChild(vLine);
|
|
489
|
+
document.body.appendChild(crosshair);
|
|
490
|
+
this.crosshairEl = { container: crosshair, hLine, vLine };
|
|
491
|
+
|
|
445
492
|
let lastCursor = '';
|
|
446
493
|
this._onMouseMove = (e) => {
|
|
447
|
-
const
|
|
494
|
+
const x = e.clientX;
|
|
495
|
+
const y = e.clientY;
|
|
496
|
+
|
|
497
|
+
// 更新十字线位置
|
|
498
|
+
if (this.crosshairEl) {
|
|
499
|
+
this.crosshairEl.hLine.style.top = y + 'px';
|
|
500
|
+
this.crosshairEl.vLine.style.left = x + 'px';
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const target = document.elementFromPoint(x, y);
|
|
448
504
|
let cursorType = null;
|
|
449
505
|
if (target) {
|
|
450
506
|
if (target.dataset.cursor && this.cursors[target.dataset.cursor]) {
|
|
@@ -465,9 +521,9 @@
|
|
|
465
521
|
}
|
|
466
522
|
if (cursorType !== lastCursor) {
|
|
467
523
|
lastCursor = cursorType;
|
|
468
|
-
debugDiv.textContent =
|
|
524
|
+
debugDiv.textContent = `${cursorType} @ (${x}, ${y})`;
|
|
469
525
|
} else {
|
|
470
|
-
debugDiv.textContent =
|
|
526
|
+
debugDiv.textContent = `${cursorType} @ (${x}, ${y})`;
|
|
471
527
|
}
|
|
472
528
|
};
|
|
473
529
|
document.addEventListener('mousemove', this._onMouseMove);
|
|
@@ -480,6 +536,7 @@
|
|
|
480
536
|
this._injectStyles();
|
|
481
537
|
if (this.options.debug) {
|
|
482
538
|
if (this.debugEl) this.debugEl.remove();
|
|
539
|
+
if (this.crosshairEl) this.crosshairEl.container.remove();
|
|
483
540
|
this._initDebug();
|
|
484
541
|
}
|
|
485
542
|
console.log('[AnimeCursor] Refresh complete');
|
|
@@ -489,6 +546,7 @@
|
|
|
489
546
|
if (this.disabled) return;
|
|
490
547
|
if (this.styleEl) this.styleEl.remove();
|
|
491
548
|
if (this.debugEl) this.debugEl.remove();
|
|
549
|
+
if (this.crosshairEl) this.crosshairEl.container.remove();
|
|
492
550
|
if (this._onMouseMove) {
|
|
493
551
|
document.removeEventListener('mousemove', this._onMouseMove);
|
|
494
552
|
}
|
|
@@ -499,16 +557,18 @@
|
|
|
499
557
|
|
|
500
558
|
disable() {
|
|
501
559
|
if (this.disabled) return;
|
|
502
|
-
// 移除样式表
|
|
503
560
|
if (this.styleEl) {
|
|
504
561
|
this.styleEl.remove();
|
|
505
562
|
this.styleEl = null;
|
|
506
563
|
}
|
|
507
|
-
// 移除 debug 相关
|
|
508
564
|
if (this.debugEl) {
|
|
509
565
|
this.debugEl.remove();
|
|
510
566
|
this.debugEl = null;
|
|
511
567
|
}
|
|
568
|
+
if (this.crosshairEl) {
|
|
569
|
+
this.crosshairEl.container.remove();
|
|
570
|
+
this.crosshairEl = null;
|
|
571
|
+
}
|
|
512
572
|
if (this._onMouseMove) {
|
|
513
573
|
document.removeEventListener('mousemove', this._onMouseMove);
|
|
514
574
|
this._onMouseMove = null;
|
|
@@ -520,9 +580,7 @@
|
|
|
520
580
|
enable() {
|
|
521
581
|
if (!this.disabled) return;
|
|
522
582
|
this.disabled = false;
|
|
523
|
-
// 重新注入样式
|
|
524
583
|
this._injectStyles();
|
|
525
|
-
// 如果 debug 模式开启,重新初始化 debug
|
|
526
584
|
if (this.options.debug) {
|
|
527
585
|
this._initDebug();
|
|
528
586
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).AnimeCursor=t()}(this,function(){"use strict";let e=null;return class{static get instance(){return e}static destroy(){return!!e&&(e.destroy(),!0)}static refresh(){return!!e&&(e.refresh(),!0)}static disable(){return!!e&&(e.disable(),!0)}static enable(){return!!e&&(e.enable(),!0)}constructor(t={}){return e?(console.warn("[AnimeCursor] Instance already exists, returning existing one"),e):(this.options={debug:!1,enableTouch:!1,fallbackCursor:"auto",excludeSelectors:"input, textarea, [contenteditable]",combineAnimations:!1,...t},this.disabled=!1,this.cursors=this.options.cursors||{},this.cursorAnimationStrings={},this.combinedRules=new Map,this.options.enableTouch||this.isMouseLikeDevice()?(this.styleEl=null,this.debugEl=null,this._onMouseMove=null,this._validateOptions(),this._preloadImages(),this._checkDomLoad(),void(e=this)):(this.disabled=!0,void(this.options.debug&&console.warn("[AnimeCursor] Touch device detected, cursor animations disabled"))))}isMouseLikeDevice(){return window.matchMedia("(pointer: fine)").matches}_validateOptions(){if(this.disabled)return;if(!this.cursors||0===Object.keys(this.cursors).length)throw new Error("[AnimeCursor] At least one cursor must be defined");let e=!1;for(const[t,s]of Object.entries(this.cursors)){if(!s.image)throw new Error(`[AnimeCursor] Cursor "${t}" missing required setting: image`);if(void 0!==s.frames&&void 0!==s.duration){if(typeof s.frames!==typeof s.duration)console.warn(`[AnimeCursor] Cursor "${t}" has mismatched types for frames and duration, treating as static cursor`),delete s.frames,delete s.duration;else if(Array.isArray(s.frames)&&Array.isArray(s.duration))if(s.frames.length!==s.duration.length)console.warn(`[AnimeCursor] Cursor "${t}" frames and duration arrays have different lengths, treating as static cursor`),delete s.frames,delete s.duration;else{for(let e of s.frames)if(!Number.isInteger(e)||e<=0){console.warn(`[AnimeCursor] Cursor "${t}" frames array contains invalid value, treating as static cursor`),delete s.frames,delete s.duration;break}for(let e of s.duration)if("number"!=typeof e||e<=0){console.warn(`[AnimeCursor] Cursor "${t}" duration array contains invalid value, treating as static cursor`),delete s.frames,delete s.duration;break}}else"number"==typeof s.frames&&"number"==typeof s.duration?(s.frames<=0||s.duration<=0)&&(console.warn(`[AnimeCursor] Cursor "${t}" frames or duration <= 0, treating as static cursor`),delete s.frames,delete s.duration):(console.warn(`[AnimeCursor] Cursor "${t}" frames and duration must be both numbers or both arrays, treating as static cursor`),delete s.frames,delete s.duration)}else void 0===s.frames&&void 0===s.duration||(console.warn(`[AnimeCursor] Cursor "${t}" has only frames or duration defined, treating as static cursor`),delete s.frames,delete s.duration);if(s.tags&&!Array.isArray(s.tags))throw new Error(`[AnimeCursor] Cursor "${t}" tags must be an array`);if(s.default){if(e)throw new Error("[AnimeCursor] Only one default cursor allowed");e=!0}if(s.offset&&(!Array.isArray(s.offset)||2!==s.offset.length))throw new Error(`[AnimeCursor] Cursor "${t}" offset must be [x, y] array`)}this.defaultCursorName=e?Object.keys(this.cursors).find(e=>this.cursors[e].default):null}_preloadImages(){const e=new Set;for(const t of Object.values(this.cursors)){this._getFrameUrls(t).forEach(t=>e.add(t))}e.forEach(e=>{const t=document.createElement("link");t.rel="preload",t.as="image",t.href=e,e.startsWith("http")&&!e.startsWith(window.location.origin)&&(t.crossOrigin="anonymous"),document.head.appendChild(t)}),this.options.debug&&e.size&&console.info(`[AnimeCursor] Preloaded ${e.size} cursor images`)}_getFrameUrls(e){let t=1;void 0!==e.frames&&(Array.isArray(e.frames)?t=e.frames.reduce((e,t)=>e+t,0):"number"==typeof e.frames&&(t=e.frames));const{image:s}=e;if(1===t)return[s];const{prefix:r,suffix:o,startNum:i,numFormat:n,ext:a}=this._parseImagePattern(s),u=[];for(let e=0;e<t;e++){const t=i+e,s=`${r}${n?this._formatNumber(t,n):t}${o}${a}`;u.push(s)}return u}_parseImagePattern(e){const t=e.match(/\.[^.]+$/),s=t?t[0]:"",r=e.slice(0,-s.length),o=r.match(/(\d+)(?!.*\d)/);if(!o)return{prefix:r+"_",suffix:"",startNum:1,numFormat:null,ext:s};const i=o[0],n=parseInt(i,10),a=i.length;return{prefix:r.slice(0,o.index),suffix:r.slice(o.index+i.length),startNum:n,numFormat:a,ext:s}}_formatNumber(e,t){return String(e).padStart(t,"0")}_checkDomLoad(){const e=()=>{this._injectStyles(),this.options.debug&&this._initDebug(),console.log("[AnimeCursor] Initialization complete")};"loading"===document.readyState?document.addEventListener("DOMContentLoaded",e):e()}_injectStyles(){if(this.disabled)return;this.combinedRules.clear();const e=document.createElement("style");e.id="animecursor-styles";let t="";if(this.defaultCursorName){const e=this.cursors[this.defaultCursorName];t+=`* { ${this._buildCursorCss(this.defaultCursorName,e)} }\n`}for(const[e,s]of Object.entries(this.cursors)){const r=`.ac-cursor-${e}`,o=s.offset||[0,0],i=s.fallback||this.options.fallbackCursor,n=this._getFrameUrls(s),a=n.length;let u="";if(void 0!==s.frames&&void 0!==s.duration&&(Array.isArray(s.frames)&&Array.isArray(s.duration)||"number"==typeof s.frames&&"number"==typeof s.duration)&&a>1){const a=`ac_anim_${e}`;let l=`@keyframes ${a} {\n`;const d=this._buildKeyframes(s,n);for(const e of d){let t=(100*e.percent).toFixed(5);1===e.percent&&(t="100");l+=` ${t}% { ${`cursor: url("${e.url}") ${o[0]} ${o[1]}, ${i};`} }\n`}l+="}\n",t+=l;const c=`${a} ${Array.isArray(s.duration)?s.duration.reduce((e,t)=>e+t,0):s.duration}s steps(1) infinite ${s.pingpong?"alternate":""}`;u=c,t+=`${r} { cursor: url("${n[0]}") ${o[0]} ${o[1]}, ${i}; animation: ${c}; }\n`}else t+=`${r} { cursor: url("${n[0]}") ${o[0]} ${o[1]}, ${i}; }\n`;if(this.cursorAnimationStrings[e]=u,s.tags&&s.tags.length){t+=`${s.tags.join(", ")} { ${this._buildCursorCss(e,s)} }\n`}t+=`[data-cursor="${e}"] { ${this._buildCursorCss(e,s)} }\n`}if(this.options.excludeSelectors&&(t+=`${this.options.excludeSelectors} { cursor: text !important; animation: none !important; }\n`),this.options.combineAnimations){const e=document.querySelectorAll("[data-ac-animation]");for(const s of e){const e=s.getAttribute("data-ac-animation");if(!e)continue;let r=this._getCursorTypeForElement(s);if(!r)continue;const o=this.cursorAnimationStrings[r];if(!o)continue;const i=`${r}:${e}`;if(!this.combinedRules.has(i)){const s=`ac-combined-${this._simpleHash(i)}`;t+=`.${s} { animation: ${o}, ${e}; }\n`,this.combinedRules.set(i,s)}const n=this.combinedRules.get(i);s.classList.add(n)}}t+="body.animecursor-disabled * { cursor: auto !important; animation: none !important; }\n",e.textContent=t,document.head.appendChild(e),this.styleEl=e}_getCursorTypeForElement(e){if(e.dataset.cursor&&this.cursors[e.dataset.cursor])return e.dataset.cursor;for(const[t,s]of Object.entries(this.cursors))if(s.tags&&s.tags.some(t=>e.matches(t)))return t;return this.defaultCursorName}_buildKeyframes(e,t){let s=e.frames,r=e.duration;const o=t.length;if("number"==typeof s){const e=r/s;s=new Array(s).fill(1),r=new Array(s.length).fill(e)}const i=[];let n=r.reduce((e,t)=>e+t,0),a=0,u=0;for(let e=0;e<s.length;e++){const o=s[e],l=r[e]/o;for(let e=0;e<o;e++){const e=a/n;i.push({percent:e,url:t[u]}),a+=l,u++}}return i.push({percent:1,url:t[o-1]}),i}_buildCursorCss(e,t){const s=this._getFrameUrls(t),r=t.offset||[0,0],o=t.fallback||this.options.fallbackCursor;let i=`cursor: url("${s[0]}") ${r[0]} ${r[1]}, ${o};`;if(void 0!==t.frames&&void 0!==t.duration&&(Array.isArray(t.frames)&&Array.isArray(t.duration)||"number"==typeof t.frames&&"number"==typeof t.duration)&&s.length>1){i+=` animation: ac_anim_${e} ${Array.isArray(t.duration)?t.duration.reduce((e,t)=>e+t,0):t.duration}s steps(1) infinite ${t.pingpong?"alternate":""};`}return i}_simpleHash(e){let t=0;for(let s=0;s<e.length;s++){t=(t<<5)-t+e.charCodeAt(s),t|=0}return Math.abs(t).toString(36)}_initDebug(){const e=document.createElement("div");e.className="animecursor-debug",e.style.cssText="\n position: fixed;\n top: 0;\n left: 0;\n background: rgba(0,0,0,0.7);\n color: #0f0;\n padding: 4px 8px;\n font-family: monospace;\n font-size: 12px;\n z-index: 2147483647;\n pointer-events: none;\n white-space: nowrap;\n ",document.body.appendChild(e),this.debugEl=e;let t="";this._onMouseMove=s=>{const r=document.elementFromPoint(s.clientX,s.clientY);let o=null;if(r)if(r.dataset.cursor&&this.cursors[r.dataset.cursor])o=r.dataset.cursor;else for(const[e,t]of Object.entries(this.cursors))if(t.tags&&t.tags.some(e=>r.matches(e))){o=e;break}o||this.defaultCursorName?!o&&this.defaultCursorName&&(o=this.defaultCursorName):o="native",o!==t?(t=o,e.textContent=`🎯 ${o} @ (${s.clientX}, ${s.clientY})`):e.textContent=`🎯 ${o} @ (${s.clientX}, ${s.clientY})`},document.addEventListener("mousemove",this._onMouseMove)}refresh(){this.disabled||(this.styleEl&&this.styleEl.remove(),this.combinedRules.clear(),this._injectStyles(),this.options.debug&&(this.debugEl&&this.debugEl.remove(),this._initDebug()),console.log("[AnimeCursor] Refresh complete"))}destroy(){this.disabled||(this.styleEl&&this.styleEl.remove(),this.debugEl&&this.debugEl.remove(),this._onMouseMove&&document.removeEventListener("mousemove",this._onMouseMove),document.body.classList.remove("animecursor-disabled"),e=null,console.log("[AnimeCursor] Destroyed"))}disable(){this.disabled||(this.styleEl&&(this.styleEl.remove(),this.styleEl=null),this.debugEl&&(this.debugEl.remove(),this.debugEl=null),this._onMouseMove&&(document.removeEventListener("mousemove",this._onMouseMove),this._onMouseMove=null),this.disabled=!0,this.options.debug&&console.log("[AnimeCursor] Disabled"))}enable(){this.disabled&&(this.disabled=!1,this._injectStyles(),this.options.debug&&this._initDebug(),this.options.debug&&console.log("[AnimeCursor] Enabled"))}}});
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).AnimeCursor=t()}(this,function(){"use strict";let e=null;return class{static get instance(){return e}static destroy(){return!!e&&(e.destroy(),!0)}static refresh(){return!!e&&(e.refresh(),!0)}static disable(){return!!e&&(e.disable(),!0)}static enable(){return!!e&&(e.enable(),!0)}constructor(t={}){return e?(console.warn("[AnimeCursor] Instance already exists, returning existing one"),e):(this.options={debug:!1,enableTouch:!1,fallbackCursor:"auto",excludeSelectors:"input, textarea, [contenteditable]",combineAnimations:!1,...t},this.disabled=!1,this.cursors=this.options.cursors||{},this.cursorAnimationStrings={},this.combinedRules=new Map,this.options.enableTouch||this.isMouseLikeDevice()?(this.styleEl=null,this.debugEl=null,this.crosshairEl=null,this._onMouseMove=null,this._validateOptions(),this._preloadImages(),this._checkDomLoad(),void(e=this)):(this.disabled=!0,void(this.options.debug&&console.warn("[AnimeCursor] Touch device detected, cursor animations disabled"))))}isMouseLikeDevice(){return window.matchMedia("(pointer: fine)").matches}_validateOptions(){if(this.disabled)return;if(!this.cursors||0===Object.keys(this.cursors).length)throw new Error("[AnimeCursor] At least one cursor must be defined");let e=!1;for(const[t,s]of Object.entries(this.cursors)){if(!s.image)throw new Error(`[AnimeCursor] Cursor "${t}" missing required setting: image`);if(void 0!==s.frames&&void 0!==s.duration){if(typeof s.frames!==typeof s.duration)console.warn(`[AnimeCursor] Cursor "${t}" has mismatched types for frames and duration, treating as static cursor`),delete s.frames,delete s.duration;else if(Array.isArray(s.frames)&&Array.isArray(s.duration))if(s.frames.length!==s.duration.length)console.warn(`[AnimeCursor] Cursor "${t}" frames and duration arrays have different lengths, treating as static cursor`),delete s.frames,delete s.duration;else{for(let e of s.frames)if(!Number.isInteger(e)||e<=0){console.warn(`[AnimeCursor] Cursor "${t}" frames array contains invalid value, treating as static cursor`),delete s.frames,delete s.duration;break}for(let e of s.duration)if("number"!=typeof e||e<=0){console.warn(`[AnimeCursor] Cursor "${t}" duration array contains invalid value, treating as static cursor`),delete s.frames,delete s.duration;break}}else"number"==typeof s.frames&&"number"==typeof s.duration?(s.frames<=0||s.duration<=0)&&(console.warn(`[AnimeCursor] Cursor "${t}" frames or duration <= 0, treating as static cursor`),delete s.frames,delete s.duration):(console.warn(`[AnimeCursor] Cursor "${t}" frames and duration must be both numbers or both arrays, treating as static cursor`),delete s.frames,delete s.duration)}else void 0===s.frames&&void 0===s.duration||(console.warn(`[AnimeCursor] Cursor "${t}" has only frames or duration defined, treating as static cursor`),delete s.frames,delete s.duration);if(s.tags&&!Array.isArray(s.tags))throw new Error(`[AnimeCursor] Cursor "${t}" tags must be an array`);if(s.default){if(e)throw new Error("[AnimeCursor] Only one default cursor allowed");e=!0}if(s.offset&&(!Array.isArray(s.offset)||2!==s.offset.length))throw new Error(`[AnimeCursor] Cursor "${t}" offset must be [x, y] array`)}this.defaultCursorName=e?Object.keys(this.cursors).find(e=>this.cursors[e].default):null}_preloadImages(){const e=new Set;for(const t of Object.values(this.cursors)){this._getFrameUrls(t).forEach(t=>e.add(t))}e.forEach(e=>{const t=document.createElement("link");t.rel="preload",t.as="image",t.href=e,e.startsWith("http")&&!e.startsWith(window.location.origin)&&(t.crossOrigin="anonymous"),document.head.appendChild(t)}),this.options.debug&&e.size&&console.info(`[AnimeCursor] Preloaded ${e.size} cursor images`)}_getFrameUrls(e){let t=1;void 0!==e.frames&&(Array.isArray(e.frames)?t=e.frames.reduce((e,t)=>e+t,0):"number"==typeof e.frames&&(t=e.frames));const{image:s}=e;if(1===t)return[s];const{prefix:r,suffix:o,startNum:n,numFormat:i,ext:a}=this._parseImagePattern(s),u=[];for(let e=0;e<t;e++){const t=n+e,s=`${r}${i?this._formatNumber(t,i):t}${o}${a}`;u.push(s)}return u}_parseImagePattern(e){const t=e.match(/\.[^.]+$/),s=t?t[0]:"",r=e.slice(0,-s.length),o=r.match(/(\d+)(?!.*\d)/);if(!o)return{prefix:r+"_",suffix:"",startNum:1,numFormat:null,ext:s};const n=o[0],i=parseInt(n,10),a=n.length;return{prefix:r.slice(0,o.index),suffix:r.slice(o.index+n.length),startNum:i,numFormat:a,ext:s}}_formatNumber(e,t){return String(e).padStart(t,"0")}_checkDomLoad(){const e=()=>{this._injectStyles(),this.options.debug&&this._initDebug(),console.log("[AnimeCursor] Initialization complete")};"loading"===document.readyState?document.addEventListener("DOMContentLoaded",e):e()}_injectStyles(){if(this.disabled)return;this.combinedRules.clear();const e=document.createElement("style");e.id="animecursor-styles";let t="";if(this.defaultCursorName){const e=this.cursors[this.defaultCursorName];t+=`* { ${this._buildCursorCss(this.defaultCursorName,e)} }\n`}for(const[e,s]of Object.entries(this.cursors)){const r=`.ac-cursor-${e}`,o=s.offset||[0,0],n=s.fallback||this.options.fallbackCursor,i=this._getFrameUrls(s),a=i.length;let u="";if(void 0!==s.frames&&void 0!==s.duration&&(Array.isArray(s.frames)&&Array.isArray(s.duration)||"number"==typeof s.frames&&"number"==typeof s.duration)&&a>1){const a=`ac_anim_${e}`;let l=`@keyframes ${a} {\n`;const c=this._buildKeyframes(s,i);for(const e of c){let t=(100*e.percent).toFixed(5);1===e.percent&&(t="100");l+=` ${t}% { ${`cursor: url("${e.url}") ${o[0]} ${o[1]}, ${n};`} }\n`}l+="}\n",t+=l;const d=`${a} ${Array.isArray(s.duration)?s.duration.reduce((e,t)=>e+t,0):s.duration}s steps(1) infinite ${s.pingpong?"alternate":""}`;u=d,t+=`${r} { cursor: url("${i[0]}") ${o[0]} ${o[1]}, ${n}; animation: ${d}; }\n`}else{const s=`ac_anim_${e}_static`;t+=`@keyframes ${s} {\n`,t+=` 0%, 100% { cursor: url("${i[0]}") ${o[0]} ${o[1]}, ${n}; }\n`,t+="}\n";const a=`${s} 0.001s forwards steps(1)`;u=a,t+=`${r} { cursor: url("${i[0]}") ${o[0]} ${o[1]}, ${n}; animation: ${a}; }\n`}if(this.cursorAnimationStrings[e]=u,s.tags&&s.tags.length){t+=`${s.tags.join(", ")} { ${this._buildCursorCss(e,s)} }\n`}t+=`[data-cursor="${e}"] { ${this._buildCursorCss(e,s)} }\n`}if(this.options.excludeSelectors&&(t+=`${this.options.excludeSelectors} { cursor: text !important; animation: none !important; }\n`),this.options.combineAnimations){const e=document.querySelectorAll("[data-ac-animation]");for(const s of e){const e=s.getAttribute("data-ac-animation");if(!e)continue;let r=this._getCursorTypeForElement(s);if(!r)continue;const o=this.cursorAnimationStrings[r];if(!o)continue;const n=`${r}:${e}`;if(!this.combinedRules.has(n)){const s=`ac-combined-${this._simpleHash(n)}`;t+=`.${s} { animation: ${o}, ${e}; }\n`,this.combinedRules.set(n,s)}const i=this.combinedRules.get(n);s.classList.add(i)}}t+="body.animecursor-disabled * { cursor: auto !important; animation: none !important; }\n",e.textContent=t,document.head.appendChild(e),this.styleEl=e}_getCursorTypeForElement(e){if(e.dataset.cursor&&this.cursors[e.dataset.cursor])return e.dataset.cursor;for(const[t,s]of Object.entries(this.cursors))if(s.tags&&s.tags.some(t=>e.matches(t)))return t;return this.defaultCursorName}_buildKeyframes(e,t){let s=e.frames,r=e.duration;const o=t.length;if("number"==typeof s){const e=r/s;s=new Array(s).fill(1),r=new Array(s.length).fill(e)}const n=[];let i=r.reduce((e,t)=>e+t,0),a=0,u=0;for(let e=0;e<s.length;e++){const o=s[e],l=r[e]/o;for(let e=0;e<o;e++){const e=a/i;n.push({percent:e,url:t[u]}),a+=l,u++}}return n.push({percent:1,url:t[o-1]}),n}_buildCursorCss(e,t){const s=this._getFrameUrls(t),r=t.offset||[0,0],o=t.fallback||this.options.fallbackCursor;let n=`cursor: url("${s[0]}") ${r[0]} ${r[1]}, ${o};`;if(void 0!==t.frames&&void 0!==t.duration&&(Array.isArray(t.frames)&&Array.isArray(t.duration)||"number"==typeof t.frames&&"number"==typeof t.duration)&&s.length>1){n+=` animation: ac_anim_${e} ${Array.isArray(t.duration)?t.duration.reduce((e,t)=>e+t,0):t.duration}s steps(1) infinite ${t.pingpong?"alternate":""};`}else n+=` animation: ac_anim_${e}_static 0.001s forwards steps(1);`;return n}_simpleHash(e){let t=0;for(let s=0;s<e.length;s++){t=(t<<5)-t+e.charCodeAt(s),t|=0}return Math.abs(t).toString(36)}_initDebug(){const e=document.createElement("div");e.className="animecursor-debug",e.style.cssText="\n position: fixed;\n top: 0;\n left: 0;\n background: rgba(0,0,0,0.7);\n color: #0f0;\n padding: 4px 8px;\n font-family: monospace;\n font-size: 12px;\n z-index: 2147483647;\n pointer-events: auto;\n white-space: nowrap;\n transition: opacity 0.2s ease;\n ",e.addEventListener("mouseenter",()=>{e.style.opacity="0.5"}),e.addEventListener("mouseleave",()=>{e.style.opacity="1"}),document.body.appendChild(e),this.debugEl=e;const t=document.createElement("div");t.className="animecursor-crosshair",t.style.cssText="\n position: fixed;\n top: 0;\n left: 0;\n width: 0;\n height: 0;\n pointer-events: none;\n z-index: 2147483646;\n ";const s=document.createElement("div");s.className="ac-crosshair-h",s.style.cssText="\n position: fixed;\n left: 0;\n width: 100%;\n height: 1px;\n background-color: rgba(255,0,0,0.6);\n pointer-events: none;\n transform: translateY(-0.5px);\n ";const r=document.createElement("div");r.className="ac-crosshair-v",r.style.cssText="\n position: fixed;\n top: 0;\n width: 1px;\n height: 100%;\n background-color: rgba(255,0,0,0.6);\n pointer-events: none;\n transform: translateX(-0.5px);\n ",t.appendChild(s),t.appendChild(r),document.body.appendChild(t),this.crosshairEl={container:t,hLine:s,vLine:r};let o="";this._onMouseMove=t=>{const s=t.clientX,r=t.clientY;this.crosshairEl&&(this.crosshairEl.hLine.style.top=r+"px",this.crosshairEl.vLine.style.left=s+"px");const n=document.elementFromPoint(s,r);let i=null;if(n)if(n.dataset.cursor&&this.cursors[n.dataset.cursor])i=n.dataset.cursor;else for(const[e,t]of Object.entries(this.cursors))if(t.tags&&t.tags.some(e=>n.matches(e))){i=e;break}i||this.defaultCursorName?!i&&this.defaultCursorName&&(i=this.defaultCursorName):i="native",i!==o?(o=i,e.textContent=`${i} @ (${s}, ${r})`):e.textContent=`${i} @ (${s}, ${r})`},document.addEventListener("mousemove",this._onMouseMove)}refresh(){this.disabled||(this.styleEl&&this.styleEl.remove(),this.combinedRules.clear(),this._injectStyles(),this.options.debug&&(this.debugEl&&this.debugEl.remove(),this.crosshairEl&&this.crosshairEl.container.remove(),this._initDebug()),console.log("[AnimeCursor] Refresh complete"))}destroy(){this.disabled||(this.styleEl&&this.styleEl.remove(),this.debugEl&&this.debugEl.remove(),this.crosshairEl&&this.crosshairEl.container.remove(),this._onMouseMove&&document.removeEventListener("mousemove",this._onMouseMove),document.body.classList.remove("animecursor-disabled"),e=null,console.log("[AnimeCursor] Destroyed"))}disable(){this.disabled||(this.styleEl&&(this.styleEl.remove(),this.styleEl=null),this.debugEl&&(this.debugEl.remove(),this.debugEl=null),this.crosshairEl&&(this.crosshairEl.container.remove(),this.crosshairEl=null),this._onMouseMove&&(document.removeEventListener("mousemove",this._onMouseMove),this._onMouseMove=null),this.disabled=!0,this.options.debug&&console.log("[AnimeCursor] Disabled"))}enable(){this.disabled&&(this.disabled=!1,this._injectStyles(),this.options.debug&&this._initDebug(),this.options.debug&&console.log("[AnimeCursor] Enabled"))}}});
|