├── .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 | ![](https://img.shields.io/bundlephobia/min/custom-cursor) 4 | ![](https://img.shields.io/npm/v/custom-cursor) 5 | ![](https://img.shields.io/npm/dt/custom-cursor) 6 | ![](https://img.shields.io/github/license/markmead/custom-cursor) 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 | --------------------------------------------------------------------------------