UNPKG

cursorlab

Version:

A powerful, modular JavaScript library for creating custom mouse cursors and trail effects

393 lines (337 loc) 13.7 kB
// Browser-compatible version of CursorLab (function(global) { 'use strict'; class CursorLab { constructor() { this.originalCursor = document.body.style.cursor || 'default'; this.customCursorElement = null; this.isCustomCursorActive = false; this.mousePosition = { x: 0, y: 0 }; this.targetPosition = { x: 0, y: 0 }; this.animationFrame = null; // Trail properties this.trailType = 'circle'; this.trailDelay = 0.1; this.trailSize = { width: 20, height: 20, radius: 10 }; this.trailThickness = 2; this.trailColor = '#1abc9c'; this.init(); } init() { // Track mouse movement document.addEventListener('mousemove', (e) => { this.mousePosition.x = e.clientX; this.mousePosition.y = e.clientY; }); // Inject CSS styles this.injectStyles(); } injectStyles() { const styleId = 'cursorlab-styles'; // Check if styles already exist if (document.getElementById(styleId)) { return; } const style = document.createElement('style'); style.id = styleId; style.textContent = ` .cursorlab-trail { position: fixed; pointer-events: none; z-index: 9999; transform: translate(-50%, -50%); } `; document.head.appendChild(style); } // New modular API methods setCursorTrail(trailType) { this.trailType = trailType; if (this.isCustomCursorActive) { this.applyTrailStyles(); } return this; } trailDelay(delay = 0.1) { this.trailDelayValue = delay; return this; } setSize(widthOrRadius, height) { if (height !== undefined) { // Two parameters: width and height this.trailSize.width = widthOrRadius; this.trailSize.height = height; this.trailSize.radius = Math.min(widthOrRadius, height) / 2; } else { // One parameter: radius or size this.trailSize.radius = widthOrRadius / 2; this.trailSize.width = widthOrRadius; this.trailSize.height = widthOrRadius; } if (this.isCustomCursorActive) { this.applyTrailStyles(); } return this; } setThickness(thickness) { this.trailThickness = thickness; if (this.isCustomCursorActive) { this.applyTrailStyles(); } return this; } setColor(color) { this.trailColor = color; if (this.isCustomCursorActive) { this.applyTrailStyles(); } return this; } setCustomCursor(cursorType, param1, param2) { if (cursorType === 'crosshair') { // Handle crosshair with custom parameters const thickness = param1 || 2; const length = param2 || 15; const crosshairSvg = `data:image/svg+xml;base64,${btoa(` <svg xmlns="http://www.w3.org/2000/svg" width="${length * 2}" height="${length * 2}" viewBox="0 0 ${length * 2} ${length * 2}"> <line x1="${length}" y1="0" x2="${length}" y2="${length * 2}" stroke="white" stroke-width="${thickness}" /> <line x1="0" y1="${length}" x2="${length * 2}" y2="${length}" stroke="white" stroke-width="${thickness}" /> <line x1="${length}" y1="0" x2="${length}" y2="${length * 2}" stroke="black" stroke-width="${thickness - 1}" /> <line x1="0" y1="${length}" x2="${length * 2}" y2="${length}" stroke="black" stroke-width="${thickness - 1}" /> </svg> `)}`; document.body.style.cursor = `url("${crosshairSvg}") ${length} ${length}, crosshair`; } else if (cursorType && cursorType.startsWith('url(')) { // Handle image cursors document.body.style.cursor = cursorType; } else if (cursorType) { // Handle any CSS cursor value document.body.style.cursor = cursorType; } return this; } setCustomStyle(cssStyles) { if (cssStyles && typeof cssStyles === 'object') { // Apply styles to the body for cursor-related styling Object.keys(cssStyles).forEach(property => { if (property.startsWith('cursor') || property === 'pointer-events') { document.body.style[property] = cssStyles[property]; } }); } else if (typeof cssStyles === 'string') { // Apply CSS string to a style element const styleId = 'cursorlab-custom-style'; let styleElement = document.getElementById(styleId); if (!styleElement) { styleElement = document.createElement('style'); styleElement.id = styleId; document.head.appendChild(styleElement); } styleElement.textContent = cssStyles; } return this; } startTrail() { this.createCustomTrail(); return this; } createCustomTrail() { this.removeCustomCursor(); this.customCursorElement = document.createElement('div'); this.customCursorElement.className = 'cursorlab-trail'; // Apply trail styling based on current properties this.applyTrailStyles(); document.body.appendChild(this.customCursorElement); this.isCustomCursorActive = true; // Start animation with current delay const animate = () => { if (!this.isCustomCursorActive || !this.customCursorElement) return; // Linear interpolation for smooth movement const delay = this.trailDelayValue || this.trailDelay; this.targetPosition.x += (this.mousePosition.x - this.targetPosition.x) * delay; this.targetPosition.y += (this.mousePosition.y - this.targetPosition.y) * delay; this.customCursorElement.style.left = this.targetPosition.x + 'px'; this.customCursorElement.style.top = this.targetPosition.y + 'px'; this.animationFrame = requestAnimationFrame(animate); }; // Initialize position this.targetPosition.x = this.mousePosition.x; this.targetPosition.y = this.mousePosition.y; animate(); } applyTrailStyles() { if (!this.customCursorElement) return; const { width, height, radius } = this.trailSize; const color = this.trailColor; const thickness = this.trailThickness; // Reset styles this.customCursorElement.style.width = ''; this.customCursorElement.style.height = ''; this.customCursorElement.style.background = ''; this.customCursorElement.style.border = ''; this.customCursorElement.style.borderRadius = ''; this.customCursorElement.style.clipPath = ''; this.customCursorElement.style.boxShadow = ''; switch (this.trailType) { case 'circle': this.customCursorElement.style.width = (radius * 2) + 'px'; this.customCursorElement.style.height = (radius * 2) + 'px'; this.customCursorElement.style.border = `${thickness}px solid ${color}`; this.customCursorElement.style.borderRadius = '50%'; this.customCursorElement.style.background = 'transparent'; this.customCursorElement.style.boxShadow = `0 0 10px ${color}40`; break; case 'circle-filled': this.customCursorElement.style.width = (radius * 2) + 'px'; this.customCursorElement.style.height = (radius * 2) + 'px'; this.customCursorElement.style.background = color; this.customCursorElement.style.borderRadius = '50%'; this.customCursorElement.style.boxShadow = `0 0 10px ${color}40`; break; case 'square': this.customCursorElement.style.width = width + 'px'; this.customCursorElement.style.height = height + 'px'; this.customCursorElement.style.border = `${thickness}px solid ${color}`; this.customCursorElement.style.background = 'transparent'; this.customCursorElement.style.boxShadow = `0 0 10px ${color}40`; break; case 'square-filled': this.customCursorElement.style.width = width + 'px'; this.customCursorElement.style.height = height + 'px'; this.customCursorElement.style.background = color; this.customCursorElement.style.boxShadow = `0 0 10px ${color}40`; break; case 'triangle': this.customCursorElement.style.width = '0'; this.customCursorElement.style.height = '0'; this.customCursorElement.style.borderLeft = `${width/2}px solid transparent`; this.customCursorElement.style.borderRight = `${width/2}px solid transparent`; this.customCursorElement.style.borderBottom = `${height}px solid ${color}`; this.customCursorElement.style.boxShadow = `0 0 10px ${color}40`; break; case 'star': this.customCursorElement.style.width = width + 'px'; this.customCursorElement.style.height = height + 'px'; this.customCursorElement.style.background = color; this.customCursorElement.style.clipPath = 'polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%)'; this.customCursorElement.style.boxShadow = `0 0 12px ${color}60`; break; case 'dot': this.customCursorElement.style.width = (radius * 2) + 'px'; this.customCursorElement.style.height = (radius * 2) + 'px'; this.customCursorElement.style.background = color; this.customCursorElement.style.borderRadius = '50%'; this.customCursorElement.style.boxShadow = `0 0 8px ${color}60`; break; } } removeCustomCursor() { if (this.customCursorElement) { this.customCursorElement.remove(); this.customCursorElement = null; } this.isCustomCursorActive = false; if (this.animationFrame) { cancelAnimationFrame(this.animationFrame); this.animationFrame = null; } } setDefault() { this.removeCustomCursor(); return this; } setNormalCursor() { document.body.style.cursor = this.originalCursor; return this; } // Utility methods isActive() { return this.isCustomCursorActive; } destroy() { this.removeCustomCursor(); const styleElement = document.getElementById('cursorlab-styles'); if (styleElement) { styleElement.remove(); } const customStyleElement = document.getElementById('cursorlab-custom-style'); if (customStyleElement) { customStyleElement.remove(); } } } // Static methods for easy usage CursorLab.setCursorTrail = function(trailType) { if (!global.cursorLabInstance) { global.cursorLabInstance = new CursorLab(); } return global.cursorLabInstance.setCursorTrail(trailType); }; CursorLab.trailDelay = function(delay) { if (!global.cursorLabInstance) { global.cursorLabInstance = new CursorLab(); } return global.cursorLabInstance.trailDelay(delay); }; CursorLab.setSize = function(widthOrRadius, height) { if (!global.cursorLabInstance) { global.cursorLabInstance = new CursorLab(); } return global.cursorLabInstance.setSize(widthOrRadius, height); }; CursorLab.setThickness = function(thickness) { if (!global.cursorLabInstance) { global.cursorLabInstance = new CursorLab(); } return global.cursorLabInstance.setThickness(thickness); }; CursorLab.setColor = function(color) { if (!global.cursorLabInstance) { global.cursorLabInstance = new CursorLab(); } return global.cursorLabInstance.setColor(color); }; CursorLab.setCustomCursor = function(cursorType, param1, param2) { if (!global.cursorLabInstance) { global.cursorLabInstance = new CursorLab(); } return global.cursorLabInstance.setCustomCursor(cursorType, param1, param2); }; CursorLab.setCustomStyle = function(cssStyles) { if (!global.cursorLabInstance) { global.cursorLabInstance = new CursorLab(); } return global.cursorLabInstance.setCustomStyle(cssStyles); }; CursorLab.startTrail = function() { if (!global.cursorLabInstance) { global.cursorLabInstance = new CursorLab(); } return global.cursorLabInstance.startTrail(); }; CursorLab.setDefault = function() { if (global.cursorLabInstance) { return global.cursorLabInstance.setDefault(); } }; CursorLab.setNormalCursor = function() { if (!global.cursorLabInstance) { global.cursorLabInstance = new CursorLab(); } return global.cursorLabInstance.setNormalCursor(); }; CursorLab.destroy = function() { if (global.cursorLabInstance) { global.cursorLabInstance.destroy(); global.cursorLabInstance = null; } }; // Export for different environments if (typeof module !== 'undefined' && module.exports) { // Node.js module.exports = CursorLab; } else if (typeof window !== 'undefined') { // Browser window.CursorLab = CursorLab; } })(typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : this);