├── .gitignore ├── scripts └── build.js ├── package.json ├── dist ├── cdn.min.js └── esm.min.js ├── LICENSE ├── src └── index.js └── README.md /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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--