UNPKG

cursor-blob

Version:

Lightweight library for animated, interactive cursors using GSAP

77 lines (76 loc) 2.57 kB
import h from "gsap"; const c = 0.1, m = 485, u = 0.35, l = 180, p = Math.PI, S = 0.95, n = 2; class o { static gsap = h; cursor; cursorRim; cursorDot; duration; ease; pos = { x: 0, y: 0 }; vel = { x: 0, y: 0 }; animFrame = null; boundMove = this.setFromEvent.bind(this); rimX; rimY; rimRotate; rimScaleX; rimScaleY; dotX; dotY; static registerGSAP(t) { o.gsap = t; } static getScale(t, s) { const i = Math.hypot(t, s); return Math.min(i / m, u); } static getAngle(t, s) { return Math.atan2(s, t) * l / p; } constructor({ cursorEl: t, cursorRimEl: s, cursorDotEl: i, duration: e = S, ease: r = "expo.out" }) { this.cursor = t, this.cursorRim = s, this.cursorDot = i, this.duration = e, this.ease = r, this.setupQuickSetters(), window.addEventListener("pointermove", this.boundMove), this.animate(); } setupQuickSetters() { const t = o.gsap; this.rimX = t.quickSetter(this.cursorRim, "x", "px"), this.rimY = t.quickSetter(this.cursorRim, "y", "px"), this.rimRotate = t.quickSetter(this.cursorRim, "rotate", "deg"), this.rimScaleX = t.quickSetter(this.cursorRim, "scaleX"), this.rimScaleY = t.quickSetter(this.cursorRim, "scaleY"), this.dotX = t.quickSetter(this.cursorDot, "x", "px"), this.dotY = t.quickSetter(this.cursorDot, "y", "px"); } loop() { const { x: t, y: s } = this.pos, { x: i, y: e } = this.vel, r = o.getAngle(i, e), a = o.getScale(i, e); this.rimX(t), this.rimY(s), this.rimRotate(r), this.rimScaleX(1 + a), this.rimScaleY(1 - a), this.dotX(t + i * c), this.dotY(s + e * c); } animate = () => { this.loop(), this.animFrame = requestAnimationFrame(this.animate); }; setFromEvent(t) { const { clientX: s, clientY: i } = t, e = this.cursorRim.offsetWidth / n, r = this.cursorRim.offsetHeight / n; o.gsap.killTweensOf(this.pos), o.gsap.to(this.pos, { x: s - e, y: i - r, duration: this.duration, ease: this.ease, onUpdate: () => { this.vel.x = s - e - this.pos.x, this.vel.y = i - r - this.pos.y, this.applyStyle(t); } }); } applyStyle(t) { let s = t.target; for (; s && !s.dataset?.cursorStyle && s !== document.body; ) s = s.parentElement; const i = s?.dataset?.cursorStyle ?? "default"; this.cursor.className = `cursor cursor--${i}`; } destroy() { window.removeEventListener("pointermove", this.boundMove), o.gsap.killTweensOf(this.pos), this.animFrame !== null && cancelAnimationFrame(this.animFrame); } } export { o as default };