cursor-blob
Version:
Lightweight library for animated, interactive cursors using GSAP
77 lines (76 loc) • 2.57 kB
JavaScript
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
};