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 CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
 
8
8
 
9
- [[简体中文]](#animecursorsc)
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.1/dist/anime-cursor.umd.min.js"></script>
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.1/dist/anime-cursor.umd.min.js"></script>
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
@@ -1,5 +1,5 @@
1
1
  // AnimeCursor by github@ShuninYu
2
- // v2.1.0
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
- css += `${className} { cursor: url("${frameUrls[0]}") ${offset[0]} ${offset[1]}, ${fallback}; }\n`;
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; // 可能为 null
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
- position: fixed;
425
- top: 0;
426
- left: 0;
427
- background: rgba(0,0,0,0.7);
428
- color: #0f0;
429
- padding: 4px 8px;
430
- font-family: monospace;
431
- font-size: 12px;
432
- z-index: 2147483647;
433
- pointer-events: none;
434
- white-space: nowrap;
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 target = document.elementFromPoint(e.clientX, e.clientY);
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 = `🎯 ${cursorType} @ (${e.clientX}, ${e.clientY})`;
518
+ debugDiv.textContent = `${cursorType} @ (${x}, ${y})`;
463
519
  } else {
464
- debugDiv.textContent = `🎯 ${cursorType} @ (${e.clientX}, ${e.clientY})`;
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
  }
@@ -5,7 +5,7 @@
5
5
  })(this, (function () { 'use strict';
6
6
 
7
7
  // AnimeCursor by github@ShuninYu
8
- // v2.1.0
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
- css += `${className} { cursor: url("${frameUrls[0]}") ${offset[0]} ${offset[1]}, ${fallback}; }\n`;
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; // 可能为 null
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
- position: fixed;
431
- top: 0;
432
- left: 0;
433
- background: rgba(0,0,0,0.7);
434
- color: #0f0;
435
- padding: 4px 8px;
436
- font-family: monospace;
437
- font-size: 12px;
438
- z-index: 2147483647;
439
- pointer-events: none;
440
- white-space: nowrap;
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 target = document.elementFromPoint(e.clientX, e.clientY);
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 = `🎯 ${cursorType} @ (${e.clientX}, ${e.clientY})`;
524
+ debugDiv.textContent = `${cursorType} @ (${x}, ${y})`;
469
525
  } else {
470
- debugDiv.textContent = `🎯 ${cursorType} @ (${e.clientX}, ${e.clientY})`;
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"))}}});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anime-cursor",
3
- "version": "2.1.1",
3
+ "version": "2.1.3",
4
4
  "description": "A lightweight JavaScript library for animated custom cursors",
5
5
  "main": "dist/anime-cursor.umd.js",
6
6
  "module": "dist/anime-cursor.esm.js",