anime-cursor 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -7
- package/dist/anime-cursor.esm.js +152 -21
- package/dist/anime-cursor.umd.js +160 -29
- package/dist/anime-cursor.umd.min.js +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
## [Visit the official website](https://shuninyu.github.io/anime-cursor/) for more informations
|
|
9
9
|
## [Read documents](https://shuninyu.github.io/anime-cursor/docs) to get started with AnimeCursor
|
|
10
10
|
|
|
11
|
-
AnimeCursor is a lightweight JavaScript library for animated custom cursors.
|
|
11
|
+
AnimeCursor is a lightweight JavaScript library for frame by frame animated custom cursors.
|
|
12
12
|
|
|
13
13
|
AnimeCursor has no dependencies on any frameworks, making it suitable for personal websites, creative portfolios, and experimental UI projects.
|
|
14
14
|
|
|
@@ -41,16 +41,20 @@ import AnimeCursor from 'anime-cursor';
|
|
|
41
41
|
new AnimeCursor({...});
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
###
|
|
44
|
+
### Host Yourself
|
|
45
45
|
|
|
46
46
|
```html
|
|
47
47
|
<script src="anime-cursor.umd.min.js"></script>
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
## 🚀
|
|
50
|
+
## 🚀 How to use
|
|
51
51
|
|
|
52
52
|
Here is an example of how to use AnimeCursor:
|
|
53
53
|
|
|
54
|
+
‌**IMPORTANT**‌
|
|
55
|
+
- Ensure the initialization code is placed **within** the `<body>` tag of your HTML document.
|
|
56
|
+
- For optimal performance, it is recommended to initialize AnimeCursor **before** the DOM has fully loaded, as certain features require execution prior to the completion of DOM loading.
|
|
57
|
+
|
|
54
58
|
```html
|
|
55
59
|
<script>
|
|
56
60
|
new AnimeCursor({
|
|
@@ -58,7 +62,7 @@ new AnimeCursor({
|
|
|
58
62
|
// each type of cursor needs tags, size and image
|
|
59
63
|
idle: {
|
|
60
64
|
size: [64,64],
|
|
61
|
-
image: '
|
|
65
|
+
image: 'https://example.com/cursor_default.png', // static cursor only needs image
|
|
62
66
|
default: true // set this cursor as default cursor
|
|
63
67
|
// only default cursor doesn't needs tags
|
|
64
68
|
},
|
|
@@ -66,7 +70,7 @@ new AnimeCursor({
|
|
|
66
70
|
pointer: {
|
|
67
71
|
tags: ['a', 'button'],
|
|
68
72
|
size: [64,64],
|
|
69
|
-
image: '
|
|
73
|
+
image: 'https://example.com/cursor_pointer.png',
|
|
70
74
|
frames: 3,
|
|
71
75
|
duration: 0.3,
|
|
72
76
|
pingpong: true, // enable pingpong loop
|
|
@@ -76,7 +80,7 @@ new AnimeCursor({
|
|
|
76
80
|
text: {
|
|
77
81
|
tags: ['p', 'h1', 'h2', 'span'],
|
|
78
82
|
size: [32, 64],
|
|
79
|
-
image: '
|
|
83
|
+
image: 'https://example.com/cursor_text.gif'
|
|
80
84
|
}
|
|
81
85
|
}
|
|
82
86
|
});
|
|
@@ -167,7 +171,7 @@ If `duration` is not set, the cursor will be treated as a **static cursor**, eve
|
|
|
167
171
|
## [访问官网](https://shuninyu.github.io/anime-cursor/)以获取更多信息
|
|
168
172
|
## [阅读文档](https://shuninyu.github.io/anime-cursor/docs/zh)快速上手 AnimeCursor
|
|
169
173
|
|
|
170
|
-
AnimeCursor
|
|
174
|
+
AnimeCursor 是一个轻量级自定义逐帧动画光标 JavaScript 库。
|
|
171
175
|
|
|
172
176
|
AnimeCursor 无需依赖任何框架,适合个人网站、创意作品集以及实验性 UI 项目。
|
|
173
177
|
|
|
@@ -209,6 +213,11 @@ new AnimeCursor({...});
|
|
|
209
213
|
## 🚀 基础用法
|
|
210
214
|
|
|
211
215
|
下面是一个 AnimeCursor 使用示例:
|
|
216
|
+
|
|
217
|
+
**重要提示**
|
|
218
|
+
- 请务必将初始化代码置于HTML文档的 **`<body>`** 标签内部。
|
|
219
|
+
- 为获得最佳性能,建议在DOM完全加载**之前**初始化AnimeCursor,因其部分功能需在DOM加载完成前执行。
|
|
220
|
+
|
|
212
221
|
```html
|
|
213
222
|
<script>
|
|
214
223
|
new AnimeCursor({
|
package/dist/anime-cursor.esm.js
CHANGED
|
@@ -1,10 +1,56 @@
|
|
|
1
1
|
// AnimeCursor by github@ShuninYu
|
|
2
|
-
// v0.
|
|
2
|
+
// v0.3.0
|
|
3
|
+
|
|
4
|
+
// 静态变量存储唯一实例
|
|
5
|
+
let _instance = null;
|
|
3
6
|
|
|
4
7
|
class AnimeCursor {
|
|
5
8
|
|
|
9
|
+
static get instance() {
|
|
10
|
+
return _instance;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
static destroy() {
|
|
14
|
+
if (_instance) {
|
|
15
|
+
_instance.destroy();
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static refresh() {
|
|
22
|
+
if (_instance) {
|
|
23
|
+
_instance.refresh();
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static disable() {
|
|
30
|
+
if (_instance) {
|
|
31
|
+
_instance.disable();
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static enable() {
|
|
38
|
+
if (_instance) {
|
|
39
|
+
_instance.enable();
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
6
45
|
constructor(options = {}) {
|
|
46
|
+
// 如果已有实例,直接返回它
|
|
47
|
+
if (_instance) {
|
|
48
|
+
console.warn('[AnimeCursor] AnimeCursor already exists.');
|
|
49
|
+
return _instance;
|
|
50
|
+
}
|
|
51
|
+
|
|
7
52
|
this.options = {
|
|
53
|
+
displayOnLoad: false,
|
|
8
54
|
enableTouch: false,
|
|
9
55
|
debug: false,
|
|
10
56
|
...options
|
|
@@ -23,12 +69,15 @@ class AnimeCursor {
|
|
|
23
69
|
this.cursorEl = null;
|
|
24
70
|
this.lastCursorType = null;
|
|
25
71
|
this.debugEl = null;
|
|
72
|
+
this.styleEl = null;
|
|
73
|
+
this._onMouseMove = null;
|
|
26
74
|
|
|
27
75
|
this._validateOptions();
|
|
28
|
-
this.
|
|
29
|
-
this.
|
|
30
|
-
|
|
31
|
-
|
|
76
|
+
this._injectPreload();
|
|
77
|
+
this._checkDomLoad();
|
|
78
|
+
|
|
79
|
+
// 保存实例引用
|
|
80
|
+
_instance = this;
|
|
32
81
|
}
|
|
33
82
|
|
|
34
83
|
isMouseLikeDevice() {
|
|
@@ -75,19 +124,28 @@ class AnimeCursor {
|
|
|
75
124
|
this.styleEl = null;
|
|
76
125
|
}
|
|
77
126
|
|
|
78
|
-
// 4 清理 data-cursor
|
|
127
|
+
// 4 清理 data-cursor(只清理由 AnimeCursor 添加的)
|
|
79
128
|
for (const cfg of Object.values(this.options.cursors)) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
129
|
+
// v0.2.1 添加检查:只有存在且为数组的 tags 才进行处理
|
|
130
|
+
if (cfg.tags && Array.isArray(cfg.tags)) {
|
|
131
|
+
cfg.tags.forEach(tag => {
|
|
132
|
+
document.querySelectorAll(tag).forEach(el => {
|
|
133
|
+
if (el.dataset.cursorBound) {
|
|
134
|
+
delete el.dataset.cursor;
|
|
135
|
+
delete el.dataset.cursorBound;
|
|
136
|
+
}
|
|
137
|
+
});
|
|
85
138
|
});
|
|
86
|
-
}
|
|
139
|
+
}
|
|
87
140
|
}
|
|
88
141
|
|
|
89
142
|
// 5 重置状态
|
|
90
143
|
this.lastCursorType = null;
|
|
144
|
+
|
|
145
|
+
// 清除静态引用
|
|
146
|
+
if (_instance === this) {
|
|
147
|
+
_instance = null;
|
|
148
|
+
}
|
|
91
149
|
}
|
|
92
150
|
disable() {
|
|
93
151
|
if (this.disabled) return;
|
|
@@ -95,6 +153,7 @@ class AnimeCursor {
|
|
|
95
153
|
|
|
96
154
|
if (this.cursorEl) {
|
|
97
155
|
this.cursorEl.style.display = 'none';
|
|
156
|
+
this.styleEl.innerHTML = this.styleEl.innerHTML.replace('* {cursor: none !important;}', '');
|
|
98
157
|
console.log('[AnimeCursor] AnimeCursor disabled!');
|
|
99
158
|
}
|
|
100
159
|
}
|
|
@@ -104,6 +163,7 @@ class AnimeCursor {
|
|
|
104
163
|
|
|
105
164
|
if (this.cursorEl) {
|
|
106
165
|
this.cursorEl.style.display = '';
|
|
166
|
+
this.styleEl.innerHTML += '* {cursor: none; !important;}';
|
|
107
167
|
console.log('[AnimeCursor] AnimeCursor enabled!');
|
|
108
168
|
}
|
|
109
169
|
}
|
|
@@ -124,7 +184,7 @@ class AnimeCursor {
|
|
|
124
184
|
for (const [name, cfg] of Object.entries(this.options.cursors)) {
|
|
125
185
|
if (cfg.default === true) {
|
|
126
186
|
if (this.defaultCursorType) {
|
|
127
|
-
throw new Error('[AnimeCursor]
|
|
187
|
+
throw new Error('[AnimeCursor] There can only be one default cursor');
|
|
128
188
|
}
|
|
129
189
|
this.defaultCursorType = name;
|
|
130
190
|
}
|
|
@@ -157,6 +217,64 @@ class AnimeCursor {
|
|
|
157
217
|
}
|
|
158
218
|
}
|
|
159
219
|
|
|
220
|
+
// ----------------------------
|
|
221
|
+
// 等待 DOM 加载完成
|
|
222
|
+
// ----------------------------
|
|
223
|
+
_checkDomLoad() {
|
|
224
|
+
const init = () => {
|
|
225
|
+
this._injectHTML();
|
|
226
|
+
this._injectCSS();
|
|
227
|
+
this._bindElements();
|
|
228
|
+
this._bindMouse();
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
if (document.readyState === 'loading') {
|
|
232
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
233
|
+
} else {
|
|
234
|
+
init();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ----------------------------
|
|
239
|
+
// 插入光标图片预加载()
|
|
240
|
+
// ----------------------------
|
|
241
|
+
_injectPreload() {
|
|
242
|
+
if (this.disabled) return;
|
|
243
|
+
|
|
244
|
+
// 收集所有需要预加载的图片URL
|
|
245
|
+
const imageUrls = new Set();
|
|
246
|
+
|
|
247
|
+
// 遍历所有光标配置,提取图片URL
|
|
248
|
+
for (const cfg of Object.values(this.options.cursors)) {
|
|
249
|
+
if (cfg.image) {
|
|
250
|
+
imageUrls.add(cfg.image);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// 为每个图片URL创建预加载标签
|
|
255
|
+
imageUrls.forEach(url => {
|
|
256
|
+
const link = document.createElement('link');
|
|
257
|
+
link.rel = 'preload';
|
|
258
|
+
link.as = 'image';
|
|
259
|
+
link.href = url;
|
|
260
|
+
|
|
261
|
+
// 可选:添加跨域处理(如果图片来自不同域名)
|
|
262
|
+
if (url.startsWith('http') && !url.startsWith(window.location.origin)) {
|
|
263
|
+
link.crossOrigin = 'anonymous';
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
document.head.appendChild(link);
|
|
267
|
+
|
|
268
|
+
if (this.options.debug) {
|
|
269
|
+
console.info(`[AnimeCursor] Preloading image: ${url}`);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
if (this.options.debug && imageUrls.size > 0) {
|
|
274
|
+
console.info(`[AnimeCursor] Preloaded ${imageUrls.size} cursor image(s)`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
160
278
|
// ----------------------------
|
|
161
279
|
// 插入光标元素 HTML
|
|
162
280
|
// ----------------------------
|
|
@@ -166,7 +284,7 @@ class AnimeCursor {
|
|
|
166
284
|
const cursor = document.createElement('div');
|
|
167
285
|
cursor.id = 'anime-cursor';
|
|
168
286
|
|
|
169
|
-
// 如果debug选项存在,则添加debug
|
|
287
|
+
// 如果debug选项存在,则添加debug元素
|
|
170
288
|
if (this.options.debug) {
|
|
171
289
|
cursor.className = 'cursor-default cursor-debugmode';
|
|
172
290
|
const debuger = document.createElement('div');
|
|
@@ -175,6 +293,14 @@ class AnimeCursor {
|
|
|
175
293
|
this.debugEl = debuger;
|
|
176
294
|
}
|
|
177
295
|
else {cursor.className = 'cursor-default';}
|
|
296
|
+
|
|
297
|
+
// 检查是否设置初始化时显示光标
|
|
298
|
+
if (this.options.displayOnLoad) {
|
|
299
|
+
cursor.style.display = 'block';
|
|
300
|
+
} else {
|
|
301
|
+
cursor.style.display = 'none';
|
|
302
|
+
cursor.dataset.animecursorHide = 'true';
|
|
303
|
+
}
|
|
178
304
|
document.body.appendChild(cursor);
|
|
179
305
|
this.cursorEl = cursor;
|
|
180
306
|
}
|
|
@@ -186,13 +312,12 @@ class AnimeCursor {
|
|
|
186
312
|
if (this.disabled) return;
|
|
187
313
|
|
|
188
314
|
const style = document.createElement('style');
|
|
315
|
+
style.id = 'animecursor-styles';
|
|
189
316
|
let css = '';
|
|
190
317
|
|
|
191
318
|
/* 通用样式 */
|
|
192
319
|
css += `
|
|
193
|
-
* {
|
|
194
|
-
cursor: none !important;
|
|
195
|
-
}
|
|
320
|
+
* {cursor: none !important;}
|
|
196
321
|
#anime-cursor {
|
|
197
322
|
position: fixed;
|
|
198
323
|
top: 0;
|
|
@@ -313,7 +438,7 @@ class AnimeCursor {
|
|
|
313
438
|
});
|
|
314
439
|
}
|
|
315
440
|
if (refresh) {
|
|
316
|
-
console.info('[AnimeCursor] refresh done
|
|
441
|
+
console.info('[AnimeCursor] refresh done');
|
|
317
442
|
}
|
|
318
443
|
}
|
|
319
444
|
|
|
@@ -324,12 +449,18 @@ class AnimeCursor {
|
|
|
324
449
|
if (this.disabled) return;
|
|
325
450
|
|
|
326
451
|
this._onMouseMove = (e) => {
|
|
452
|
+
if (this.disabled) return;
|
|
453
|
+
|
|
327
454
|
const x = e.clientX;
|
|
328
455
|
const y = e.clientY;
|
|
329
456
|
|
|
330
457
|
this.cursorEl.style.left = x + 'px';
|
|
331
458
|
this.cursorEl.style.top = y + 'px';
|
|
332
459
|
|
|
460
|
+
if (this.cursorEl.dataset.animecursorHide) {
|
|
461
|
+
this.cursorEl.style.display = 'block';
|
|
462
|
+
}
|
|
463
|
+
|
|
333
464
|
if (this.debugEl) {
|
|
334
465
|
this.debugEl.style.left = x + 'px';
|
|
335
466
|
this.debugEl.style.top = y + 'px';
|
|
@@ -373,6 +504,6 @@ class AnimeCursor {
|
|
|
373
504
|
|
|
374
505
|
return 2147483646; // 浏览器安全最大值 2147483647 减一为留给debug覆盖
|
|
375
506
|
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
export { AnimeCursor as default };
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
export { AnimeCursor as default };
|
package/dist/anime-cursor.umd.js
CHANGED
|
@@ -1,16 +1,62 @@
|
|
|
1
|
-
(function (global, factory) {
|
|
2
|
-
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
3
|
-
typeof define === 'function' && define.amd ? define(factory) :
|
|
4
|
-
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.AnimeCursor = factory());
|
|
5
|
-
})(this, (function () { 'use strict';
|
|
6
|
-
|
|
1
|
+
(function (global, factory) {
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.AnimeCursor = factory());
|
|
5
|
+
})(this, (function () { 'use strict';
|
|
6
|
+
|
|
7
7
|
// AnimeCursor by github@ShuninYu
|
|
8
|
-
// v0.
|
|
8
|
+
// v0.3.0
|
|
9
|
+
|
|
10
|
+
// 静态变量存储唯一实例
|
|
11
|
+
let _instance = null;
|
|
9
12
|
|
|
10
13
|
class AnimeCursor {
|
|
11
14
|
|
|
15
|
+
static get instance() {
|
|
16
|
+
return _instance;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static destroy() {
|
|
20
|
+
if (_instance) {
|
|
21
|
+
_instance.destroy();
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static refresh() {
|
|
28
|
+
if (_instance) {
|
|
29
|
+
_instance.refresh();
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static disable() {
|
|
36
|
+
if (_instance) {
|
|
37
|
+
_instance.disable();
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static enable() {
|
|
44
|
+
if (_instance) {
|
|
45
|
+
_instance.enable();
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
12
51
|
constructor(options = {}) {
|
|
52
|
+
// 如果已有实例,直接返回它
|
|
53
|
+
if (_instance) {
|
|
54
|
+
console.warn('[AnimeCursor] AnimeCursor already exists.');
|
|
55
|
+
return _instance;
|
|
56
|
+
}
|
|
57
|
+
|
|
13
58
|
this.options = {
|
|
59
|
+
displayOnLoad: false,
|
|
14
60
|
enableTouch: false,
|
|
15
61
|
debug: false,
|
|
16
62
|
...options
|
|
@@ -29,12 +75,15 @@
|
|
|
29
75
|
this.cursorEl = null;
|
|
30
76
|
this.lastCursorType = null;
|
|
31
77
|
this.debugEl = null;
|
|
78
|
+
this.styleEl = null;
|
|
79
|
+
this._onMouseMove = null;
|
|
32
80
|
|
|
33
81
|
this._validateOptions();
|
|
34
|
-
this.
|
|
35
|
-
this.
|
|
36
|
-
|
|
37
|
-
|
|
82
|
+
this._injectPreload();
|
|
83
|
+
this._checkDomLoad();
|
|
84
|
+
|
|
85
|
+
// 保存实例引用
|
|
86
|
+
_instance = this;
|
|
38
87
|
}
|
|
39
88
|
|
|
40
89
|
isMouseLikeDevice() {
|
|
@@ -81,19 +130,28 @@
|
|
|
81
130
|
this.styleEl = null;
|
|
82
131
|
}
|
|
83
132
|
|
|
84
|
-
// 4 清理 data-cursor
|
|
133
|
+
// 4 清理 data-cursor(只清理由 AnimeCursor 添加的)
|
|
85
134
|
for (const cfg of Object.values(this.options.cursors)) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
135
|
+
// v0.2.1 添加检查:只有存在且为数组的 tags 才进行处理
|
|
136
|
+
if (cfg.tags && Array.isArray(cfg.tags)) {
|
|
137
|
+
cfg.tags.forEach(tag => {
|
|
138
|
+
document.querySelectorAll(tag).forEach(el => {
|
|
139
|
+
if (el.dataset.cursorBound) {
|
|
140
|
+
delete el.dataset.cursor;
|
|
141
|
+
delete el.dataset.cursorBound;
|
|
142
|
+
}
|
|
143
|
+
});
|
|
91
144
|
});
|
|
92
|
-
}
|
|
145
|
+
}
|
|
93
146
|
}
|
|
94
147
|
|
|
95
148
|
// 5 重置状态
|
|
96
149
|
this.lastCursorType = null;
|
|
150
|
+
|
|
151
|
+
// 清除静态引用
|
|
152
|
+
if (_instance === this) {
|
|
153
|
+
_instance = null;
|
|
154
|
+
}
|
|
97
155
|
}
|
|
98
156
|
disable() {
|
|
99
157
|
if (this.disabled) return;
|
|
@@ -101,6 +159,7 @@
|
|
|
101
159
|
|
|
102
160
|
if (this.cursorEl) {
|
|
103
161
|
this.cursorEl.style.display = 'none';
|
|
162
|
+
this.styleEl.innerHTML = this.styleEl.innerHTML.replace('* {cursor: none !important;}', '');
|
|
104
163
|
console.log('[AnimeCursor] AnimeCursor disabled!');
|
|
105
164
|
}
|
|
106
165
|
}
|
|
@@ -110,6 +169,7 @@
|
|
|
110
169
|
|
|
111
170
|
if (this.cursorEl) {
|
|
112
171
|
this.cursorEl.style.display = '';
|
|
172
|
+
this.styleEl.innerHTML += '* {cursor: none; !important;}';
|
|
113
173
|
console.log('[AnimeCursor] AnimeCursor enabled!');
|
|
114
174
|
}
|
|
115
175
|
}
|
|
@@ -130,7 +190,7 @@
|
|
|
130
190
|
for (const [name, cfg] of Object.entries(this.options.cursors)) {
|
|
131
191
|
if (cfg.default === true) {
|
|
132
192
|
if (this.defaultCursorType) {
|
|
133
|
-
throw new Error('[AnimeCursor]
|
|
193
|
+
throw new Error('[AnimeCursor] There can only be one default cursor');
|
|
134
194
|
}
|
|
135
195
|
this.defaultCursorType = name;
|
|
136
196
|
}
|
|
@@ -163,6 +223,64 @@
|
|
|
163
223
|
}
|
|
164
224
|
}
|
|
165
225
|
|
|
226
|
+
// ----------------------------
|
|
227
|
+
// 等待 DOM 加载完成
|
|
228
|
+
// ----------------------------
|
|
229
|
+
_checkDomLoad() {
|
|
230
|
+
const init = () => {
|
|
231
|
+
this._injectHTML();
|
|
232
|
+
this._injectCSS();
|
|
233
|
+
this._bindElements();
|
|
234
|
+
this._bindMouse();
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
if (document.readyState === 'loading') {
|
|
238
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
239
|
+
} else {
|
|
240
|
+
init();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ----------------------------
|
|
245
|
+
// 插入光标图片预加载()
|
|
246
|
+
// ----------------------------
|
|
247
|
+
_injectPreload() {
|
|
248
|
+
if (this.disabled) return;
|
|
249
|
+
|
|
250
|
+
// 收集所有需要预加载的图片URL
|
|
251
|
+
const imageUrls = new Set();
|
|
252
|
+
|
|
253
|
+
// 遍历所有光标配置,提取图片URL
|
|
254
|
+
for (const cfg of Object.values(this.options.cursors)) {
|
|
255
|
+
if (cfg.image) {
|
|
256
|
+
imageUrls.add(cfg.image);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// 为每个图片URL创建预加载标签
|
|
261
|
+
imageUrls.forEach(url => {
|
|
262
|
+
const link = document.createElement('link');
|
|
263
|
+
link.rel = 'preload';
|
|
264
|
+
link.as = 'image';
|
|
265
|
+
link.href = url;
|
|
266
|
+
|
|
267
|
+
// 可选:添加跨域处理(如果图片来自不同域名)
|
|
268
|
+
if (url.startsWith('http') && !url.startsWith(window.location.origin)) {
|
|
269
|
+
link.crossOrigin = 'anonymous';
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
document.head.appendChild(link);
|
|
273
|
+
|
|
274
|
+
if (this.options.debug) {
|
|
275
|
+
console.info(`[AnimeCursor] Preloading image: ${url}`);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
if (this.options.debug && imageUrls.size > 0) {
|
|
280
|
+
console.info(`[AnimeCursor] Preloaded ${imageUrls.size} cursor image(s)`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
166
284
|
// ----------------------------
|
|
167
285
|
// 插入光标元素 HTML
|
|
168
286
|
// ----------------------------
|
|
@@ -172,7 +290,7 @@
|
|
|
172
290
|
const cursor = document.createElement('div');
|
|
173
291
|
cursor.id = 'anime-cursor';
|
|
174
292
|
|
|
175
|
-
// 如果debug选项存在,则添加debug
|
|
293
|
+
// 如果debug选项存在,则添加debug元素
|
|
176
294
|
if (this.options.debug) {
|
|
177
295
|
cursor.className = 'cursor-default cursor-debugmode';
|
|
178
296
|
const debuger = document.createElement('div');
|
|
@@ -181,6 +299,14 @@
|
|
|
181
299
|
this.debugEl = debuger;
|
|
182
300
|
}
|
|
183
301
|
else {cursor.className = 'cursor-default';}
|
|
302
|
+
|
|
303
|
+
// 检查是否设置初始化时显示光标
|
|
304
|
+
if (this.options.displayOnLoad) {
|
|
305
|
+
cursor.style.display = 'block';
|
|
306
|
+
} else {
|
|
307
|
+
cursor.style.display = 'none';
|
|
308
|
+
cursor.dataset.animecursorHide = 'true';
|
|
309
|
+
}
|
|
184
310
|
document.body.appendChild(cursor);
|
|
185
311
|
this.cursorEl = cursor;
|
|
186
312
|
}
|
|
@@ -192,13 +318,12 @@
|
|
|
192
318
|
if (this.disabled) return;
|
|
193
319
|
|
|
194
320
|
const style = document.createElement('style');
|
|
321
|
+
style.id = 'animecursor-styles';
|
|
195
322
|
let css = '';
|
|
196
323
|
|
|
197
324
|
/* 通用样式 */
|
|
198
325
|
css += `
|
|
199
|
-
* {
|
|
200
|
-
cursor: none !important;
|
|
201
|
-
}
|
|
326
|
+
* {cursor: none !important;}
|
|
202
327
|
#anime-cursor {
|
|
203
328
|
position: fixed;
|
|
204
329
|
top: 0;
|
|
@@ -319,7 +444,7 @@
|
|
|
319
444
|
});
|
|
320
445
|
}
|
|
321
446
|
if (refresh) {
|
|
322
|
-
console.info('[AnimeCursor] refresh done
|
|
447
|
+
console.info('[AnimeCursor] refresh done');
|
|
323
448
|
}
|
|
324
449
|
}
|
|
325
450
|
|
|
@@ -330,12 +455,18 @@
|
|
|
330
455
|
if (this.disabled) return;
|
|
331
456
|
|
|
332
457
|
this._onMouseMove = (e) => {
|
|
458
|
+
if (this.disabled) return;
|
|
459
|
+
|
|
333
460
|
const x = e.clientX;
|
|
334
461
|
const y = e.clientY;
|
|
335
462
|
|
|
336
463
|
this.cursorEl.style.left = x + 'px';
|
|
337
464
|
this.cursorEl.style.top = y + 'px';
|
|
338
465
|
|
|
466
|
+
if (this.cursorEl.dataset.animecursorHide) {
|
|
467
|
+
this.cursorEl.style.display = 'block';
|
|
468
|
+
}
|
|
469
|
+
|
|
339
470
|
if (this.debugEl) {
|
|
340
471
|
this.debugEl.style.left = x + 'px';
|
|
341
472
|
this.debugEl.style.top = y + 'px';
|
|
@@ -379,8 +510,8 @@
|
|
|
379
510
|
|
|
380
511
|
return 2147483646; // 浏览器安全最大值 2147483647 减一为留给debug覆盖
|
|
381
512
|
}
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
return AnimeCursor;
|
|
385
|
-
|
|
386
|
-
}));
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return AnimeCursor;
|
|
516
|
+
|
|
517
|
+
}));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(e,s){"object"==typeof exports&&"undefined"!=typeof module?module.exports=s():"function"==typeof define&&define.amd?define(s):(e="undefined"!=typeof globalThis?globalThis:e||self).AnimeCursor=s()}(this,function(){"use strict";return class{constructor(
|
|
1
|
+
!function(e,s){"object"==typeof exports&&"undefined"!=typeof module?module.exports=s():"function"==typeof define&&define.amd?define(s):(e="undefined"!=typeof globalThis?globalThis:e||self).AnimeCursor=s()}(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(s={}){return e?(console.warn("[AnimeCursor] AnimeCursor already exists."),e):(this.options={displayOnLoad:!1,enableTouch:!1,debug:!1,...s},this.disabled=!1,this.options.enableTouch||this.isMouseLikeDevice()?(this.cursorEl=null,this.lastCursorType=null,this.debugEl=null,this.styleEl=null,this._onMouseMove=null,this._validateOptions(),this._injectPreload(),this._checkDomLoad(),void(e=this)):(this.disabled=!0,void(this.options.debug&&console.warn("[AnimeCursor] Touch device detected, cursor disabled."))))}isMouseLikeDevice(){if(!this.disabled)return window.matchMedia("(pointer: fine)").matches}refresh(){this.disabled||(this.options.debug&&console.info("[AnimeCursor] starting refresh..."),this._bindElements(!0))}destroy(){if(!this.disabled){this._onMouseMove&&(document.removeEventListener("mousemove",this._onMouseMove),this._onMouseMove=null),this.cursorEl&&(this.cursorEl.remove(),this.cursorEl=null),this.debugEl&&(this.debugEl.remove(),this.debugEl=null),this.styleEl&&(this.styleEl.remove(),this.styleEl=null);for(const e of Object.values(this.options.cursors))e.tags&&Array.isArray(e.tags)&&e.tags.forEach(e=>{document.querySelectorAll(e).forEach(e=>{e.dataset.cursorBound&&(delete e.dataset.cursor,delete e.dataset.cursorBound)})});this.lastCursorType=null,e===this&&(e=null)}}disable(){this.disabled||(this.disabled=!0,this.cursorEl&&(this.cursorEl.style.display="none",this.styleEl.innerHTML=this.styleEl.innerHTML.replace("* {cursor: none !important;}",""),console.log("[AnimeCursor] AnimeCursor disabled!")))}enable(){this.disabled&&(this.disabled=!1,this.cursorEl&&(this.cursorEl.style.display="",this.styleEl.innerHTML+="* {cursor: none; !important;}",console.log("[AnimeCursor] AnimeCursor enabled!")))}_validateOptions(){if(!this.disabled){if(!this.options||!this.options.cursors)throw console.error("[AnimeCursor] missing cursors set up"),new Error("AnimeCursor init failed");this.defaultCursorType=null;for(const[e,s]of Object.entries(this.options.cursors))if(!0===s.default){if(this.defaultCursorType)throw new Error("[AnimeCursor] There can only be one default cursor");this.defaultCursorType=e}for(const[e,s]of Object.entries(this.options.cursors)){if(["size","image"].forEach(t=>{if(void 0===s[t])throw console.error(`[AnimeCursor] cursor "${e}" missing required setting: ${t}`),new Error("AnimeCursor init failed")}),!(s.default||Array.isArray(s.tags)&&0!==s.tags.length))throw console.error(`[AnimeCursor] non-default cursor "${e}" must define at least one tag`),new Error("AnimeCursor init failed");if(s.default&&void 0!==s.tags&&!Array.isArray(s.tags))throw console.error(`[AnimeCursor] default cursor "${e}" 's tags must be an array if provided`),new Error("AnimeCursor init failed");if(void 0!==s.duration&&"number"!=typeof s.duration)throw console.error(`[AnimeCursor] cursor "${e}" 's duration must be a number(seconds)`),new Error("AnimeCursor init failed")}}}_checkDomLoad(){const e=()=>{this._injectHTML(),this._injectCSS(),this._bindElements(),this._bindMouse()};"loading"===document.readyState?document.addEventListener("DOMContentLoaded",e):e()}_injectPreload(){if(this.disabled)return;const e=new Set;for(const s of Object.values(this.options.cursors))s.image&&e.add(s.image);e.forEach(e=>{const s=document.createElement("link");s.rel="preload",s.as="image",s.href=e,e.startsWith("http")&&!e.startsWith(window.location.origin)&&(s.crossOrigin="anonymous"),document.head.appendChild(s),this.options.debug&&console.info(`[AnimeCursor] Preloading image: ${e}`)}),this.options.debug&&e.size>0&&console.info(`[AnimeCursor] Preloaded ${e.size} cursor image(s)`)}_injectHTML(){if(this.disabled)return;const e=document.createElement("div");if(e.id="anime-cursor",this.options.debug){e.className="cursor-default cursor-debugmode";const s=document.createElement("div");s.className="anime-cursor-debug",document.body.appendChild(s),this.debugEl=s}else e.className="cursor-default";this.options.displayOnLoad?e.style.display="block":(e.style.display="none",e.dataset.animecursorHide="true"),document.body.appendChild(e),this.cursorEl=e}_injectCSS(){if(this.disabled)return;const e=document.createElement("style");e.id="animecursor-styles";let s="";s+=`\n * {cursor: none !important;}\n #anime-cursor {\n position: fixed;\n top: 0;\n left: 0;\n pointer-events: none;\n background-repeat: no-repeat;\n transform-origin: 0 0;\n transform-style: preserve-3d;\n z-index: ${this._getMaxZIndex()};\n }\n .cursor-debugmode {\n border: 1px solid green;\n }\n .anime-cursor-debug {\n position: fixed;\n top: 0;\n left: 0;\n width: fit-content;\n height: fit-content;\n padding: 5px;\n font-size: 16px;\n text-wrap: nowrap;\n color: red;\n pointer-events: none;\n overflow: visible;\n z-index: 2147483647;\n }\n .anime-cursor-debug::before {\n position: absolute;\n content: "";\n top: 0;\n left: 0;\n width: 100vw;\n height: 1px;\n background-color: red;\n }\n .anime-cursor-debug::after {\n position: absolute;\n content: "";\n top: 0;\n left: 0;\n width: 1px;\n height: 100vh;\n background-color: red;\n }\n `;for(const[e,o]of Object.entries(this.options.cursors)){const r=`.cursor-${e}`,n=o.size,i=o.frames,l=o.image,a=o.offset,d=o.zIndex,u=o.scale,c=l.toLowerCase().endsWith(".gif");var t;t=o.pixel?"pixelated":"auto",s+=`\n ${r} {\n width: ${n[0]}px;\n height: ${n[1]}px;\n background-image: url("${l}");\n image-rendering: ${t};\n ${u||a?`transform: ${[u&&`scale(${u[0]}, ${u[1]})`,a&&`translate(-${a[0]}px, -${a[1]}px)`].filter(Boolean).join(" ")};`:""}\n \n ${void 0!==d?`z-index:${d};`:""}\n }`;const h=o.duration;if(!c&&i>1&&"number"==typeof h){const t=`animecursor_${e}`;s+=`\n ${r} {\n animation: ${t} steps(${i}) ${h}s infinite ${o.pingpong?"alternate":""};\n }\n\n @keyframes ${t} {\n from { background-position: 0 0; }\n to { background-position: -${n[0]*i}px 0; }\n }\n `}}e.textContent=s,document.head.appendChild(e),this.styleEl=e}_bindElements(e){if(!this.disabled){for(const[e,s]of Object.entries(this.options.cursors))s.tags&&0!==s.tags.length&&s.tags.forEach(s=>{const t=s.toUpperCase();document.querySelectorAll(t).forEach(s=>{s.dataset.cursor||(s.dataset.cursor=e,s.dataset.cursorBound="true")})});e&&console.info("[AnimeCursor] refresh done")}}_bindMouse(){this.disabled||(this._onMouseMove=e=>{if(this.disabled)return;const s=e.clientX,t=e.clientY;this.cursorEl.style.left=s+"px",this.cursorEl.style.top=t+"px",this.cursorEl.dataset.animecursorHide&&(this.cursorEl.style.display="block"),this.debugEl&&(this.debugEl.style.left=s+"px",this.debugEl.style.top=t+"px");let o=null;const r=document.elementFromPoint(s,t);r&&r.dataset&&r.dataset.cursor?o=r.dataset.cursor:this.defaultCursorType&&(o=this.defaultCursorType),o&&(this.debugEl&&(this.debugEl.textContent=`(${s}px , ${t}px) ${o}`),o!==this.lastCursorType&&(this.debugEl?this.cursorEl.className=`cursor-${o} cursor-debugmode`:this.cursorEl.className=`cursor-${o}`,this.lastCursorType=o))},document.addEventListener("mousemove",this._onMouseMove),console.log("[AnimeCursor] AnimeCursor setted up."))}_getMaxZIndex(){if(!this.disabled)return 2147483646}}});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anime-cursor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
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",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"author": "ShuninYu",
|
|
24
24
|
"license": "MIT",
|
|
25
|
+
"homepage": "https://animecursor.js.org",
|
|
25
26
|
"devDependencies": {
|
|
26
27
|
"@rollup/plugin-terser": "^0.4.4",
|
|
27
28
|
"rollup": "^4.56.0"
|