anime-cursor 0.1.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/LICENSE +21 -0
- package/README.md +307 -0
- package/dist/anime-cursor.esm.js +254 -0
- package/dist/anime-cursor.umd.js +262 -0
- package/dist/anime-cursor.umd.min.js +1 -0
- package/package.json +20 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ShuninYu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# AnimeCursor
|
|
2
|
+
|
|
3
|
+
<div align="center">
|
|
4
|
+
<img src="title.gif" width="60%" alt="AnimeCursor"/>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
[[简体中文]](#SChinese)
|
|
8
|
+
|
|
9
|
+
AnimeCursor is a lightweight animated cursor JS library that enables dynamic mouse pointers for websites.
|
|
10
|
+
|
|
11
|
+
AnimeCursor has no dependencies on any frameworks, making it suitable for personal websites, creative portfolios, and experimental UI projects.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## ✨ Features
|
|
16
|
+
|
|
17
|
+
* Supports PNG sprite sheet frame-by-frame animation
|
|
18
|
+
* Supports GIF (animated GIFs, not static GIFs used by native cursors)
|
|
19
|
+
* Customizable cursor types, automatically switched by AnimeCursor
|
|
20
|
+
* CSS-based animation implementation, high performance
|
|
21
|
+
* Prepare PNG sprite sheets in the correct format, and AnimeCursor will automatically generate cursor animations based on your settings
|
|
22
|
+
* Built with native JavaScript, no third-party dependencies
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 📦 Installation
|
|
27
|
+
|
|
28
|
+
### Local storage
|
|
29
|
+
|
|
30
|
+
```html
|
|
31
|
+
<script src="anime-cursor.umd.min.js"></script>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 🚀 Basic Usage
|
|
37
|
+
|
|
38
|
+
Here is an example of how to use AnimeCursor:
|
|
39
|
+
|
|
40
|
+
```html
|
|
41
|
+
<script>
|
|
42
|
+
new AnimeCursor({
|
|
43
|
+
cursors: {
|
|
44
|
+
default: {
|
|
45
|
+
tags: ['body'],
|
|
46
|
+
size: [64,64],
|
|
47
|
+
image: './cursor_default.png',
|
|
48
|
+
frames: 1
|
|
49
|
+
},
|
|
50
|
+
pointer: {
|
|
51
|
+
tags: ['a', 'button'],
|
|
52
|
+
size: [64,64],
|
|
53
|
+
image: './cursor_pointer.png',
|
|
54
|
+
frames: 3,
|
|
55
|
+
duration: 0.3,
|
|
56
|
+
pingpong: true,
|
|
57
|
+
offset: [10, 4]
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
</script>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## ⚙️ Configuration Options
|
|
67
|
+
|
|
68
|
+
### `cursors` (Required)
|
|
69
|
+
|
|
70
|
+
An object used to define all cursor types.
|
|
71
|
+
|
|
72
|
+
Each key represents a cursor type (the name can be freely defined).
|
|
73
|
+
|
|
74
|
+
#### Cursor Configuration Parameters
|
|
75
|
+
|
|
76
|
+
For each key, the following parameters can be set. Missing required parameters will cause an error.
|
|
77
|
+
|
|
78
|
+
| Parameter | Type | Required | Description |
|
|
79
|
+
|-|-|-|-|
|
|
80
|
+
| `tags` | `string[]` | ✅ | HTML tags that should use this cursor |
|
|
81
|
+
| `size` | `number` | ✅ | Cursor dimensions [width, height] in pixels |
|
|
82
|
+
| `offset` | `[number, number]` | | Cursor alignment offset [ x , y ] |
|
|
83
|
+
| `image` | `string` | ✅ | Image path (PNG / GIF) |
|
|
84
|
+
| `frames` | `number` | ✅ | Number of frames for PNG sprites (set to `1` for static images) |
|
|
85
|
+
| `duration` | `number` | | Animation loop duration (seconds) ⚠️ PNG sprite animations *only* work when this parameter is set |
|
|
86
|
+
| `pingpong` | `boolean` | | Enable ping-pong (back-and-forth) looping (for PNG sprite animations only) |
|
|
87
|
+
| `scale` | `[number, number]` | | Cursor scale factor based on `size` [ x , y ] ⚠️ Only supported for GIF cursors. Do not set for PNG sprite cursors, as it will break the animation. |
|
|
88
|
+
| `pixel` | `boolean` | | Enable pixelated rendering |
|
|
89
|
+
| `zIndex` | `number` | | Cursor z-index layer (not recommended to modify) |
|
|
90
|
+
|
|
91
|
+
### `debug` (Optional)
|
|
92
|
+
|
|
93
|
+
Enables debugging overlay.
|
|
94
|
+
|
|
95
|
+
The debugging overlay shows the real mouse position and the current cursor type, helping to correct alignment offsets.
|
|
96
|
+
|
|
97
|
+
### `enableTouch` (Optional)
|
|
98
|
+
|
|
99
|
+
Enables animated cursors on mobile touch devices.
|
|
100
|
+
|
|
101
|
+
AnimeCursor automatically detects mobile touch devices (e.g., phones, tablets) and disables animated cursors on them by default.
|
|
102
|
+
If you want animated cursors to be displayed on these devices, add `enableTouch: true`.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 📝 Notes
|
|
107
|
+
|
|
108
|
+
### 📁 Files
|
|
109
|
+
|
|
110
|
+
* For any PNG sprite animation cursor, its PNG sprite sheet should be arranged in a single horizontal row. AnimeCursor will automatically generate the PNG sprite animation.
|
|
111
|
+
For example, if you set the `size` (width, height) for a `pointer` cursor to `[64px , 64px]` and `frames` to `3`, the prepared sprite sheet dimensions (width, height) should be: `[192px , 64px]`.
|
|
112
|
+
|
|
113
|
+
* For pixel art with a large number of frames, you can use the original image (whether GIF or PNG-sprite-sheet) to save storage space or bandwidth. Then, use the `scale` parameter in the configuration to resize the cursor and set `pixel` to `true` to avoid blurry scaling.
|
|
114
|
+
|
|
115
|
+
### 🧩 Tagging Mechanism
|
|
116
|
+
|
|
117
|
+
AnimeCursor automatically adds `data-cursor` attributes to page elements based on the configuration:
|
|
118
|
+
|
|
119
|
+
```html
|
|
120
|
+
<!-- Using the example configuration from the Basic Usage section -->
|
|
121
|
+
<button data-cursor="pointer"></button>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`data-cursor` will NOT be added in the following cases:
|
|
125
|
+
|
|
126
|
+
* The element's `tagName` is not included in the `tags` configuration of any custom cursor type.
|
|
127
|
+
* The element already had a `data-cursor` attribute *before* AnimeCursor was initialized.
|
|
128
|
+
|
|
129
|
+
Therefore, to assign a specific animated cursor to a particular element, simply add the corresponding `data-cursor` attribute to that element. AnimeCursor will not overwrite pre-existing `data-cursor` tags.
|
|
130
|
+
|
|
131
|
+
### 🎞️ Animation Rules
|
|
132
|
+
|
|
133
|
+
#### PNG Cursors
|
|
134
|
+
|
|
135
|
+
Animation is generated **only when all of the following conditions are met**:
|
|
136
|
+
|
|
137
|
+
* The image is a PNG
|
|
138
|
+
* `frames > 1`
|
|
139
|
+
* `duration` is set
|
|
140
|
+
|
|
141
|
+
If `duration` is not set, the cursor will be treated as a **static cursor**, even if `frames > 1`.
|
|
142
|
+
|
|
143
|
+
#### GIF Cursors
|
|
144
|
+
|
|
145
|
+
* GIFs do not use `frames`, `duration`, or `pingpong`
|
|
146
|
+
* Animation is controlled by the GIF file itself
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## ❌ Error Handling
|
|
151
|
+
|
|
152
|
+
* Missing required configuration parameters will directly terminate initialization.
|
|
153
|
+
* Invalid optional configuration will also throw errors.
|
|
154
|
+
* All errors are prefixed with `[AnimeCursor]` when logged to the console.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
<h1 id="SChinese">AnimeCursor</h1>
|
|
158
|
+
|
|
159
|
+
<div align="center">
|
|
160
|
+
<img src="title.gif" width="60%" alt="AnimeCursor"/>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
AnimeCursor 是一个轻量级动画光标JS,能够让网站拥有动态的鼠标指针。
|
|
164
|
+
|
|
165
|
+
AnimeCursor 无需依赖任何框架,适合个人网站、创意作品集以及实验性 UI 项目。
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## ✨ 特性
|
|
170
|
+
|
|
171
|
+
* 支持 PNG 精灵图逐帧动画
|
|
172
|
+
* 支持 GIF(动态gif,而不是原生光标的静止gif)
|
|
173
|
+
* 自定义光标类型,由 AnimeCursor 自动切换
|
|
174
|
+
* 基于 CSS 的动画实现,高性能
|
|
175
|
+
* 按照格式准备好 PNG 精灵图表,AnimeCursor 将基于你的设置自动生成光标动画
|
|
176
|
+
* 基于原生JavaScript,无任何第三方依赖
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## 📦 引入方式
|
|
181
|
+
|
|
182
|
+
### 本地部署
|
|
183
|
+
|
|
184
|
+
```html
|
|
185
|
+
<script src="anime-cursor.umd.min.js"></script>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## 🚀 基础用法
|
|
191
|
+
|
|
192
|
+
下面是一个 AnimeCursor 使用示例:
|
|
193
|
+
```html
|
|
194
|
+
<script>
|
|
195
|
+
new AnimeCursor({
|
|
196
|
+
cursors: {
|
|
197
|
+
default: {
|
|
198
|
+
tags: ['body'],
|
|
199
|
+
size: [64,64],
|
|
200
|
+
image: './cursor_default.png',
|
|
201
|
+
frames: 1
|
|
202
|
+
},
|
|
203
|
+
pointer: {
|
|
204
|
+
tags: ['a', 'button'],
|
|
205
|
+
size: [64,64],
|
|
206
|
+
image: './cursor_pointer.png',
|
|
207
|
+
frames: 3,
|
|
208
|
+
duration: 0.3,
|
|
209
|
+
pingpong: true,
|
|
210
|
+
offset: [10, 4]
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
</script>
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## ⚙️ 配置项说明
|
|
220
|
+
|
|
221
|
+
### `cursors`(必填)
|
|
222
|
+
|
|
223
|
+
用于定义所有光标类型的对象。
|
|
224
|
+
|
|
225
|
+
每一个 key 代表一种光标类型(名称可以自由定义)。
|
|
226
|
+
|
|
227
|
+
#### 光标配置参数
|
|
228
|
+
|
|
229
|
+
对于每个key,有以下参数可以设置,其中必填项如果缺失则会报错。
|
|
230
|
+
|
|
231
|
+
|参数|类型|必填|说明|
|
|
232
|
+
|-|-|-|-|
|
|
233
|
+
|`tags`|`string[]`|✅|使用该光标的 HTML 标签|
|
|
234
|
+
|`size`|`number`|✅|光标尺寸(宽高,像素)|
|
|
235
|
+
|`offset`|`[number, number]`||光标对齐偏移量 [ x , y ]|
|
|
236
|
+
|`image`|`string`|✅|图片路径(PNG / GIF)|
|
|
237
|
+
|`frames`|`number`|✅|PNG 帧数(静态图片请设置为 `1` )|
|
|
238
|
+
|`duration`|`number`||动画循环时长(秒)⚠️PNG精灵图动画只有设置该参数才会生效|
|
|
239
|
+
|`pingpong`|`boolean`||是否启用乒乓循环(仅PNG精灵图动画)|
|
|
240
|
+
|`scale`|`[number, number]`||基于size的光标缩放 [ x , y ]⚠️仅支持GIF光标,PNG精灵图动画光标请勿设置,否则会使动画失效|
|
|
241
|
+
|`pixel`|`boolean`||是否启用像素化渲染|
|
|
242
|
+
|`zIndex`|`number`||光标层级(不建议添加此项设置)|
|
|
243
|
+
|
|
244
|
+
### `debug`(选填)
|
|
245
|
+
|
|
246
|
+
用于启用debug覆盖。
|
|
247
|
+
|
|
248
|
+
debug覆盖会显示鼠标的真实位置以及当前光标类型,以帮助纠正对齐偏移量。
|
|
249
|
+
|
|
250
|
+
### `enableTouch`(选填)
|
|
251
|
+
|
|
252
|
+
用于启用移动触屏设备上的动画光标。
|
|
253
|
+
|
|
254
|
+
AnimeCursor 会自动识别移动触屏设备(比如手机、平板电脑)并默认屏蔽这些设备上的动画光标。
|
|
255
|
+
如果你想在这些设备上显示动画光标,可以添加 `enableTouch: true` 。
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## 📝 注意事项
|
|
260
|
+
|
|
261
|
+
### 📁 文件
|
|
262
|
+
|
|
263
|
+
* 对于任何 PNG 精灵图动画光标,它的 PNG 精灵图表都应该为单行横向布局,AnimeCursor 会自动生成 PNG 精灵图动画。
|
|
264
|
+
例如,你为 `pointer` 光标设置的`size`(长,宽)为 `[64px , 64px]` ,帧数为 `3` ,那么你准备的精灵图表尺寸(长,宽)应该为: `[192px , 64px]` 。
|
|
265
|
+
|
|
266
|
+
* 对于帧数特别多的像素图,你可以使用原尺寸图片(无论是gif还是png精灵图表)以节省存储空间或带宽,并在参数中设置 `scale` 来缩放光标,并将 `pixel` 设置为 `true` 来避免缩放模糊。
|
|
267
|
+
|
|
268
|
+
### 🧩 标记机制
|
|
269
|
+
|
|
270
|
+
AnimeCursor 会根据配置自动为页面元素添加 `data-cursor`:
|
|
271
|
+
|
|
272
|
+
```html
|
|
273
|
+
<!-- 以上面的示例用法配置为例 -->
|
|
274
|
+
<button data-cursor="pointer"></button>
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
只有在以下情况不会添加 `data-cursor`:
|
|
278
|
+
|
|
279
|
+
* 元素的 `tagName` 不在任何自定义光标类型的配置中。
|
|
280
|
+
* 元素在 AnimeCursor 配置之前就已经存在 `data-cursor` 。
|
|
281
|
+
|
|
282
|
+
因此,如果想要给某个特定元素指定一种动画指针,只需要给该元素添加指定的 `data-cursor` 即可,AnimeCursor 不会覆盖事先设定好的标记。
|
|
283
|
+
|
|
284
|
+
### 🎞️ 动画判定
|
|
285
|
+
|
|
286
|
+
#### PNG 光标
|
|
287
|
+
|
|
288
|
+
只有在 **同时满足以下条件** 时,才会生成动画:
|
|
289
|
+
|
|
290
|
+
* 图片为 PNG
|
|
291
|
+
* `frames > 1`
|
|
292
|
+
* 设置了 `duration`
|
|
293
|
+
|
|
294
|
+
如果未设置 `duration`,即使帧数大于 1,也会被视为**静态光标**。
|
|
295
|
+
|
|
296
|
+
#### GIF 光标
|
|
297
|
+
|
|
298
|
+
* GIF 不使用 `frames`、`duration` 或 `pingpong`
|
|
299
|
+
* 动画由 GIF 自身控制
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## ❌ 错误处理
|
|
304
|
+
|
|
305
|
+
* 缺少必填配置会直接终止初始化
|
|
306
|
+
* 非法的可选配置也会报错
|
|
307
|
+
* 所有错误均以 `[AnimeCursor]` 前缀输出到console
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
class AnimeCursor {
|
|
2
|
+
|
|
3
|
+
constructor(options = {}) {
|
|
4
|
+
this.options = {
|
|
5
|
+
enableTouch: false,
|
|
6
|
+
debug: false,
|
|
7
|
+
...options
|
|
8
|
+
};
|
|
9
|
+
this.disabled = false;
|
|
10
|
+
|
|
11
|
+
if (!this.options.enableTouch && !this.isMouseLikeDevice()) {
|
|
12
|
+
this.disabled = true;
|
|
13
|
+
|
|
14
|
+
if (this.options.debug) {
|
|
15
|
+
console.warn('[AnimeCursor] Touch device detected, cursor disabled.');
|
|
16
|
+
}
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
this.cursorEl = null;
|
|
21
|
+
this.lastCursorType = null;
|
|
22
|
+
this.debugEl = null;
|
|
23
|
+
|
|
24
|
+
this._validateOptions();
|
|
25
|
+
this._injectHTML();
|
|
26
|
+
this._injectCSS();
|
|
27
|
+
this._bindElements();
|
|
28
|
+
this._bindMouse();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
isMouseLikeDevice() {
|
|
32
|
+
return window.matchMedia('(pointer: fine)').matches;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
destroy() {
|
|
36
|
+
if (this.disabled) return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ----------------------------
|
|
40
|
+
// 配置校验(必填项)
|
|
41
|
+
// ----------------------------
|
|
42
|
+
_validateOptions() {
|
|
43
|
+
if (!this.options || !this.options.cursors) {
|
|
44
|
+
console.error('[AnimeCursor] 缺少 cursors 配置');
|
|
45
|
+
throw new Error('AnimeCursor init failed');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (const [name, cfg] of Object.entries(this.options.cursors)) {
|
|
49
|
+
const required = ['tags', 'size', 'image', 'frames'];
|
|
50
|
+
required.forEach(key => {
|
|
51
|
+
if (cfg[key] === undefined) {
|
|
52
|
+
console.error(`[AnimeCursor] 光标 "${name}" 缺少必填项:${key}`);
|
|
53
|
+
throw new Error('AnimeCursor init failed');
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
if (!Array.isArray(cfg.tags)) {
|
|
58
|
+
console.error(`[AnimeCursor] 光标 "${name}" 的 tags 必须是数组`);
|
|
59
|
+
throw new Error('AnimeCursor init failed');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (cfg.duration !== undefined && typeof cfg.duration !== 'number') {
|
|
63
|
+
console.error(`[AnimeCursor] 光标 "${name}" 的 duration 必须是数字(秒)`);
|
|
64
|
+
throw new Error('AnimeCursor init failed');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ----------------------------
|
|
70
|
+
// 插入光标元素 HTML
|
|
71
|
+
// ----------------------------
|
|
72
|
+
_injectHTML() {
|
|
73
|
+
const cursor = document.createElement('div');
|
|
74
|
+
cursor.id = 'anime-cursor';
|
|
75
|
+
|
|
76
|
+
// 如果debug选项存在,则添加debug子元素
|
|
77
|
+
if (this.options.debug) {
|
|
78
|
+
cursor.className = 'cursor-default cursor-debugmode';
|
|
79
|
+
const debuger = document.createElement('div');
|
|
80
|
+
debuger.className = 'anime-cursor-debug';
|
|
81
|
+
document.body.appendChild(debuger);
|
|
82
|
+
this.debugEl = debuger;
|
|
83
|
+
}
|
|
84
|
+
else {cursor.className = 'cursor-default';}
|
|
85
|
+
document.body.appendChild(cursor);
|
|
86
|
+
this.cursorEl = cursor;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ----------------------------
|
|
90
|
+
// 插入基础 CSS
|
|
91
|
+
// ----------------------------
|
|
92
|
+
_injectCSS() {
|
|
93
|
+
const style = document.createElement('style');
|
|
94
|
+
let css = '';
|
|
95
|
+
|
|
96
|
+
/* 通用样式和debug样式 */
|
|
97
|
+
css += `
|
|
98
|
+
* {
|
|
99
|
+
cursor: none !important;
|
|
100
|
+
}
|
|
101
|
+
#anime-cursor {
|
|
102
|
+
position: fixed;
|
|
103
|
+
top: 0;
|
|
104
|
+
left: 0;
|
|
105
|
+
pointer-events: none;
|
|
106
|
+
background-repeat: no-repeat;
|
|
107
|
+
transform-origin: 0 0;
|
|
108
|
+
transform-style: preserve-3d;
|
|
109
|
+
z-index: ${this._getMaxZIndex()};
|
|
110
|
+
}
|
|
111
|
+
.cursor-debugmode {
|
|
112
|
+
border: 1px solid green;
|
|
113
|
+
}
|
|
114
|
+
.anime-cursor-debug {
|
|
115
|
+
position: fixed;
|
|
116
|
+
top: 0;
|
|
117
|
+
left: 0;
|
|
118
|
+
width: fit-content;
|
|
119
|
+
height: fit-content;
|
|
120
|
+
padding: 5px;
|
|
121
|
+
font-size: 16px;
|
|
122
|
+
text-wrap: nowrap;
|
|
123
|
+
color: red;
|
|
124
|
+
pointer-events: none;
|
|
125
|
+
overflow: visible;
|
|
126
|
+
z-index: 2147483647;
|
|
127
|
+
}
|
|
128
|
+
.anime-cursor-debug::before {
|
|
129
|
+
position: absolute;
|
|
130
|
+
content: "";
|
|
131
|
+
top: 0;
|
|
132
|
+
left: 0;
|
|
133
|
+
width: 100vw;
|
|
134
|
+
height: 1px;
|
|
135
|
+
background-color: red;
|
|
136
|
+
}
|
|
137
|
+
.anime-cursor-debug::after {
|
|
138
|
+
position: absolute;
|
|
139
|
+
content: "";
|
|
140
|
+
top: 0;
|
|
141
|
+
left: 0;
|
|
142
|
+
width: 1px;
|
|
143
|
+
height: 100vh;
|
|
144
|
+
background-color: red;
|
|
145
|
+
}
|
|
146
|
+
`;
|
|
147
|
+
|
|
148
|
+
/* 每种光标以及debug生成 CSS */
|
|
149
|
+
for (const [type, cfg] of Object.entries(this.options.cursors)) {
|
|
150
|
+
const className = `.cursor-${type}`;
|
|
151
|
+
const size = cfg.size;
|
|
152
|
+
const frames = cfg.frames;
|
|
153
|
+
const image = cfg.image;
|
|
154
|
+
const offset = cfg.offset;
|
|
155
|
+
const zIndex = cfg.zIndex;
|
|
156
|
+
const scale = cfg.scale;
|
|
157
|
+
const isGif = image.toLowerCase().endsWith('.gif');
|
|
158
|
+
var pixel;
|
|
159
|
+
if (cfg.pixel) {pixel = 'pixelated';}
|
|
160
|
+
else {pixel = 'auto';}
|
|
161
|
+
|
|
162
|
+
css += `
|
|
163
|
+
${className} {
|
|
164
|
+
width: ${size[0]}px;
|
|
165
|
+
height: ${size[1]}px;
|
|
166
|
+
background-image: url("${image}");
|
|
167
|
+
image-rendering: ${pixel};
|
|
168
|
+
${(scale || offset) ? `transform: ${[scale && `scale(${scale[0]}, ${scale[1]})`, offset && `translate(-${offset[0]}px, -${offset[1]}px)`].filter(Boolean).join(' ')};` : ''}
|
|
169
|
+
|
|
170
|
+
${zIndex !== undefined ? `z-index:${zIndex};` : ''}
|
|
171
|
+
}`;
|
|
172
|
+
|
|
173
|
+
/* PNG 精灵图动画 */
|
|
174
|
+
const duration = cfg.duration;
|
|
175
|
+
const hasAnimation =
|
|
176
|
+
!isGif &&
|
|
177
|
+
frames > 1 &&
|
|
178
|
+
typeof duration === 'number';
|
|
179
|
+
|
|
180
|
+
if (hasAnimation) {
|
|
181
|
+
const animName = `animecursor_${type}`;
|
|
182
|
+
|
|
183
|
+
css += `
|
|
184
|
+
${className} {
|
|
185
|
+
animation: ${animName} steps(${frames}) ${duration}s infinite ${cfg.pingpong ? 'alternate' : ''};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
@keyframes ${animName} {
|
|
189
|
+
from { background-position: 0 0; }
|
|
190
|
+
to { background-position: -${size[0] * frames}px 0; }
|
|
191
|
+
}
|
|
192
|
+
`;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
style.textContent = css;
|
|
197
|
+
document.head.appendChild(style);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ----------------------------
|
|
201
|
+
// 给元素自动添加 data-cursor
|
|
202
|
+
// ----------------------------
|
|
203
|
+
_bindElements() {
|
|
204
|
+
for (const [type, cfg] of Object.entries(this.options.cursors)) {
|
|
205
|
+
cfg.tags.forEach(tag => {
|
|
206
|
+
const tagName = tag.toUpperCase();
|
|
207
|
+
document.querySelectorAll(tagName).forEach(el => {
|
|
208
|
+
if (!el.dataset.cursor) {
|
|
209
|
+
el.dataset.cursor = type;
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ----------------------------
|
|
217
|
+
// 鼠标跟随 & 光标切换
|
|
218
|
+
// ----------------------------
|
|
219
|
+
_bindMouse() {
|
|
220
|
+
document.addEventListener('mousemove', e => {
|
|
221
|
+
const x = e.clientX;
|
|
222
|
+
const y = e.clientY;
|
|
223
|
+
|
|
224
|
+
this.cursorEl.style.left = x + 'px';
|
|
225
|
+
this.cursorEl.style.top = y + 'px';
|
|
226
|
+
|
|
227
|
+
if (this.debugEl) {
|
|
228
|
+
this.debugEl.style.left = x + 'px';
|
|
229
|
+
this.debugEl.style.top = y + 'px';
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const target = document.elementFromPoint(x, y);
|
|
233
|
+
if (!target) return;
|
|
234
|
+
|
|
235
|
+
const cursorType = target.dataset.cursor || 'default';
|
|
236
|
+
if (this.debugEl) {this.debugEl.textContent = `(${x}px , ${y}px) ${cursorType}`;}
|
|
237
|
+
|
|
238
|
+
if (cursorType !== this.lastCursorType) {
|
|
239
|
+
if (this.debugEl) {this.cursorEl.className = `cursor-${cursorType}` + ' cursor-debugmode';}
|
|
240
|
+
else {this.cursorEl.className = `cursor-${cursorType}`;}
|
|
241
|
+
this.lastCursorType = cursorType;
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ----------------------------
|
|
247
|
+
// 获取可用最大 z-index
|
|
248
|
+
// ----------------------------
|
|
249
|
+
_getMaxZIndex() {
|
|
250
|
+
return 2147483646; // 浏览器安全最大值 2147483647 减一为留给debug覆盖
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export { AnimeCursor as default };
|
|
@@ -0,0 +1,262 @@
|
|
|
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
|
+
class AnimeCursor {
|
|
8
|
+
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.options = {
|
|
11
|
+
enableTouch: false,
|
|
12
|
+
debug: false,
|
|
13
|
+
...options
|
|
14
|
+
};
|
|
15
|
+
this.disabled = false;
|
|
16
|
+
|
|
17
|
+
if (!this.options.enableTouch && !this.isMouseLikeDevice()) {
|
|
18
|
+
this.disabled = true;
|
|
19
|
+
|
|
20
|
+
if (this.options.debug) {
|
|
21
|
+
console.warn('[AnimeCursor] Touch device detected, cursor disabled.');
|
|
22
|
+
}
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
this.cursorEl = null;
|
|
27
|
+
this.lastCursorType = null;
|
|
28
|
+
this.debugEl = null;
|
|
29
|
+
|
|
30
|
+
this._validateOptions();
|
|
31
|
+
this._injectHTML();
|
|
32
|
+
this._injectCSS();
|
|
33
|
+
this._bindElements();
|
|
34
|
+
this._bindMouse();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
isMouseLikeDevice() {
|
|
38
|
+
return window.matchMedia('(pointer: fine)').matches;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
destroy() {
|
|
42
|
+
if (this.disabled) return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ----------------------------
|
|
46
|
+
// 配置校验(必填项)
|
|
47
|
+
// ----------------------------
|
|
48
|
+
_validateOptions() {
|
|
49
|
+
if (!this.options || !this.options.cursors) {
|
|
50
|
+
console.error('[AnimeCursor] 缺少 cursors 配置');
|
|
51
|
+
throw new Error('AnimeCursor init failed');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (const [name, cfg] of Object.entries(this.options.cursors)) {
|
|
55
|
+
const required = ['tags', 'size', 'image', 'frames'];
|
|
56
|
+
required.forEach(key => {
|
|
57
|
+
if (cfg[key] === undefined) {
|
|
58
|
+
console.error(`[AnimeCursor] 光标 "${name}" 缺少必填项:${key}`);
|
|
59
|
+
throw new Error('AnimeCursor init failed');
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (!Array.isArray(cfg.tags)) {
|
|
64
|
+
console.error(`[AnimeCursor] 光标 "${name}" 的 tags 必须是数组`);
|
|
65
|
+
throw new Error('AnimeCursor init failed');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (cfg.duration !== undefined && typeof cfg.duration !== 'number') {
|
|
69
|
+
console.error(`[AnimeCursor] 光标 "${name}" 的 duration 必须是数字(秒)`);
|
|
70
|
+
throw new Error('AnimeCursor init failed');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ----------------------------
|
|
76
|
+
// 插入光标元素 HTML
|
|
77
|
+
// ----------------------------
|
|
78
|
+
_injectHTML() {
|
|
79
|
+
const cursor = document.createElement('div');
|
|
80
|
+
cursor.id = 'anime-cursor';
|
|
81
|
+
|
|
82
|
+
// 如果debug选项存在,则添加debug子元素
|
|
83
|
+
if (this.options.debug) {
|
|
84
|
+
cursor.className = 'cursor-default cursor-debugmode';
|
|
85
|
+
const debuger = document.createElement('div');
|
|
86
|
+
debuger.className = 'anime-cursor-debug';
|
|
87
|
+
document.body.appendChild(debuger);
|
|
88
|
+
this.debugEl = debuger;
|
|
89
|
+
}
|
|
90
|
+
else {cursor.className = 'cursor-default';}
|
|
91
|
+
document.body.appendChild(cursor);
|
|
92
|
+
this.cursorEl = cursor;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ----------------------------
|
|
96
|
+
// 插入基础 CSS
|
|
97
|
+
// ----------------------------
|
|
98
|
+
_injectCSS() {
|
|
99
|
+
const style = document.createElement('style');
|
|
100
|
+
let css = '';
|
|
101
|
+
|
|
102
|
+
/* 通用样式和debug样式 */
|
|
103
|
+
css += `
|
|
104
|
+
* {
|
|
105
|
+
cursor: none !important;
|
|
106
|
+
}
|
|
107
|
+
#anime-cursor {
|
|
108
|
+
position: fixed;
|
|
109
|
+
top: 0;
|
|
110
|
+
left: 0;
|
|
111
|
+
pointer-events: none;
|
|
112
|
+
background-repeat: no-repeat;
|
|
113
|
+
transform-origin: 0 0;
|
|
114
|
+
transform-style: preserve-3d;
|
|
115
|
+
z-index: ${this._getMaxZIndex()};
|
|
116
|
+
}
|
|
117
|
+
.cursor-debugmode {
|
|
118
|
+
border: 1px solid green;
|
|
119
|
+
}
|
|
120
|
+
.anime-cursor-debug {
|
|
121
|
+
position: fixed;
|
|
122
|
+
top: 0;
|
|
123
|
+
left: 0;
|
|
124
|
+
width: fit-content;
|
|
125
|
+
height: fit-content;
|
|
126
|
+
padding: 5px;
|
|
127
|
+
font-size: 16px;
|
|
128
|
+
text-wrap: nowrap;
|
|
129
|
+
color: red;
|
|
130
|
+
pointer-events: none;
|
|
131
|
+
overflow: visible;
|
|
132
|
+
z-index: 2147483647;
|
|
133
|
+
}
|
|
134
|
+
.anime-cursor-debug::before {
|
|
135
|
+
position: absolute;
|
|
136
|
+
content: "";
|
|
137
|
+
top: 0;
|
|
138
|
+
left: 0;
|
|
139
|
+
width: 100vw;
|
|
140
|
+
height: 1px;
|
|
141
|
+
background-color: red;
|
|
142
|
+
}
|
|
143
|
+
.anime-cursor-debug::after {
|
|
144
|
+
position: absolute;
|
|
145
|
+
content: "";
|
|
146
|
+
top: 0;
|
|
147
|
+
left: 0;
|
|
148
|
+
width: 1px;
|
|
149
|
+
height: 100vh;
|
|
150
|
+
background-color: red;
|
|
151
|
+
}
|
|
152
|
+
`;
|
|
153
|
+
|
|
154
|
+
/* 每种光标以及debug生成 CSS */
|
|
155
|
+
for (const [type, cfg] of Object.entries(this.options.cursors)) {
|
|
156
|
+
const className = `.cursor-${type}`;
|
|
157
|
+
const size = cfg.size;
|
|
158
|
+
const frames = cfg.frames;
|
|
159
|
+
const image = cfg.image;
|
|
160
|
+
const offset = cfg.offset;
|
|
161
|
+
const zIndex = cfg.zIndex;
|
|
162
|
+
const scale = cfg.scale;
|
|
163
|
+
const isGif = image.toLowerCase().endsWith('.gif');
|
|
164
|
+
var pixel;
|
|
165
|
+
if (cfg.pixel) {pixel = 'pixelated';}
|
|
166
|
+
else {pixel = 'auto';}
|
|
167
|
+
|
|
168
|
+
css += `
|
|
169
|
+
${className} {
|
|
170
|
+
width: ${size[0]}px;
|
|
171
|
+
height: ${size[1]}px;
|
|
172
|
+
background-image: url("${image}");
|
|
173
|
+
image-rendering: ${pixel};
|
|
174
|
+
${(scale || offset) ? `transform: ${[scale && `scale(${scale[0]}, ${scale[1]})`, offset && `translate(-${offset[0]}px, -${offset[1]}px)`].filter(Boolean).join(' ')};` : ''}
|
|
175
|
+
|
|
176
|
+
${zIndex !== undefined ? `z-index:${zIndex};` : ''}
|
|
177
|
+
}`;
|
|
178
|
+
|
|
179
|
+
/* PNG 精灵图动画 */
|
|
180
|
+
const duration = cfg.duration;
|
|
181
|
+
const hasAnimation =
|
|
182
|
+
!isGif &&
|
|
183
|
+
frames > 1 &&
|
|
184
|
+
typeof duration === 'number';
|
|
185
|
+
|
|
186
|
+
if (hasAnimation) {
|
|
187
|
+
const animName = `animecursor_${type}`;
|
|
188
|
+
|
|
189
|
+
css += `
|
|
190
|
+
${className} {
|
|
191
|
+
animation: ${animName} steps(${frames}) ${duration}s infinite ${cfg.pingpong ? 'alternate' : ''};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
@keyframes ${animName} {
|
|
195
|
+
from { background-position: 0 0; }
|
|
196
|
+
to { background-position: -${size[0] * frames}px 0; }
|
|
197
|
+
}
|
|
198
|
+
`;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
style.textContent = css;
|
|
203
|
+
document.head.appendChild(style);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ----------------------------
|
|
207
|
+
// 给元素自动添加 data-cursor
|
|
208
|
+
// ----------------------------
|
|
209
|
+
_bindElements() {
|
|
210
|
+
for (const [type, cfg] of Object.entries(this.options.cursors)) {
|
|
211
|
+
cfg.tags.forEach(tag => {
|
|
212
|
+
const tagName = tag.toUpperCase();
|
|
213
|
+
document.querySelectorAll(tagName).forEach(el => {
|
|
214
|
+
if (!el.dataset.cursor) {
|
|
215
|
+
el.dataset.cursor = type;
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ----------------------------
|
|
223
|
+
// 鼠标跟随 & 光标切换
|
|
224
|
+
// ----------------------------
|
|
225
|
+
_bindMouse() {
|
|
226
|
+
document.addEventListener('mousemove', e => {
|
|
227
|
+
const x = e.clientX;
|
|
228
|
+
const y = e.clientY;
|
|
229
|
+
|
|
230
|
+
this.cursorEl.style.left = x + 'px';
|
|
231
|
+
this.cursorEl.style.top = y + 'px';
|
|
232
|
+
|
|
233
|
+
if (this.debugEl) {
|
|
234
|
+
this.debugEl.style.left = x + 'px';
|
|
235
|
+
this.debugEl.style.top = y + 'px';
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const target = document.elementFromPoint(x, y);
|
|
239
|
+
if (!target) return;
|
|
240
|
+
|
|
241
|
+
const cursorType = target.dataset.cursor || 'default';
|
|
242
|
+
if (this.debugEl) {this.debugEl.textContent = `(${x}px , ${y}px) ${cursorType}`;}
|
|
243
|
+
|
|
244
|
+
if (cursorType !== this.lastCursorType) {
|
|
245
|
+
if (this.debugEl) {this.cursorEl.className = `cursor-${cursorType}` + ' cursor-debugmode';}
|
|
246
|
+
else {this.cursorEl.className = `cursor-${cursorType}`;}
|
|
247
|
+
this.lastCursorType = cursorType;
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ----------------------------
|
|
253
|
+
// 获取可用最大 z-index
|
|
254
|
+
// ----------------------------
|
|
255
|
+
_getMaxZIndex() {
|
|
256
|
+
return 2147483646; // 浏览器安全最大值 2147483647 减一为留给debug覆盖
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return AnimeCursor;
|
|
261
|
+
|
|
262
|
+
}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(e="undefined"!=typeof globalThis?globalThis:e||self).AnimeCursor=n()}(this,function(){"use strict";return class{constructor(e={}){if(this.options={enableTouch:!1,debug:!1,...e},this.disabled=!1,!this.options.enableTouch&&!this.isMouseLikeDevice())return this.disabled=!0,void(this.options.debug&&console.warn("[AnimeCursor] Touch device detected, cursor disabled."));this.cursorEl=null,this.lastCursorType=null,this.debugEl=null,this._validateOptions(),this._injectHTML(),this._injectCSS(),this._bindElements(),this._bindMouse()}isMouseLikeDevice(){return window.matchMedia("(pointer: fine)").matches}destroy(){this.disabled}_validateOptions(){if(!this.options||!this.options.cursors)throw console.error("[AnimeCursor] 缺少 cursors 配置"),new Error("AnimeCursor init failed");for(const[e,n]of Object.entries(this.options.cursors)){if(["tags","size","image","frames"].forEach(t=>{if(void 0===n[t])throw console.error(`[AnimeCursor] 光标 "${e}" 缺少必填项:${t}`),new Error("AnimeCursor init failed")}),!Array.isArray(n.tags))throw console.error(`[AnimeCursor] 光标 "${e}" 的 tags 必须是数组`),new Error("AnimeCursor init failed");if(void 0!==n.duration&&"number"!=typeof n.duration)throw console.error(`[AnimeCursor] 光标 "${e}" 的 duration 必须是数字(秒)`),new Error("AnimeCursor init failed")}}_injectHTML(){const e=document.createElement("div");if(e.id="anime-cursor",this.options.debug){e.className="cursor-default cursor-debugmode";const n=document.createElement("div");n.className="anime-cursor-debug",document.body.appendChild(n),this.debugEl=n}else e.className="cursor-default";document.body.appendChild(e),this.cursorEl=e}_injectCSS(){const e=document.createElement("style");let n="";n+=`\n* {\ncursor: none !important;\n}\n#anime-cursor {\nposition: fixed;\ntop: 0;\nleft: 0;\npointer-events: none;\nbackground-repeat: no-repeat;\ntransform-origin: 0 0;\ntransform-style: preserve-3d;\nz-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}`,s=o.size,i=o.frames,d=o.image,u=o.offset,a=o.zIndex,c=o.scale,l=d.toLowerCase().endsWith(".gif");var t;t=o.pixel?"pixelated":"auto",n+=`\n${r} {\nwidth: ${s[0]}px;\nheight: ${s[1]}px;\nbackground-image: url("${d}");\nimage-rendering: ${t};\n${c||u?`transform: ${[c&&`scale(${c[0]}, ${c[1]})`,u&&`translate(-${u[0]}px, -${u[1]}px)`].filter(Boolean).join(" ")};`:""}\n \n${void 0!==a?`z-index:${a};`:""}\n}`;const p=o.duration;if(!l&&i>1&&"number"==typeof p){const t=`animecursor_${e}`;n+=`\n${r} {\nanimation: ${t} steps(${i}) ${p}s infinite ${o.pingpong?"alternate":""};\n}\n\n@keyframes ${t} {\nfrom { background-position: 0 0; }\nto { background-position: -${s[0]*i}px 0; }\n}\n`}}e.textContent=n,document.head.appendChild(e)}_bindElements(){for(const[e,n]of Object.entries(this.options.cursors))n.tags.forEach(n=>{const t=n.toUpperCase();document.querySelectorAll(t).forEach(n=>{n.dataset.cursor||(n.dataset.cursor=e)})})}_bindMouse(){document.addEventListener("mousemove",e=>{const n=e.clientX,t=e.clientY;this.cursorEl.style.left=n+"px",this.cursorEl.style.top=t+"px",this.debugEl&&(this.debugEl.style.left=n+"px",this.debugEl.style.top=t+"px");const o=document.elementFromPoint(n,t);if(!o)return;const r=o.dataset.cursor||"default";this.debugEl&&(this.debugEl.textContent=`(${n}px , ${t}px) ${r}`),r!==this.lastCursorType&&(this.debugEl?this.cursorEl.className=`cursor-${r} cursor-debugmode`:this.cursorEl.className=`cursor-${r}`,this.lastCursorType=r)})}_getMaxZIndex(){return 2147483646}}});
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "anime-cursor",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A lightweight JS for website animated cursors",
|
|
5
|
+
"main": "dist/anime-cursor.umd.js",
|
|
6
|
+
"module": "dist/anime-cursor.esm.js",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"keywords": ["cursor", "animation", "ui", "javascript"],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "rollup -c"
|
|
13
|
+
},
|
|
14
|
+
"author": "ShuninYu",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
18
|
+
"rollup": "^4.56.0"
|
|
19
|
+
}
|
|
20
|
+
}
|