proteuscursor
Version:
Proteus Cursor is a dynamic JavaScript library that revolutionizes web user interaction by transforming the mouse cursor based on HTML element interactions. Inspired by the shape-shifting god Proteus, this library allows the cursor to change into various
43 lines (41 loc) • 13 kB
JavaScript
(function(l,u){typeof exports=="object"&&typeof module<"u"?module.exports=u():typeof define=="function"&&define.amd?define(u):(l=typeof globalThis<"u"?globalThis:l||self,l.ProteusCursor=u())})(this,function(){"use strict";/*!
* Proteus Cursor v1.1.5
* https://github.com/Shuriken933/proteus-cursor
*
* A dynamic JavaScript library that transforms the default mouse cursor
* into interactive shapes based on HTML element interactions.
* Inspired by Proteus, the Greek god of change, this library provides
* a flexible way to customize the user’s pointer experience on the web.
*
* Features:
* - Cursor shape customization (dot, circle, fluid, text, etc.)
* - Magnetic effects
* - Smooth shadow animations
* - Easy integration via ES module or browser script tag
*
* Author: Eros Agostini (https://github.com/Shuriken933)
* License: MIT
* Released: July 2025
*
* © 2025 Eros Agostini. All rights reserved.
*/class l{velocity=0;_x=0;_y=0;mouseX=0;mouseY=0;cursorX=0;cursorY=0;prevMouseX=0;prevMouseY=0;constructor(t={}){this.testMode=!1,this.shape=t.shape||"default",this.shape_size=t.shape_size||"10px",this.shape_color=t.shape_color||"#fff",this.hasShadow=t.hasShadow??!0,this.hasShadow?(this.shadow_delay=t.shadow_delay||"0.3s",this.shadow_size=t.shadow_size||"40px",this.shadow_color=t.shadow_color||"#ffffff"):(this.shadow_delay="0s",document.querySelector(".proteus-cursor-shadow").style.display="none"),this.text="",this.text_color="",this.text_weight="",this.text_size="",this.speed=.9,this.maxVelocity=10,this.isMagnetic=!1,this.eventListeners=[],this.animationIds=[],this.intervals=[],this.timeouts=[],this.isDestroyed=!1,this.boundMouseMove=this.handleMouseMove.bind(this),this.boundMouseEnter=this.handleMouseEnter.bind(this),this.boundMouseLeave=this.handleMouseLeave.bind(this),this.boundAnimateCircle=this.animateCircleShadow.bind(this),this.boundAnimateFluid=this.animateFluidCursor.bind(this),this.init(),this.dataAttributeEvents()}init(){this.init_HTMLcursorAndShadow(),this.$shape=document.getElementById("proteus-cursor-shape"),this.$shadow=document.getElementById("proteus-cursor-shadow"),this.$shape.style.width=this.shape_size||"20px",this.$shape.style.height=this.shape_size||"20px",this.$shadow.style.width=this.shadow_size||"40px",this.$shadow.style.height=this.shadow_size||"40px",this.setShape(this.shape)}init_HTMLcursorAndShadow(){if(document.getElementById("proteus-cursor-shape"))return;const t=document.createElement("div");t.className="proteus-cursor-shape",t.id="proteus-cursor-shape";const e=document.createElement("div");e.className="proteus-cursor-shadow",e.id="proteus-cursor-shadow";const s=document.body;s.prepend(t),s.prepend(e)}addEventListenerTracked(t,e,s,i=!1){this.isDestroyed||(t.addEventListener(e,s,i),this.eventListeners.push({element:t,event:e,handler:s,options:i}))}requestAnimationFrameTracked(t){if(this.isDestroyed)return;const e=requestAnimationFrame(t);return this.animationIds.push(e),e}setShape(t){switch(document.querySelector("body").classList.remove("proteus-is-a-fluid"),document.querySelector("body").classList.remove("proteus-is-a-circle"),console.log("setShape executed"),this.shape=t,this.shape){case"default":break;case"circle":this.setShape__circle(this.shape);break;case"fluid":this.setShape__fluid();break}u(this.shape)}setShape__circle(t){this.delay=8,this._x=0,this._y=0,this.endX=window.innerWidth/2,this.endY=window.innerHeight/2,this.cursorVisible=!0,this.cursorEnlarged=!1,document.querySelector("body").classList.add("proteus-is-a-circle"),document.body.style.cursor="none",this.shape__circle__interactions(),this.shape__circle__animateShadow()}shape__circle__interactions(){this.isDestroyed||(document.querySelectorAll("a, button, input").forEach(t=>{this.addEventListenerTracked(t,"mouseover",this.boundMouseEnter),this.addEventListenerTracked(t,"mouseout",this.boundMouseLeave)}),this.addEventListenerTracked(document,"mousemove",this.boundMouseMove))}handleMouseMove(t){this.isDestroyed||(this.cursorVisible=!0,this.toggleCursorVisibility(),this.endX=t.pageX,this.endY=t.pageY,this.$shape&&(this.$shape.style.top=this.endY+"px",this.$shape.style.left=this.endX+"px"))}handleMouseEnter(){this.isDestroyed||(this.cursorEnlarged=!0,this.toggleCursorSize())}handleMouseLeave(){this.isDestroyed||(this.cursorEnlarged=!1,this.toggleCursorSize())}animateCircleShadow(){this.isDestroyed||(this._x+=(this.endX-this._x)/this.delay,this._y+=(this.endY-this._y)/this.delay,this.$shadow&&(this.$shadow.style.top=this._y+"px",this.$shadow.style.left=this._x+"px"),this.requestAnimationFrameTracked(this.boundAnimateCircle))}shape__circle__animateShadow(){this.animateCircleShadow()}toggleCursorSize(){this.cursorEnlarged?(this.$shape.style.transform="translate(-50%, -50%) scale(1.5)",this.$shadow.style.transform="translate(-50%, -50%) scale(1.5)"):(this.$shape.style.transform="translate(-50%, -50%) scale(1)",this.$shadow.style.transform="translate(-50%, -50%) scale(1)")}toggleCursorVisibility(){this.cursorVisible?(this.$shape.style.opacity=1,this.$shadow.style.opacity=1):(this.$shape.style.opacity=0,this.$shadow.style.opacity=0)}setShape__fluid__animateCursor__calcVelocity(){const t=e=>{if(this.isDestroyed)return;const s=e.clientX-this.prevMouseX,i=e.clientY-this.prevMouseY;this.velocity=Math.sqrt(s*s+i*i),this.prevMouseX=this.mouseX,this.prevMouseY=this.mouseY,this.mouseX=e.clientX,this.mouseY=e.clientY};this.addEventListenerTracked(document,"mousemove",t)}setShape__fluid__animateCursor(){if(this.isDestroyed)return;this.velocityInitialized||(this.setShape__fluid__animateCursor__calcVelocity(),this.velocityInitialized=!0),this.cursorX+=(this.mouseX-this.cursorX)*this.speed,this.cursorY+=(this.mouseY-this.cursorY)*this.speed;const t=Math.min(this.velocity/this.maxVelocity,1);if(t>.01){const e=this.mouseX-this.cursorX,s=this.mouseY-this.cursorY,i=Math.sqrt(e*e+s*s);if(i>0){const o=e/i,h=s/i,r=1+t*1.5,n=1-t*.3,f=o*o*(r-1)+1,m=o*h*(r-1),_=o*h*(r-1),b=h*h*(r-1)+1,d=-h,c=o,$=f+d*d*(n-1),w=m+d*c*(n-1),x=_+d*c*(n-1),S=b+c*c*(n-1);this.$shape&&(this.$shape.style.transform=`matrix(${$}, ${w}, ${x}, ${S}, 0, 0)`)}else this.$shape&&(this.$shape.style.transform="matrix(1, 0, 0, 1, 0, 0)")}else this.$shape&&(this.$shape.style.transform="matrix(1, 0, 0, 1, 0, 0)");this.$shape&&(this.$shape.style.left=this.cursorX-this.$shape.offsetWidth/2+"px",this.$shape.style.top=this.cursorY-this.$shape.offsetHeight/2+"px"),this.velocity*=.95,this.requestAnimationFrameTracked(this.boundAnimateFluid)}animateFluidCursor(){this.setShape__fluid__animateCursor()}setShape__fluid(){if(document.querySelector("body").classList.add("proteus-is-a-fluid"),document.body.style.cursor="none",!this.$shape){console.error("Elemento con id 'cursor' non trovato!");return}this.$shape.style.position="fixed",this.$shape.style.width=this.shape_size||"20px",this.$shape.style.height=this.shape_size||"20px",this.$shape.style.backgroundColor=this.shape_color||"#fff",this.$shape.style.borderRadius="50%",this.$shape.style.pointerEvents="none",this.$shape.style.zIndex="9999",this.$shape.style.transition="all 0.3s cubic-bezier(0.23, 1, 0.320, 1)",this.hasShadow&&(this.$shape.style.boxShadow=`0 0 ${this.shadow_size} ${this.shadow_color}`),this.velocityInitialized=!1,this.cursorX=window.innerWidth/2,this.cursorY=window.innerHeight/2,this.setShape__fluid__animateCursor()}destroy(){console.log("🔴 Destroying ProteusCursor instance..."),this.isDestroyed=!0,this.animationIds.forEach(e=>{cancelAnimationFrame(e)}),this.animationIds=[],this.intervals.forEach(e=>clearInterval(e)),this.timeouts.forEach(e=>clearTimeout(e)),this.intervals=[],this.timeouts=[],this.eventListeners.forEach(({element:e,event:s,handler:i,options:o})=>{try{e.removeEventListener(s,i,o)}catch(h){console.warn("Error removing event listener:",h)}}),this.eventListeners=[],document.body.style.cursor="";const t=document.querySelector("body");t&&(t.classList.remove("proteus-is-a-fluid"),t.classList.remove("proteus-is-a-circle")),this.$shape&&(this.$shape.style.cssText="",this.$shape.style.display="none",this.$shape.style.opacity="0",this.$shape.style.transform="",this.$shape.style.left="",this.$shape.style.top="",this.$shape.style.width="",this.$shape.style.height="",this.$shape.style.backgroundColor="",this.$shape.style.borderRadius="",this.$shape.style.boxShadow="",this.$shape.textContent=""),this.$shadow&&(this.$shadow.style.cssText="",this.$shadow.style.display="none",this.$shadow.style.opacity="0",this.$shadow.style.transform="",this.$shadow.style.left="",this.$shadow.style.top="",this.$shadow.style.width="",this.$shadow.style.height="",this.$shadow.style.backgroundColor=""),this.$shape=null,this.$shadow=null,this.boundMouseMove=null,this.boundMouseEnter=null,this.boundMouseLeave=null,this.boundAnimateCircle=null,this.boundAnimateFluid=null,this.velocity=0,this._x=0,this._y=0,this.mouseX=0,this.mouseY=0,this.cursorX=0,this.cursorY=0,this.prevMouseX=0,this.prevMouseY=0,this.velocityInitialized=!1,console.log("✅ ProteusCursor instance completely destroyed")}setShapeSize(t,e,s=!1){console.log("setShapeSize executed"),y(this),s?(this.shape_size=t||"20px",this.shadow_size=e||"20px",this.$shape.style.width=t||"20px",this.$shape.style.height=e||"20px"):(this.$shape.style.width=t||"20px",this.$shape.style.height=e||"20px")}setShapeColor(t,e=!1){e?(this.shape_color=t,this.$shape.style.backgroundColor=t):this.$shape.style.backgroundColor=t}setShadowEnabled(t,e=!1){this.shape==="circle"?e?(this.hasShadow=t,this.hasShadow?this.$shadow.style.display="block":this.$shadow.style.display="none"):t?this.$shadow.style.display="block":this.$shadow.style.display="none":this.shape==="fluid"&&e&&(this.$shape.style.boxShadow=`0 0 ${this.shadow_size} ${this.shadow_color}`)}setShadowSize(t,e){this.$shadow.style.width=t||"20px",this.$shadow.style.height=e||"20px"}setShadowColor(t,e=.5){const s=p(t,e);this.$shadow.style.backgroundColor=s}setText(t,e=!1){e?(this.text=t,document.querySelector(".proteus-cursor-shape").textContent=this.text):document.querySelector(".proteus-cursor-shape").textContent=t}setTextColor(t,e=!1){e&&(this.text_color=t),document.querySelector(".proteus-cursor-shape").style.color=t}setTextWeight(t,e=!1){e&&(this.text_weight=t),document.querySelector(".proteus-cursor-shape").style.fontWeight=t}setTextSize(t,e=!1){e&&(this.text_size=t),document.querySelector(".proteus-cursor-shape").style.fontSize=t}setSpeed(t){this.speed=t}setMaxVelocity(t){this.maxVelocity=t}dataAttributeEvents(){document.querySelectorAll("[data-proteus-shapeSize], [data-proteus-shapeColor], [data-proteus-text], [data-proteus-textColor], [data-proteus-textSize], [data-proteus-textWeight]").forEach(t=>{t.addEventListener("mouseenter",()=>{const e=t.getAttribute("data-proteus-shapeSize"),s=t.getAttribute("data-proteus-shapeColor"),i=t.getAttribute("data-proteus-text"),o=t.getAttribute("data-proteus-textColor"),h=t.getAttribute("data-proteus-textSize"),r=t.getAttribute("data-proteus-textWeight"),n=t.getAttribute("data-proteus-shadowIsEnabled");e&&this.setShapeSize(e,e),s&&this.setShapeColor(s),i&&this.setText(i),o&&this.setTextColor(o),h&&this.setTextSize(h),r&&this.setTextWeight(r),n&&this.setShadowEnabled(!0)}),t.addEventListener("mouseleave",()=>{this.setShapeSize(this.shape_size,this.shape_size),this.setShapeColor(this.shape_color),this.setText(this.text),this.setTextColor(this.text_color),this.setTextSize(this.text_size),this.setTextWeight(this.text_weight)})})}enableTestMode(){this.testMode=!0,this.enableTestMode__generateHTML();const t=document.querySelector("#proteus-panel-test"),e=document.querySelector("#proteus-button-test");e.addEventListener("click",()=>{t.classList.toggle("open")}),document.querySelector("#button-setShape-dot"),document.querySelector("#button-setShape-circle"),e.classList.add("active")}enableTestMode__generateHTML(){const t=document.querySelector("body");t.insertAdjacentHTML("beforeend",`
<button id="proteus-button-test">
<img src="icons/icon-cursor.svg" alt="icon" width="35" height="35" />
</button>
`),t.insertAdjacentHTML("beforeend",`
<div id="proteus-panel-test">
<p>Type cursor</p>
<ul>
<li><button id="button-setShape-circle">circle</button></li>
<li><button id="button-setShape-dot">dot</button></li>
<li><button></button></li>
<li><button></button></li>
</ul>
<p>Modifiers</p>
<ul>
<li><button>magnetic</button></li>
<li><button>parallax hover</button></li>
<li><button>text</button></li>
</ul>
</div>
`)}disableTestMode(){this.testMode=!1,document.querySelector("#proteus-button-test").classList.remove("active")}}function u(a){console.log("This is the type: ",a)}function p(a,t=1){const e=parseInt(a.slice(1,3),16),s=parseInt(a.slice(3,5),16),i=parseInt(a.slice(5,7),16);return`rgba(${e}, ${s}, ${i}, ${t})`}function y(a){console.log(a)}return l});