anime-cursor 0.1.1 → 0.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 CHANGED
@@ -23,7 +23,7 @@ AnimeCursor has no dependencies on any frameworks, making it suitable for person
23
23
 
24
24
  ## 📦 Installation
25
25
 
26
- ### jsDeliver
26
+ ### CDN
27
27
 
28
28
  ```html
29
29
  <script src="https://cdn.jsdelivr.net/npm/anime-cursor/dist/anime-cursor.umd.min.js"></script>
@@ -53,20 +53,27 @@ Here is an example of how to use AnimeCursor:
53
53
  <script>
54
54
  new AnimeCursor({
55
55
  cursors: {
56
+ // each type of cursor needs tags, size and image
56
57
  default: {
57
- tags: ['body'],
58
+ tags: ['body'], // default cursor recommended setting
58
59
  size: [64,64],
59
- image: './cursor_default.png',
60
- frames: 1
60
+ image: './cursor_default.png' // static cursor only needs image
61
61
  },
62
+ // png sprite animated cursor needs frames and duration
62
63
  pointer: {
63
64
  tags: ['a', 'button'],
64
65
  size: [64,64],
65
66
  image: './cursor_pointer.png',
66
67
  frames: 3,
67
68
  duration: 0.3,
68
- pingpong: true,
69
- offset: [10, 4]
69
+ pingpong: true, // enable pingpong loop
70
+ offset: [10, 4] // if the pointing spot is not at the top left of the image, set offset
71
+ },
72
+ // gif cursor doesn't needs frames and duration
73
+ text: {
74
+ tags: ['p', 'h1', 'h2', 'span'],
75
+ size: [32, 64],
76
+ image: './cursor_text.gif'
70
77
  }
71
78
  }
72
79
  });
@@ -89,11 +96,11 @@ For each key, the following parameters can be set. Missing required parameters w
89
96
  |-|-|-|-|
90
97
  | `tags` | `string[]` | ✅ | HTML tags that should use this cursor |
91
98
  | `size` | `number` | ✅ | Cursor dimensions [width, height] in pixels |
92
- | `offset` | `[number, number]` | | Cursor alignment offset [ x , y ] |
93
99
  | `image` | `string` | ✅ | Image path (PNG / GIF) |
94
- | `frames` | `number` | ✅ | Number of frames for PNG sprites (set to `1` for static images) |
100
+ | `frames` | `number` || Number of frames for PNG sprites (set to `1` for static images) |
95
101
  | `duration` | `number` | | Animation loop duration (seconds) ⚠️ PNG sprite animations *only* work when this parameter is set |
96
102
  | `pingpong` | `boolean` | | Enable ping-pong (back-and-forth) looping (for PNG sprite animations only) |
103
+ | `offset` | `[number, number]` | | Cursor alignment offset [ x , y ] ⚠️ This parameter is not affected by `scale`|
97
104
  | `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. |
98
105
  | `pixel` | `boolean` | | Enable pixelated rendering |
99
106
  | `zIndex` | `number` | | Cursor z-index layer (not recommended to modify) |
@@ -115,7 +122,7 @@ If you want animated cursors to be displayed on these devices, add `enableTouch:
115
122
 
116
123
  ### 📁 Files
117
124
 
118
- * 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.
125
+ * **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.
119
126
  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]`.
120
127
 
121
128
  * 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.
@@ -143,7 +150,7 @@ Therefore, to assign a specific animated cursor to a particular element, simply
143
150
  Animation is generated **only when all of the following conditions are met**:
144
151
 
145
152
  * The image is a PNG
146
- * `frames > 1`
153
+ * `frames` is set and `frames > 1`
147
154
  * `duration` is set
148
155
 
149
156
  If `duration` is not set, the cursor will be treated as a **static cursor**, even if `frames > 1`.
@@ -183,7 +190,7 @@ AnimeCursor 无需依赖任何框架,适合个人网站、创意作品集以
183
190
 
184
191
  ## 📦 部署方法
185
192
 
186
- ### jsDeliver
193
+ ### CDN
187
194
 
188
195
  ```html
189
196
  <script src="https://cdn.jsdelivr.net/npm/anime-cursor/dist/anime-cursor.umd.min.js"></script>
@@ -212,20 +219,27 @@ new AnimeCursor({...});
212
219
  <script>
213
220
  new AnimeCursor({
214
221
  cursors: {
222
+ // 每种光标都需要 tags size 和 image
215
223
  default: {
216
- tags: ['body'],
224
+ tags: ['body'], // 默认光标推荐照此设置
217
225
  size: [64,64],
218
- image: './cursor_default.png',
219
- frames: 1
226
+ image: './cursor_default.png' // 静态光标只需要图片链接
220
227
  },
228
+ // png 精灵图动画光标还需要 frames 和 duration
221
229
  pointer: {
222
230
  tags: ['a', 'button'],
223
231
  size: [64,64],
224
232
  image: './cursor_pointer.png',
225
233
  frames: 3,
226
234
  duration: 0.3,
227
- pingpong: true,
228
- offset: [10, 4]
235
+ pingpong: true, // enable pingpong loop
236
+ offset: [10, 4] // if the pointing spot is not at the top left of the image, set offset
237
+ },
238
+ // gif 光标不需要 frames 或 duration
239
+ text: {
240
+ tags: ['p', 'h1', 'h2', 'span'],
241
+ size: [32, 64],
242
+ image: './cursor_text.gif'
229
243
  }
230
244
  }
231
245
  });
@@ -248,11 +262,11 @@ new AnimeCursor({
248
262
  |-|-|-|-|
249
263
  |`tags`|`string[]`|✅|使用该光标的 HTML 标签|
250
264
  |`size`|`number`|✅|光标尺寸(宽高,像素)|
251
- |`offset`|`[number, number]`||光标对齐偏移量 [ x , y ]|
252
265
  |`image`|`string`|✅|图片路径(PNG / GIF)|
253
- |`frames`|`number`|✅|PNG 帧数(静态图片请设置为 `1` )|
266
+ |`frames`|`number`||PNG 帧数(静态图片请设置为 `1` )|
254
267
  |`duration`|`number`||动画循环时长(秒)⚠️PNG精灵图动画只有设置该参数才会生效|
255
268
  |`pingpong`|`boolean`||是否启用乒乓循环(仅PNG精灵图动画)|
269
+ |`offset`|`[number, number]`||光标对齐偏移量 [ x , y ]⚠️此参数不受 `scale` 影响|
256
270
  |`scale`|`[number, number]`||基于size的光标缩放 [ x , y ]⚠️仅支持GIF光标,PNG精灵图动画光标请勿设置,否则会使动画失效|
257
271
  |`pixel`|`boolean`||是否启用像素化渲染|
258
272
  |`zIndex`|`number`||光标层级(不建议添加此项设置)|
@@ -274,7 +288,7 @@ AnimeCursor 会自动识别移动触屏设备(比如手机、平板电脑)
274
288
 
275
289
  ### 📁 文件
276
290
 
277
- * 对于任何 PNG 精灵图动画光标,它的 PNG 精灵图表都应该为单行横向布局,AnimeCursor 会自动生成 PNG 精灵图动画。
291
+ * **对于任何 PNG 精灵图动画光标,它的 PNG 精灵图表都应该为单行横向布局,** AnimeCursor 会自动生成 PNG 精灵图动画。
278
292
  例如,你为 `pointer` 光标设置的`size`(长,宽)为 `[64px , 64px]` ,帧数为 `3` ,那么你准备的精灵图表尺寸(长,宽)应该为: `[192px , 64px]` 。
279
293
 
280
294
  * 对于帧数特别多的像素图,你可以使用原尺寸图片(无论是gif还是png精灵图表)以节省存储空间或带宽,并在参数中设置 `scale` 来缩放光标,并将 `pixel` 设置为 `true` 来避免缩放模糊。
@@ -302,7 +316,7 @@ AnimeCursor 会根据配置自动为页面元素添加 `data-cursor`:
302
316
  只有在 **同时满足以下条件** 时,才会生成动画:
303
317
 
304
318
  * 图片为 PNG
305
- * `frames > 1`
319
+ * 设置了 `frames` 且 `frames > 1`
306
320
  * 设置了 `duration`
307
321
 
308
322
  如果未设置 `duration`,即使帧数大于 1,也会被视为**静态光标**。
@@ -1,3 +1,6 @@
1
+ // AnimeCursor by github@ShuninYu
2
+ // v0.1.2
3
+
1
4
  class AnimeCursor {
2
5
 
3
6
  constructor(options = {}) {
@@ -46,7 +49,7 @@ class AnimeCursor {
46
49
  }
47
50
 
48
51
  for (const [name, cfg] of Object.entries(this.options.cursors)) {
49
- const required = ['tags', 'size', 'image', 'frames'];
52
+ const required = ['tags', 'size', 'image'];
50
53
  required.forEach(key => {
51
54
  if (cfg[key] === undefined) {
52
55
  console.error(`[AnimeCursor] 光标 "${name}" 缺少必填项:${key}`);
@@ -87,13 +90,13 @@ class AnimeCursor {
87
90
  }
88
91
 
89
92
  // ----------------------------
90
- // 插入基础 CSS
93
+ // 插入样式 CSS
91
94
  // ----------------------------
92
95
  _injectCSS() {
93
96
  const style = document.createElement('style');
94
97
  let css = '';
95
98
 
96
- /* 通用样式和debug样式 */
99
+ /* 通用样式 */
97
100
  css += `
98
101
  * {
99
102
  cursor: none !important;
@@ -4,6 +4,9 @@
4
4
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.AnimeCursor = factory());
5
5
  })(this, (function () { 'use strict';
6
6
 
7
+ // AnimeCursor by github@ShuninYu
8
+ // v0.1.2
9
+
7
10
  class AnimeCursor {
8
11
 
9
12
  constructor(options = {}) {
@@ -52,7 +55,7 @@
52
55
  }
53
56
 
54
57
  for (const [name, cfg] of Object.entries(this.options.cursors)) {
55
- const required = ['tags', 'size', 'image', 'frames'];
58
+ const required = ['tags', 'size', 'image'];
56
59
  required.forEach(key => {
57
60
  if (cfg[key] === undefined) {
58
61
  console.error(`[AnimeCursor] 光标 "${name}" 缺少必填项:${key}`);
@@ -93,13 +96,13 @@
93
96
  }
94
97
 
95
98
  // ----------------------------
96
- // 插入基础 CSS
99
+ // 插入样式 CSS
97
100
  // ----------------------------
98
101
  _injectCSS() {
99
102
  const style = document.createElement('style');
100
103
  let css = '';
101
104
 
102
- /* 通用样式和debug样式 */
105
+ /* 通用样式 */
103
106
  css += `
104
107
  * {
105
108
  cursor: none !important;
@@ -1 +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}}});
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"].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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anime-cursor",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "A lightweight JS for website animated cursors",
5
5
  "main": "dist/anime-cursor.umd.js",
6
6
  "module": "dist/anime-cursor.esm.js",