anime-cursor 2.1.0 → 2.1.2
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 +2 -2
- package/dist/anime-cursor.esm.js +36 -7
- package/dist/anime-cursor.umd.js +36 -7
- package/dist/anime-cursor.umd.min.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -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
|
|
@@ -218,7 +218,7 @@ AnimeCursor 无需依赖任何框架,适合个人网站、创意作品集以
|
|
|
218
218
|
### CDN
|
|
219
219
|
|
|
220
220
|
```html
|
|
221
|
-
<script src="https://cdn.jsdelivr.net/npm/anime-cursor@v2.1.
|
|
221
|
+
<script src="https://cdn.jsdelivr.net/npm/anime-cursor@v2.1.2/dist/anime-cursor.umd.min.js"></script>
|
|
222
222
|
```
|
|
223
223
|
|
|
224
224
|
### npm
|
package/dist/anime-cursor.esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// AnimeCursor by github@ShuninYu
|
|
2
|
-
// v2.1.
|
|
2
|
+
// v2.1.2
|
|
3
3
|
|
|
4
4
|
let _instance = null;
|
|
5
5
|
|
|
@@ -247,6 +247,7 @@ class AnimeCursor {
|
|
|
247
247
|
// 核心注入样式
|
|
248
248
|
_injectStyles() {
|
|
249
249
|
if (this.disabled) return;
|
|
250
|
+
this.combinedRules.clear();
|
|
250
251
|
|
|
251
252
|
const style = document.createElement('style');
|
|
252
253
|
style.id = 'animecursor-styles';
|
|
@@ -291,7 +292,14 @@ class AnimeCursor {
|
|
|
291
292
|
cursorAnimation = animation;
|
|
292
293
|
css += `${className} { cursor: url("${frameUrls[0]}") ${offset[0]} ${offset[1]}, ${fallback}; animation: ${animation}; }\n`;
|
|
293
294
|
} else {
|
|
294
|
-
|
|
295
|
+
// 静态光标:生成一帧动画,只播放一次,结束后保持最后一帧
|
|
296
|
+
const staticKeyframeName = `ac_anim_${name}_static`;
|
|
297
|
+
css += `@keyframes ${staticKeyframeName} {\n`;
|
|
298
|
+
css += ` 0%, 100% { cursor: url("${frameUrls[0]}") ${offset[0]} ${offset[1]}, ${fallback}; }\n`;
|
|
299
|
+
css += `}\n`;
|
|
300
|
+
const staticAnimation = `${staticKeyframeName} 0.001s forwards steps(1)`;
|
|
301
|
+
cursorAnimation = staticAnimation;
|
|
302
|
+
css += `${className} { cursor: url("${frameUrls[0]}") ${offset[0]} ${offset[1]}, ${fallback}; animation: ${staticAnimation}; }\n`;
|
|
295
303
|
}
|
|
296
304
|
|
|
297
305
|
this.cursorAnimationStrings[name] = cursorAnimation;
|
|
@@ -308,21 +316,19 @@ class AnimeCursor {
|
|
|
308
316
|
css += `${this.options.excludeSelectors} { cursor: text !important; animation: none !important; }\n`;
|
|
309
317
|
}
|
|
310
318
|
|
|
311
|
-
//
|
|
319
|
+
// 自动组合动画
|
|
312
320
|
if (this.options.combineAnimations) {
|
|
313
321
|
const elements = document.querySelectorAll('[data-ac-animation]');
|
|
314
322
|
for (const el of elements) {
|
|
315
323
|
const userAnim = el.getAttribute('data-ac-animation');
|
|
316
324
|
if (!userAnim) continue;
|
|
317
325
|
|
|
318
|
-
// 确定该元素应该使用哪个光标
|
|
319
326
|
let cursorName = this._getCursorTypeForElement(el);
|
|
320
327
|
if (!cursorName) continue;
|
|
321
328
|
|
|
322
329
|
const cursorAnim = this.cursorAnimationStrings[cursorName];
|
|
323
330
|
if (!cursorAnim) continue;
|
|
324
331
|
|
|
325
|
-
// 生成唯一标识
|
|
326
332
|
const key = `${cursorName}:${userAnim}`;
|
|
327
333
|
if (!this.combinedRules.has(key)) {
|
|
328
334
|
const hash = this._simpleHash(key);
|
|
@@ -396,12 +402,17 @@ class AnimeCursor {
|
|
|
396
402
|
const offset = cfg.offset || [0, 0];
|
|
397
403
|
const fallback = cfg.fallback || this.options.fallbackCursor;
|
|
398
404
|
let css = `cursor: url("${frameUrls[0]}") ${offset[0]} ${offset[1]}, ${fallback};`;
|
|
405
|
+
|
|
399
406
|
const hasAnimation = cfg.frames !== undefined && cfg.duration !== undefined &&
|
|
400
407
|
((Array.isArray(cfg.frames) && Array.isArray(cfg.duration)) ||
|
|
401
408
|
(typeof cfg.frames === 'number' && typeof cfg.duration === 'number'));
|
|
409
|
+
|
|
402
410
|
if (hasAnimation && frameUrls.length > 1) {
|
|
403
411
|
const totalDuration = Array.isArray(cfg.duration) ? cfg.duration.reduce((a, b) => a + b, 0) : cfg.duration;
|
|
404
412
|
css += ` animation: ac_anim_${name} ${totalDuration}s steps(1) infinite ${cfg.pingpong ? 'alternate' : ''};`;
|
|
413
|
+
} else {
|
|
414
|
+
// 静态光标:一帧动画,只播放一次,结束后保持
|
|
415
|
+
css += ` animation: ac_anim_${name}_static 0.001s forwards steps(1);`;
|
|
405
416
|
}
|
|
406
417
|
return css;
|
|
407
418
|
}
|
|
@@ -492,15 +503,33 @@ class AnimeCursor {
|
|
|
492
503
|
|
|
493
504
|
disable() {
|
|
494
505
|
if (this.disabled) return;
|
|
506
|
+
// 移除样式表
|
|
507
|
+
if (this.styleEl) {
|
|
508
|
+
this.styleEl.remove();
|
|
509
|
+
this.styleEl = null;
|
|
510
|
+
}
|
|
511
|
+
// 移除 debug 相关
|
|
512
|
+
if (this.debugEl) {
|
|
513
|
+
this.debugEl.remove();
|
|
514
|
+
this.debugEl = null;
|
|
515
|
+
}
|
|
516
|
+
if (this._onMouseMove) {
|
|
517
|
+
document.removeEventListener('mousemove', this._onMouseMove);
|
|
518
|
+
this._onMouseMove = null;
|
|
519
|
+
}
|
|
495
520
|
this.disabled = true;
|
|
496
|
-
document.body.classList.add('animecursor-disabled');
|
|
497
521
|
if (this.options.debug) console.log('[AnimeCursor] Disabled');
|
|
498
522
|
}
|
|
499
523
|
|
|
500
524
|
enable() {
|
|
501
525
|
if (!this.disabled) return;
|
|
502
526
|
this.disabled = false;
|
|
503
|
-
|
|
527
|
+
// 重新注入样式
|
|
528
|
+
this._injectStyles();
|
|
529
|
+
// 如果 debug 模式开启,重新初始化 debug
|
|
530
|
+
if (this.options.debug) {
|
|
531
|
+
this._initDebug();
|
|
532
|
+
}
|
|
504
533
|
if (this.options.debug) console.log('[AnimeCursor] Enabled');
|
|
505
534
|
}
|
|
506
535
|
}
|
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.2
|
|
9
9
|
|
|
10
10
|
let _instance = null;
|
|
11
11
|
|
|
@@ -253,6 +253,7 @@
|
|
|
253
253
|
// 核心注入样式
|
|
254
254
|
_injectStyles() {
|
|
255
255
|
if (this.disabled) return;
|
|
256
|
+
this.combinedRules.clear();
|
|
256
257
|
|
|
257
258
|
const style = document.createElement('style');
|
|
258
259
|
style.id = 'animecursor-styles';
|
|
@@ -297,7 +298,14 @@
|
|
|
297
298
|
cursorAnimation = animation;
|
|
298
299
|
css += `${className} { cursor: url("${frameUrls[0]}") ${offset[0]} ${offset[1]}, ${fallback}; animation: ${animation}; }\n`;
|
|
299
300
|
} else {
|
|
300
|
-
|
|
301
|
+
// 静态光标:生成一帧动画,只播放一次,结束后保持最后一帧
|
|
302
|
+
const staticKeyframeName = `ac_anim_${name}_static`;
|
|
303
|
+
css += `@keyframes ${staticKeyframeName} {\n`;
|
|
304
|
+
css += ` 0%, 100% { cursor: url("${frameUrls[0]}") ${offset[0]} ${offset[1]}, ${fallback}; }\n`;
|
|
305
|
+
css += `}\n`;
|
|
306
|
+
const staticAnimation = `${staticKeyframeName} 0.001s forwards steps(1)`;
|
|
307
|
+
cursorAnimation = staticAnimation;
|
|
308
|
+
css += `${className} { cursor: url("${frameUrls[0]}") ${offset[0]} ${offset[1]}, ${fallback}; animation: ${staticAnimation}; }\n`;
|
|
301
309
|
}
|
|
302
310
|
|
|
303
311
|
this.cursorAnimationStrings[name] = cursorAnimation;
|
|
@@ -314,21 +322,19 @@
|
|
|
314
322
|
css += `${this.options.excludeSelectors} { cursor: text !important; animation: none !important; }\n`;
|
|
315
323
|
}
|
|
316
324
|
|
|
317
|
-
//
|
|
325
|
+
// 自动组合动画
|
|
318
326
|
if (this.options.combineAnimations) {
|
|
319
327
|
const elements = document.querySelectorAll('[data-ac-animation]');
|
|
320
328
|
for (const el of elements) {
|
|
321
329
|
const userAnim = el.getAttribute('data-ac-animation');
|
|
322
330
|
if (!userAnim) continue;
|
|
323
331
|
|
|
324
|
-
// 确定该元素应该使用哪个光标
|
|
325
332
|
let cursorName = this._getCursorTypeForElement(el);
|
|
326
333
|
if (!cursorName) continue;
|
|
327
334
|
|
|
328
335
|
const cursorAnim = this.cursorAnimationStrings[cursorName];
|
|
329
336
|
if (!cursorAnim) continue;
|
|
330
337
|
|
|
331
|
-
// 生成唯一标识
|
|
332
338
|
const key = `${cursorName}:${userAnim}`;
|
|
333
339
|
if (!this.combinedRules.has(key)) {
|
|
334
340
|
const hash = this._simpleHash(key);
|
|
@@ -402,12 +408,17 @@
|
|
|
402
408
|
const offset = cfg.offset || [0, 0];
|
|
403
409
|
const fallback = cfg.fallback || this.options.fallbackCursor;
|
|
404
410
|
let css = `cursor: url("${frameUrls[0]}") ${offset[0]} ${offset[1]}, ${fallback};`;
|
|
411
|
+
|
|
405
412
|
const hasAnimation = cfg.frames !== undefined && cfg.duration !== undefined &&
|
|
406
413
|
((Array.isArray(cfg.frames) && Array.isArray(cfg.duration)) ||
|
|
407
414
|
(typeof cfg.frames === 'number' && typeof cfg.duration === 'number'));
|
|
415
|
+
|
|
408
416
|
if (hasAnimation && frameUrls.length > 1) {
|
|
409
417
|
const totalDuration = Array.isArray(cfg.duration) ? cfg.duration.reduce((a, b) => a + b, 0) : cfg.duration;
|
|
410
418
|
css += ` animation: ac_anim_${name} ${totalDuration}s steps(1) infinite ${cfg.pingpong ? 'alternate' : ''};`;
|
|
419
|
+
} else {
|
|
420
|
+
// 静态光标:一帧动画,只播放一次,结束后保持
|
|
421
|
+
css += ` animation: ac_anim_${name}_static 0.001s forwards steps(1);`;
|
|
411
422
|
}
|
|
412
423
|
return css;
|
|
413
424
|
}
|
|
@@ -498,15 +509,33 @@
|
|
|
498
509
|
|
|
499
510
|
disable() {
|
|
500
511
|
if (this.disabled) return;
|
|
512
|
+
// 移除样式表
|
|
513
|
+
if (this.styleEl) {
|
|
514
|
+
this.styleEl.remove();
|
|
515
|
+
this.styleEl = null;
|
|
516
|
+
}
|
|
517
|
+
// 移除 debug 相关
|
|
518
|
+
if (this.debugEl) {
|
|
519
|
+
this.debugEl.remove();
|
|
520
|
+
this.debugEl = null;
|
|
521
|
+
}
|
|
522
|
+
if (this._onMouseMove) {
|
|
523
|
+
document.removeEventListener('mousemove', this._onMouseMove);
|
|
524
|
+
this._onMouseMove = null;
|
|
525
|
+
}
|
|
501
526
|
this.disabled = true;
|
|
502
|
-
document.body.classList.add('animecursor-disabled');
|
|
503
527
|
if (this.options.debug) console.log('[AnimeCursor] Disabled');
|
|
504
528
|
}
|
|
505
529
|
|
|
506
530
|
enable() {
|
|
507
531
|
if (!this.disabled) return;
|
|
508
532
|
this.disabled = false;
|
|
509
|
-
|
|
533
|
+
// 重新注入样式
|
|
534
|
+
this._injectStyles();
|
|
535
|
+
// 如果 debug 模式开启,重新初始化 debug
|
|
536
|
+
if (this.options.debug) {
|
|
537
|
+
this._initDebug();
|
|
538
|
+
}
|
|
510
539
|
if (this.options.debug) console.log('[AnimeCursor] Enabled');
|
|
511
540
|
}
|
|
512
541
|
}
|
|
@@ -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,r]of Object.entries(this.cursors)){if(!r.image)throw new Error(`[AnimeCursor] Cursor "${t}" missing required setting: image`);if(void 0!==r.frames&&void 0!==r.duration){if(typeof r.frames!==typeof r.duration)console.warn(`[AnimeCursor] Cursor "${t}" has mismatched types for frames and duration, treating as static cursor`),delete r.frames,delete r.duration;else if(Array.isArray(r.frames)&&Array.isArray(r.duration))if(r.frames.length!==r.duration.length)console.warn(`[AnimeCursor] Cursor "${t}" frames and duration arrays have different lengths, treating as static cursor`),delete r.frames,delete r.duration;else{for(let e of r.frames)if(!Number.isInteger(e)||e<=0){console.warn(`[AnimeCursor] Cursor "${t}" frames array contains invalid value, treating as static cursor`),delete r.frames,delete r.duration;break}for(let e of r.duration)if("number"!=typeof e||e<=0){console.warn(`[AnimeCursor] Cursor "${t}" duration array contains invalid value, treating as static cursor`),delete r.frames,delete r.duration;break}}else"number"==typeof r.frames&&"number"==typeof r.duration?(r.frames<=0||r.duration<=0)&&(console.warn(`[AnimeCursor] Cursor "${t}" frames or duration <= 0, treating as static cursor`),delete r.frames,delete r.duration):(console.warn(`[AnimeCursor] Cursor "${t}" frames and duration must be both numbers or both arrays, treating as static cursor`),delete r.frames,delete r.duration)}else void 0===r.frames&&void 0===r.duration||(console.warn(`[AnimeCursor] Cursor "${t}" has only frames or duration defined, treating as static cursor`),delete r.frames,delete r.duration);if(r.tags&&!Array.isArray(r.tags))throw new Error(`[AnimeCursor] Cursor "${t}" tags must be an array`);if(r.default){if(e)throw new Error("[AnimeCursor] Only one default cursor allowed");e=!0}if(r.offset&&(!Array.isArray(r.offset)||2!==r.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:r}=e;if(1===t)return[r];const{prefix:s,suffix:o,startNum:i,numFormat:n,ext:a}=this._parseImagePattern(r),u=[];for(let e=0;e<t;e++){const t=i+e,r=`${s}${n?this._formatNumber(t,n):t}${o}${a}`;u.push(r)}return u}_parseImagePattern(e){const t=e.match(/\.[^.]+$/),r=t?t[0]:"",s=e.slice(0,-r.length),o=s.match(/(\d+)(?!.*\d)/);if(!o)return{prefix:s+"_",suffix:"",startNum:1,numFormat:null,ext:r};const i=o[0],n=parseInt(i,10),a=i.length;return{prefix:s.slice(0,o.index),suffix:s.slice(o.index+i.length),startNum:n,numFormat:a,ext:r}}_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;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,r]of Object.entries(this.cursors)){const s=`.ac-cursor-${e}`,o=r.offset||[0,0],i=r.fallback||this.options.fallbackCursor,n=this._getFrameUrls(r),a=n.length;let u="";if(void 0!==r.frames&&void 0!==r.duration&&(Array.isArray(r.frames)&&Array.isArray(r.duration)||"number"==typeof r.frames&&"number"==typeof r.duration)&&a>1){const a=`ac_anim_${e}`;let l=`@keyframes ${a} {\n`;const d=this._buildKeyframes(r,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(r.duration)?r.duration.reduce((e,t)=>e+t,0):r.duration}s steps(1) infinite ${r.pingpong?"alternate":""}`;u=c,t+=`${s} { cursor: url("${n[0]}") ${o[0]} ${o[1]}, ${i}; animation: ${c}; }\n`}else t+=`${s} { cursor: url("${n[0]}") ${o[0]} ${o[1]}, ${i}; }\n`;if(this.cursorAnimationStrings[e]=u,r.tags&&r.tags.length){t+=`${r.tags.join(", ")} { ${this._buildCursorCss(e,r)} }\n`}t+=`[data-cursor="${e}"] { ${this._buildCursorCss(e,r)} }\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 r of e){const e=r.getAttribute("data-ac-animation");if(!e)continue;let s=this._getCursorTypeForElement(r);if(!s)continue;const o=this.cursorAnimationStrings[s];if(!o)continue;const i=`${s}:${e}`;if(!this.combinedRules.has(i)){const r=`ac-combined-${this._simpleHash(i)}`;t+=`.${r} { animation: ${o}, ${e}; }\n`,this.combinedRules.set(i,r)}const n=this.combinedRules.get(i);r.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,r]of Object.entries(this.cursors))if(r.tags&&r.tags.some(t=>e.matches(t)))return t;return this.defaultCursorName}_buildKeyframes(e,t){let r=e.frames,s=e.duration;const o=t.length;if("number"==typeof r){const e=s/r;r=new Array(r).fill(1),s=new Array(r.length).fill(e)}const i=[];let n=s.reduce((e,t)=>e+t,0),a=0,u=0;for(let e=0;e<r.length;e++){const o=r[e],l=s[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 r=this._getFrameUrls(t),s=t.offset||[0,0],o=t.fallback||this.options.fallbackCursor;let i=`cursor: url("${r[0]}") ${s[0]} ${s[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)&&r.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 r=0;r<e.length;r++){t=(t<<5)-t+e.charCodeAt(r),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=r=>{const s=document.elementFromPoint(r.clientX,r.clientY);let o=null;if(s)if(s.dataset.cursor&&this.cursors[s.dataset.cursor])o=s.dataset.cursor;else for(const[e,t]of Object.entries(this.cursors))if(t.tags&&t.tags.some(e=>s.matches(e))){o=e;break}o||this.defaultCursorName?!o&&this.defaultCursorName&&(o=this.defaultCursorName):o="native",o!==t?(t=o,e.textContent=`🎯 ${o} @ (${r.clientX}, ${r.clientY})`):e.textContent=`🎯 ${o} @ (${r.clientX}, ${r.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.disabled=!0,document.body.classList.add("animecursor-disabled"),this.options.debug&&console.log("[AnimeCursor] Disabled"))}enable(){this.disabled&&(this.disabled=!1,document.body.classList.remove("animecursor-disabled"),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._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{const s=`ac_anim_${e}_static`;t+=`@keyframes ${s} {\n`,t+=` 0%, 100% { cursor: url("${n[0]}") ${o[0]} ${o[1]}, ${i}; }\n`,t+="}\n";const a=`${s} 0.001s forwards steps(1)`;u=a,t+=`${r} { cursor: url("${n[0]}") ${o[0]} ${o[1]}, ${i}; 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 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":""};`}else i+=` animation: ac_anim_${e}_static 0.001s forwards steps(1);`;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"))}}});
|