├── .gitignore
├── LICENSE
├── README.md
├── builds
├── cdn.js
└── module.js
├── dist
├── cdn.min.js
└── esm.min.js
├── package-lock.json
├── package.json
├── scripts
└── build.js
└── src
└── index.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # misc
9 | .DS_Store
10 | *.pem
11 |
12 | # debug
13 | npm-debug.log*
14 | yarn-debug.log*
15 | yarn-error.log*
16 |
17 | # local env files
18 | .env.local
19 | .env.development.local
20 | .env.test.local
21 | .env.production.local
22 |
23 | # test
24 | index.html
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Mark Mead
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Custom Cursor 👆
2 |
3 | 
4 | 
5 | 
6 | 
7 |
8 | This is a tiny JavaScript package that creates custom cursor for you with
9 | minimal JavaScript and allows you to write hover effects for the cursor(s) in
10 | CSS.
11 |
12 | ## Features
13 |
14 | - 🪶 Lightweight (< 1kb minified)
15 | - 🎨 Fully customizable with CSS
16 | - ⚡ Simple API with minimal configuration
17 | - 🔄 Multiple cursor support for follow-along effects
18 | - 🎯 Target specific elements for custom hover states
19 | - 📱 Works with mouse and touch devices
20 |
21 | Perfect for creative websites, portfolios, and interactive experiences where you
22 | want to replace the default cursor with something more engaging.
23 |
24 | ## Install
25 |
26 | ## CDN
27 |
28 | For this package to work with a CDN, you'll need to access the `Cursor` class
29 | from the `window` object.
30 |
31 | ```html
32 |
36 |
37 |
42 | ```
43 |
44 | ### Configuration with CDN
45 |
46 | When using the CDN version, you still have full access to all configuration
47 | options:
48 |
49 | ```js
50 | document.addEventListener('DOMContentLoaded', () => {
51 | new window['Cursor']({
52 | count: 3, // Creates multiple cursor elements
53 | targets: ['a', 'button', '.interactive'], // Elements that trigger hover states
54 | })
55 | })
56 | ```
57 |
58 | These options work exactly the same way as in the package version, giving you
59 | complete control over your custom cursor behavior.
60 |
61 | ## Package
62 |
63 | ```shell
64 | yarn add -D custom-cursor
65 | npm install -D custom-cursor
66 | ```
67 |
68 | ```js
69 | import Cursor from 'custom-cursor'
70 |
71 | new Cursor({})
72 | ```
73 |
74 | ## Options
75 |
76 | The `Cursor` constructor accepts an optional configuration object with two
77 | parameters:
78 |
79 | ```js
80 | new Cursor({
81 | count: 5, // Creates multiple cursor elements
82 | targets: ['a', '.title', '#header'], // Elements that trigger hover states
83 | })
84 | ```
85 |
86 | Both parameters are optional and can be customized to fit your specific
87 | requirements.
88 |
89 | ### Count
90 |
91 | This parameter lets you specify the number of cursor elements to create, which
92 | is ideal for creating trailing cursor effects.
93 |
94 | When you set `count: 5`, the package generates the following HTML structure:
95 |
96 | ```html
97 |
98 |
99 |
100 |
101 |
102 | ```
103 |
104 | Each cursor element receives a `data-cursor` attribute with its index number,
105 | allowing you to style each cursor element individually with CSS:
106 |
107 | ```css
108 | [data-cursor] {
109 | width: 20px;
110 | height: 20px;
111 | }
112 |
113 | [data-cursor='0'] {
114 | background: #00f;
115 | }
116 |
117 | [data-cursor='1'] {
118 | background: #eee;
119 | }
120 | ```
121 |
122 | This approach gives you complete control over the appearance of each cursor in
123 | the sequence, creating trailing effects, size variations, or color gradients.
124 |
125 | ### Targets
126 |
127 | The `targets` parameter lets you define specific HTML elements that will trigger
128 | cursor hover effects.
129 |
130 | For example, with `targets: ['a', '.title', '#header']`, the package will:
131 |
132 | 1. Locate all `` elements, elements with the class `.title`, and the element
133 | with ID `#header`
134 | 2. Add event listeners for `mouseover` and `mouseleave` on these elements
135 | 3. When the mouse hovers over a target, add a class of `cursor-hover--`
136 | to the document body
137 |
138 | The `` portion of the class name corresponds to the identifier in your
139 | targets array. For instance, hovering over `.title` elements will add
140 | `cursor-hover--title` to the body.
141 |
142 | #### Creating Hover Styles
143 |
144 | You can style cursor hover states using the added class names. For example:
145 |
146 | ```css
147 | /* Style all cursors when hovering over links */
148 | .cursor-hover--a [data-cursor] {
149 | }
150 |
151 | /* Style all cursors when hovering over elements with .title class */
152 | .cursor-hover--title [data-cursor] {
153 | }
154 |
155 | /* Style all cursors when hovering over element with #header ID */
156 | .cursor-hover--header [data-cursor] {
157 | }
158 |
159 | /* Style specific cursors by index during hover */
160 | .cursor-hover--header [data-cursor='0'] {
161 | }
162 |
163 | .cursor-hover--header [data-cursor='1'] {
164 | }
165 | ```
166 |
167 | This approach gives you fine-grained control over cursor appearance during
168 | different hover interactions.
169 |
--------------------------------------------------------------------------------
/builds/cdn.js:
--------------------------------------------------------------------------------
1 | import Cursor from '../src/index.js'
2 |
3 | window.Cursor = Cursor
4 |
--------------------------------------------------------------------------------
/builds/module.js:
--------------------------------------------------------------------------------
1 | import Cursor from '../src/index.js'
2 |
3 | export default Cursor
4 |
--------------------------------------------------------------------------------
/dist/cdn.min.js:
--------------------------------------------------------------------------------
1 | (()=>{function r(n={}){let s=document.body,a=n.count||1,i="position: fixed; pointer-events: none; top: 0; left: 0;",c=n.targets||!1;function l(){let t=Array.from({length:a},(e,o)=>o);for(let[e]of t.entries()){let o=document.createElement("div");d(o,e)}}function d(t,e){t.setAttribute("data-cursor",`${e}`),t.setAttribute("style",i),s.append(t)}function f(){let t=document.querySelectorAll("[data-cursor]");document.addEventListener("mousemove",e=>{let{clientX:o,clientY:g}=e;for(let p of t)m(p,o,g)})}function m(t,e,o){t.style.transform=`translate3d(calc(${e}px - 50%), calc(${o}px - 50%), 0)`}function v(){if(Array.isArray(c))for(let t of c){let e=document.querySelectorAll(t);for(let o of e)o.addEventListener("mouseover",()=>{u(t)}),o.addEventListener("mouseleave",()=>{u(t)})}}function u(t){let e=t.replace(/[.#!]/g,"");s.classList.toggle(`cursor-hover--${e}`)}l(),f(),v()}window.Cursor=r;})();
2 |
--------------------------------------------------------------------------------
/dist/esm.min.js:
--------------------------------------------------------------------------------
1 | function r(n={}){let s=document.body,a=n.count||1,l="position: fixed; pointer-events: none; top: 0; left: 0;",c=n.targets||!1;function i(){let t=Array.from({length:a},(e,o)=>o);for(let[e]of t.entries()){let o=document.createElement("div");f(o,e)}}function f(t,e){t.setAttribute("data-cursor",`${e}`),t.setAttribute("style",l),s.append(t)}function d(){let t=document.querySelectorAll("[data-cursor]");document.addEventListener("mousemove",e=>{let{clientX:o,clientY:p}=e;for(let g of t)m(g,o,p)})}function m(t,e,o){t.style.transform=`translate3d(calc(${e}px - 50%), calc(${o}px - 50%), 0)`}function v(){if(Array.isArray(c))for(let t of c){let e=document.querySelectorAll(t);for(let o of e)o.addEventListener("mouseover",()=>{u(t)}),o.addEventListener("mouseleave",()=>{u(t)})}}function u(t){let e=t.replace(/[.#!]/g,"");s.classList.toggle(`cursor-hover--${e}`)}i(),d(),v()}var A=r;export{A as default};
2 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "custom-cursor",
3 | "version": "2.3.2",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "custom-cursor",
9 | "version": "2.3.2",
10 | "license": "MIT",
11 | "devDependencies": {
12 | "esbuild": "^0.25.0"
13 | }
14 | },
15 | "node_modules/@esbuild/aix-ppc64": {
16 | "version": "0.25.0",
17 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz",
18 | "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==",
19 | "cpu": [
20 | "ppc64"
21 | ],
22 | "dev": true,
23 | "license": "MIT",
24 | "optional": true,
25 | "os": [
26 | "aix"
27 | ],
28 | "engines": {
29 | "node": ">=18"
30 | }
31 | },
32 | "node_modules/@esbuild/android-arm": {
33 | "version": "0.25.0",
34 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz",
35 | "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==",
36 | "cpu": [
37 | "arm"
38 | ],
39 | "dev": true,
40 | "license": "MIT",
41 | "optional": true,
42 | "os": [
43 | "android"
44 | ],
45 | "engines": {
46 | "node": ">=18"
47 | }
48 | },
49 | "node_modules/@esbuild/android-arm64": {
50 | "version": "0.25.0",
51 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz",
52 | "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==",
53 | "cpu": [
54 | "arm64"
55 | ],
56 | "dev": true,
57 | "license": "MIT",
58 | "optional": true,
59 | "os": [
60 | "android"
61 | ],
62 | "engines": {
63 | "node": ">=18"
64 | }
65 | },
66 | "node_modules/@esbuild/android-x64": {
67 | "version": "0.25.0",
68 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz",
69 | "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==",
70 | "cpu": [
71 | "x64"
72 | ],
73 | "dev": true,
74 | "license": "MIT",
75 | "optional": true,
76 | "os": [
77 | "android"
78 | ],
79 | "engines": {
80 | "node": ">=18"
81 | }
82 | },
83 | "node_modules/@esbuild/darwin-arm64": {
84 | "version": "0.25.0",
85 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz",
86 | "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==",
87 | "cpu": [
88 | "arm64"
89 | ],
90 | "dev": true,
91 | "license": "MIT",
92 | "optional": true,
93 | "os": [
94 | "darwin"
95 | ],
96 | "engines": {
97 | "node": ">=18"
98 | }
99 | },
100 | "node_modules/@esbuild/darwin-x64": {
101 | "version": "0.25.0",
102 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz",
103 | "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==",
104 | "cpu": [
105 | "x64"
106 | ],
107 | "dev": true,
108 | "license": "MIT",
109 | "optional": true,
110 | "os": [
111 | "darwin"
112 | ],
113 | "engines": {
114 | "node": ">=18"
115 | }
116 | },
117 | "node_modules/@esbuild/freebsd-arm64": {
118 | "version": "0.25.0",
119 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz",
120 | "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==",
121 | "cpu": [
122 | "arm64"
123 | ],
124 | "dev": true,
125 | "license": "MIT",
126 | "optional": true,
127 | "os": [
128 | "freebsd"
129 | ],
130 | "engines": {
131 | "node": ">=18"
132 | }
133 | },
134 | "node_modules/@esbuild/freebsd-x64": {
135 | "version": "0.25.0",
136 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz",
137 | "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==",
138 | "cpu": [
139 | "x64"
140 | ],
141 | "dev": true,
142 | "license": "MIT",
143 | "optional": true,
144 | "os": [
145 | "freebsd"
146 | ],
147 | "engines": {
148 | "node": ">=18"
149 | }
150 | },
151 | "node_modules/@esbuild/linux-arm": {
152 | "version": "0.25.0",
153 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz",
154 | "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==",
155 | "cpu": [
156 | "arm"
157 | ],
158 | "dev": true,
159 | "license": "MIT",
160 | "optional": true,
161 | "os": [
162 | "linux"
163 | ],
164 | "engines": {
165 | "node": ">=18"
166 | }
167 | },
168 | "node_modules/@esbuild/linux-arm64": {
169 | "version": "0.25.0",
170 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz",
171 | "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==",
172 | "cpu": [
173 | "arm64"
174 | ],
175 | "dev": true,
176 | "license": "MIT",
177 | "optional": true,
178 | "os": [
179 | "linux"
180 | ],
181 | "engines": {
182 | "node": ">=18"
183 | }
184 | },
185 | "node_modules/@esbuild/linux-ia32": {
186 | "version": "0.25.0",
187 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz",
188 | "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==",
189 | "cpu": [
190 | "ia32"
191 | ],
192 | "dev": true,
193 | "license": "MIT",
194 | "optional": true,
195 | "os": [
196 | "linux"
197 | ],
198 | "engines": {
199 | "node": ">=18"
200 | }
201 | },
202 | "node_modules/@esbuild/linux-loong64": {
203 | "version": "0.25.0",
204 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz",
205 | "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==",
206 | "cpu": [
207 | "loong64"
208 | ],
209 | "dev": true,
210 | "license": "MIT",
211 | "optional": true,
212 | "os": [
213 | "linux"
214 | ],
215 | "engines": {
216 | "node": ">=18"
217 | }
218 | },
219 | "node_modules/@esbuild/linux-mips64el": {
220 | "version": "0.25.0",
221 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz",
222 | "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==",
223 | "cpu": [
224 | "mips64el"
225 | ],
226 | "dev": true,
227 | "license": "MIT",
228 | "optional": true,
229 | "os": [
230 | "linux"
231 | ],
232 | "engines": {
233 | "node": ">=18"
234 | }
235 | },
236 | "node_modules/@esbuild/linux-ppc64": {
237 | "version": "0.25.0",
238 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz",
239 | "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==",
240 | "cpu": [
241 | "ppc64"
242 | ],
243 | "dev": true,
244 | "license": "MIT",
245 | "optional": true,
246 | "os": [
247 | "linux"
248 | ],
249 | "engines": {
250 | "node": ">=18"
251 | }
252 | },
253 | "node_modules/@esbuild/linux-riscv64": {
254 | "version": "0.25.0",
255 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz",
256 | "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==",
257 | "cpu": [
258 | "riscv64"
259 | ],
260 | "dev": true,
261 | "license": "MIT",
262 | "optional": true,
263 | "os": [
264 | "linux"
265 | ],
266 | "engines": {
267 | "node": ">=18"
268 | }
269 | },
270 | "node_modules/@esbuild/linux-s390x": {
271 | "version": "0.25.0",
272 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz",
273 | "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==",
274 | "cpu": [
275 | "s390x"
276 | ],
277 | "dev": true,
278 | "license": "MIT",
279 | "optional": true,
280 | "os": [
281 | "linux"
282 | ],
283 | "engines": {
284 | "node": ">=18"
285 | }
286 | },
287 | "node_modules/@esbuild/linux-x64": {
288 | "version": "0.25.0",
289 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz",
290 | "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==",
291 | "cpu": [
292 | "x64"
293 | ],
294 | "dev": true,
295 | "license": "MIT",
296 | "optional": true,
297 | "os": [
298 | "linux"
299 | ],
300 | "engines": {
301 | "node": ">=18"
302 | }
303 | },
304 | "node_modules/@esbuild/netbsd-arm64": {
305 | "version": "0.25.0",
306 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz",
307 | "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==",
308 | "cpu": [
309 | "arm64"
310 | ],
311 | "dev": true,
312 | "license": "MIT",
313 | "optional": true,
314 | "os": [
315 | "netbsd"
316 | ],
317 | "engines": {
318 | "node": ">=18"
319 | }
320 | },
321 | "node_modules/@esbuild/netbsd-x64": {
322 | "version": "0.25.0",
323 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz",
324 | "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==",
325 | "cpu": [
326 | "x64"
327 | ],
328 | "dev": true,
329 | "license": "MIT",
330 | "optional": true,
331 | "os": [
332 | "netbsd"
333 | ],
334 | "engines": {
335 | "node": ">=18"
336 | }
337 | },
338 | "node_modules/@esbuild/openbsd-arm64": {
339 | "version": "0.25.0",
340 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz",
341 | "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==",
342 | "cpu": [
343 | "arm64"
344 | ],
345 | "dev": true,
346 | "license": "MIT",
347 | "optional": true,
348 | "os": [
349 | "openbsd"
350 | ],
351 | "engines": {
352 | "node": ">=18"
353 | }
354 | },
355 | "node_modules/@esbuild/openbsd-x64": {
356 | "version": "0.25.0",
357 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz",
358 | "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==",
359 | "cpu": [
360 | "x64"
361 | ],
362 | "dev": true,
363 | "license": "MIT",
364 | "optional": true,
365 | "os": [
366 | "openbsd"
367 | ],
368 | "engines": {
369 | "node": ">=18"
370 | }
371 | },
372 | "node_modules/@esbuild/sunos-x64": {
373 | "version": "0.25.0",
374 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz",
375 | "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==",
376 | "cpu": [
377 | "x64"
378 | ],
379 | "dev": true,
380 | "license": "MIT",
381 | "optional": true,
382 | "os": [
383 | "sunos"
384 | ],
385 | "engines": {
386 | "node": ">=18"
387 | }
388 | },
389 | "node_modules/@esbuild/win32-arm64": {
390 | "version": "0.25.0",
391 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz",
392 | "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==",
393 | "cpu": [
394 | "arm64"
395 | ],
396 | "dev": true,
397 | "license": "MIT",
398 | "optional": true,
399 | "os": [
400 | "win32"
401 | ],
402 | "engines": {
403 | "node": ">=18"
404 | }
405 | },
406 | "node_modules/@esbuild/win32-ia32": {
407 | "version": "0.25.0",
408 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz",
409 | "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==",
410 | "cpu": [
411 | "ia32"
412 | ],
413 | "dev": true,
414 | "license": "MIT",
415 | "optional": true,
416 | "os": [
417 | "win32"
418 | ],
419 | "engines": {
420 | "node": ">=18"
421 | }
422 | },
423 | "node_modules/@esbuild/win32-x64": {
424 | "version": "0.25.0",
425 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz",
426 | "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==",
427 | "cpu": [
428 | "x64"
429 | ],
430 | "dev": true,
431 | "license": "MIT",
432 | "optional": true,
433 | "os": [
434 | "win32"
435 | ],
436 | "engines": {
437 | "node": ">=18"
438 | }
439 | },
440 | "node_modules/esbuild": {
441 | "version": "0.25.0",
442 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz",
443 | "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==",
444 | "dev": true,
445 | "hasInstallScript": true,
446 | "license": "MIT",
447 | "bin": {
448 | "esbuild": "bin/esbuild"
449 | },
450 | "engines": {
451 | "node": ">=18"
452 | },
453 | "optionalDependencies": {
454 | "@esbuild/aix-ppc64": "0.25.0",
455 | "@esbuild/android-arm": "0.25.0",
456 | "@esbuild/android-arm64": "0.25.0",
457 | "@esbuild/android-x64": "0.25.0",
458 | "@esbuild/darwin-arm64": "0.25.0",
459 | "@esbuild/darwin-x64": "0.25.0",
460 | "@esbuild/freebsd-arm64": "0.25.0",
461 | "@esbuild/freebsd-x64": "0.25.0",
462 | "@esbuild/linux-arm": "0.25.0",
463 | "@esbuild/linux-arm64": "0.25.0",
464 | "@esbuild/linux-ia32": "0.25.0",
465 | "@esbuild/linux-loong64": "0.25.0",
466 | "@esbuild/linux-mips64el": "0.25.0",
467 | "@esbuild/linux-ppc64": "0.25.0",
468 | "@esbuild/linux-riscv64": "0.25.0",
469 | "@esbuild/linux-s390x": "0.25.0",
470 | "@esbuild/linux-x64": "0.25.0",
471 | "@esbuild/netbsd-arm64": "0.25.0",
472 | "@esbuild/netbsd-x64": "0.25.0",
473 | "@esbuild/openbsd-arm64": "0.25.0",
474 | "@esbuild/openbsd-x64": "0.25.0",
475 | "@esbuild/sunos-x64": "0.25.0",
476 | "@esbuild/win32-arm64": "0.25.0",
477 | "@esbuild/win32-ia32": "0.25.0",
478 | "@esbuild/win32-x64": "0.25.0"
479 | }
480 | }
481 | },
482 | "dependencies": {
483 | "@esbuild/aix-ppc64": {
484 | "version": "0.25.0",
485 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz",
486 | "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==",
487 | "dev": true,
488 | "optional": true
489 | },
490 | "@esbuild/android-arm": {
491 | "version": "0.25.0",
492 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz",
493 | "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==",
494 | "dev": true,
495 | "optional": true
496 | },
497 | "@esbuild/android-arm64": {
498 | "version": "0.25.0",
499 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz",
500 | "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==",
501 | "dev": true,
502 | "optional": true
503 | },
504 | "@esbuild/android-x64": {
505 | "version": "0.25.0",
506 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz",
507 | "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==",
508 | "dev": true,
509 | "optional": true
510 | },
511 | "@esbuild/darwin-arm64": {
512 | "version": "0.25.0",
513 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz",
514 | "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==",
515 | "dev": true,
516 | "optional": true
517 | },
518 | "@esbuild/darwin-x64": {
519 | "version": "0.25.0",
520 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz",
521 | "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==",
522 | "dev": true,
523 | "optional": true
524 | },
525 | "@esbuild/freebsd-arm64": {
526 | "version": "0.25.0",
527 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz",
528 | "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==",
529 | "dev": true,
530 | "optional": true
531 | },
532 | "@esbuild/freebsd-x64": {
533 | "version": "0.25.0",
534 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz",
535 | "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==",
536 | "dev": true,
537 | "optional": true
538 | },
539 | "@esbuild/linux-arm": {
540 | "version": "0.25.0",
541 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz",
542 | "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==",
543 | "dev": true,
544 | "optional": true
545 | },
546 | "@esbuild/linux-arm64": {
547 | "version": "0.25.0",
548 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz",
549 | "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==",
550 | "dev": true,
551 | "optional": true
552 | },
553 | "@esbuild/linux-ia32": {
554 | "version": "0.25.0",
555 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz",
556 | "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==",
557 | "dev": true,
558 | "optional": true
559 | },
560 | "@esbuild/linux-loong64": {
561 | "version": "0.25.0",
562 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz",
563 | "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==",
564 | "dev": true,
565 | "optional": true
566 | },
567 | "@esbuild/linux-mips64el": {
568 | "version": "0.25.0",
569 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz",
570 | "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==",
571 | "dev": true,
572 | "optional": true
573 | },
574 | "@esbuild/linux-ppc64": {
575 | "version": "0.25.0",
576 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz",
577 | "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==",
578 | "dev": true,
579 | "optional": true
580 | },
581 | "@esbuild/linux-riscv64": {
582 | "version": "0.25.0",
583 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz",
584 | "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==",
585 | "dev": true,
586 | "optional": true
587 | },
588 | "@esbuild/linux-s390x": {
589 | "version": "0.25.0",
590 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz",
591 | "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==",
592 | "dev": true,
593 | "optional": true
594 | },
595 | "@esbuild/linux-x64": {
596 | "version": "0.25.0",
597 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz",
598 | "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==",
599 | "dev": true,
600 | "optional": true
601 | },
602 | "@esbuild/netbsd-arm64": {
603 | "version": "0.25.0",
604 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz",
605 | "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==",
606 | "dev": true,
607 | "optional": true
608 | },
609 | "@esbuild/netbsd-x64": {
610 | "version": "0.25.0",
611 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz",
612 | "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==",
613 | "dev": true,
614 | "optional": true
615 | },
616 | "@esbuild/openbsd-arm64": {
617 | "version": "0.25.0",
618 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz",
619 | "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==",
620 | "dev": true,
621 | "optional": true
622 | },
623 | "@esbuild/openbsd-x64": {
624 | "version": "0.25.0",
625 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz",
626 | "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==",
627 | "dev": true,
628 | "optional": true
629 | },
630 | "@esbuild/sunos-x64": {
631 | "version": "0.25.0",
632 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz",
633 | "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==",
634 | "dev": true,
635 | "optional": true
636 | },
637 | "@esbuild/win32-arm64": {
638 | "version": "0.25.0",
639 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz",
640 | "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==",
641 | "dev": true,
642 | "optional": true
643 | },
644 | "@esbuild/win32-ia32": {
645 | "version": "0.25.0",
646 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz",
647 | "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==",
648 | "dev": true,
649 | "optional": true
650 | },
651 | "@esbuild/win32-x64": {
652 | "version": "0.25.0",
653 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz",
654 | "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==",
655 | "dev": true,
656 | "optional": true
657 | },
658 | "esbuild": {
659 | "version": "0.25.0",
660 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz",
661 | "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==",
662 | "dev": true,
663 | "requires": {
664 | "@esbuild/aix-ppc64": "0.25.0",
665 | "@esbuild/android-arm": "0.25.0",
666 | "@esbuild/android-arm64": "0.25.0",
667 | "@esbuild/android-x64": "0.25.0",
668 | "@esbuild/darwin-arm64": "0.25.0",
669 | "@esbuild/darwin-x64": "0.25.0",
670 | "@esbuild/freebsd-arm64": "0.25.0",
671 | "@esbuild/freebsd-x64": "0.25.0",
672 | "@esbuild/linux-arm": "0.25.0",
673 | "@esbuild/linux-arm64": "0.25.0",
674 | "@esbuild/linux-ia32": "0.25.0",
675 | "@esbuild/linux-loong64": "0.25.0",
676 | "@esbuild/linux-mips64el": "0.25.0",
677 | "@esbuild/linux-ppc64": "0.25.0",
678 | "@esbuild/linux-riscv64": "0.25.0",
679 | "@esbuild/linux-s390x": "0.25.0",
680 | "@esbuild/linux-x64": "0.25.0",
681 | "@esbuild/netbsd-arm64": "0.25.0",
682 | "@esbuild/netbsd-x64": "0.25.0",
683 | "@esbuild/openbsd-arm64": "0.25.0",
684 | "@esbuild/openbsd-x64": "0.25.0",
685 | "@esbuild/sunos-x64": "0.25.0",
686 | "@esbuild/win32-arm64": "0.25.0",
687 | "@esbuild/win32-ia32": "0.25.0",
688 | "@esbuild/win32-x64": "0.25.0"
689 | }
690 | }
691 | }
692 | }
693 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "custom-cursor",
3 | "version": "2.3.2",
4 | "description": "Create your own custom cursor with minimal JavaScript. Easy to implement, customizable, and lightweight solution for interactive web experiences 👆",
5 | "keywords": [
6 | "cursor",
7 | "custom-cursor",
8 | "interactive"
9 | ],
10 | "author": "Mark Mead",
11 | "license": "MIT",
12 | "module": "dist/esm.min.js",
13 | "unpkg": "dist/cdn.min.js",
14 | "scripts": {
15 | "build": "node scripts/build.js"
16 | },
17 | "devDependencies": {
18 | "esbuild": "^0.25.0"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/scripts/build.js:
--------------------------------------------------------------------------------
1 | buildPlugin({
2 | entryPoints: ['builds/cdn.js'],
3 | outfile: 'dist/cdn.min.js',
4 | })
5 |
6 | buildPlugin({
7 | entryPoints: ['builds/module.js'],
8 | outfile: 'dist/esm.min.js',
9 | platform: 'neutral',
10 | mainFields: ['main', 'module'],
11 | })
12 |
13 | function buildPlugin(buildOptions) {
14 | return require('esbuild').buildSync({
15 | ...buildOptions,
16 | minify: true,
17 | bundle: true,
18 | })
19 | }
20 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export default function createCursor(configOptions = {}) {
2 | const documentBody = document.body
3 |
4 | const cursorCount = configOptions.count || 1
5 | const cursorStyle = 'position: fixed; pointer-events: none; top: 0; left: 0;'
6 |
7 | const hoverTargets = configOptions.targets || false
8 |
9 | function initializeCursors() {
10 | const cursorElements = Array.from(
11 | { length: cursorCount },
12 | (_, indexPosition) => indexPosition
13 | )
14 |
15 | for (const [indexPosition] of cursorElements.entries()) {
16 | const cursorElement = document.createElement('div')
17 |
18 | createCursorElement(cursorElement, indexPosition)
19 | }
20 | }
21 |
22 | function createCursorElement(cursorElement, indexPosition) {
23 | cursorElement.setAttribute('data-cursor', `${indexPosition}`)
24 | cursorElement.setAttribute('style', cursorStyle)
25 |
26 | documentBody.append(cursorElement)
27 | }
28 |
29 | function trackMouseMovement() {
30 | const cursorElements = document.querySelectorAll('[data-cursor]')
31 |
32 | document.addEventListener('mousemove', (mouseEvent) => {
33 | const { clientX, clientY } = mouseEvent
34 |
35 | for (const cursorElement of cursorElements) {
36 | updateCursorPosition(cursorElement, clientX, clientY)
37 | }
38 | })
39 | }
40 |
41 | function updateCursorPosition(cursorElement, xPosition, yPosition) {
42 | cursorElement.style.transform = `translate3d(calc(${xPosition}px - 50%), calc(${yPosition}px - 50%), 0)`
43 | }
44 |
45 | function setupHoverEffects() {
46 | if (!Array.isArray(hoverTargets)) {
47 | return
48 | }
49 |
50 | for (const targetSelector of hoverTargets) {
51 | const targetElements = document.querySelectorAll(targetSelector)
52 |
53 | for (const targetElement of targetElements) {
54 | targetElement.addEventListener('mouseover', () => {
55 | toggleHoverState(targetSelector)
56 | })
57 |
58 | targetElement.addEventListener('mouseleave', () => {
59 | toggleHoverState(targetSelector)
60 | })
61 | }
62 | }
63 | }
64 |
65 | function toggleHoverState(targetSelector) {
66 | const targetName = targetSelector.replace(/[.#!]/g, '')
67 |
68 | documentBody.classList.toggle(`cursor-hover--${targetName}`)
69 | }
70 |
71 | initializeCursors()
72 | trackMouseMovement()
73 | setupHoverEffects()
74 | }
75 |
--------------------------------------------------------------------------------