import { gsap } from 'gsap';
import { lerp, getCursorPos } from './utils';
// Track the cursor position
let cursor = {x: 0, y: 0};
window.addEventListener('mousemove', ev => cursor = getCursorPos(ev));
/**
* Class representing a custom Cursor.
* The cursor has the following HTML syntax:
*
*
*/
export class Cursor {
// DOM elements
DOM = {
// Main element (.cursor)
el: null,
}
// Properties that will change
renderedStyles = {
// With interpolation, we can achieve a smooth animation effect when moving the cursor.
// The "previous" and "current" values are the values that will interpolate.
// The returned value will be one between these two (previous and current) at a specific increment.
// The "amt" is the amount to interpolate.
// As an example, the following formula calculates the returned translationX value to apply:
// this.renderedStyles.tx.previous = lerp(this.renderedStyles.tx.previous, this.renderedStyles.tx.current, this.renderedStyles.tx.amt);
// Cursor translation in the x-axis
tx: {previous: 0, current: 0, amt: 0.4},
// Cursor translation in the y-axis
ty: {previous: 0, current: 0, amt: 0.4},
// Scale up the cursor
scale: {previous: 1, current: 1, amt: 0.2},
// Fade out the cursor
opacity: {previous: 1, current: 1, amt: 0.3}
};
// Size and position
bounds;
/**
* Constructor.
* @param {Element} DOM_el - The .cursor element
* @param {String} triggerSelector - Selector for all the elements that when hovered trigger the cursor enter/leave methods. Default is all .
*/
constructor(DOM_el, triggerSelector = 'a') {
this.DOM = {el: DOM_el};
// Hide initially
this.DOM.el.style.opacity = 0;
// Calculate size and position
this.bounds = this.DOM.el.getBoundingClientRect();
// Mousemove event:
// Start tracking the cursor position as soon as the user moves the cursor and fede in the cursor element.
this.onMouseMoveEv = () => {
// Set up the initial values to be the same
this.renderedStyles.tx.previous = this.renderedStyles.tx.current = cursor.x - this.bounds.width/2;
this.renderedStyles.ty.previous = this.renderedStyles.ty.previous = cursor.y - this.bounds.height/2;
// Fade in
gsap.to(this.DOM.el, {duration: 0.9, ease: 'Power3.easeOut', opacity: 1});
// Start loop
requestAnimationFrame(() => this.render());
// Remove the mousemove event
window.removeEventListener('mousemove', this.onMouseMoveEv);
};
window.addEventListener('mousemove', this.onMouseMoveEv);
[...document.querySelectorAll(triggerSelector)].forEach(link => {
link.addEventListener('mouseenter', () => this.enter());
link.addEventListener('mouseleave', () => this.leave());
});
}
/**
* Mouseenter event
* Scale up and fade out.
*/
enter() {
this.renderedStyles['scale'].current = 2;
this.renderedStyles['opacity'].current = 0.8;
}
/**
* Mouseleave event
* Reset scale and opacity.
*/
leave() {
this.renderedStyles['scale'].current = 1;
this.renderedStyles['opacity'].current = 1;
}
/**
* Shows the cursor
*/
show() {
this.renderedStyles['opacity'].current = 1;
}
/**
* Hides the cursor
*/
hide() {
this.renderedStyles['opacity'].current = 0;
}
/**
* Loop
*/
render() {
// New cursor positions
this.renderedStyles['tx'].current = cursor.x - this.bounds.width/2;
this.renderedStyles['ty'].current = cursor.y - this.bounds.height/2;
// Interpolation
for (const key in this.renderedStyles ) {
this.renderedStyles[key].previous = lerp(this.renderedStyles[key].previous, this.renderedStyles[key].current, this.renderedStyles[key].amt);
}
// Apply interpolated values (smooth effect)
this.DOM.el.style.transform = `translateX(${(this.renderedStyles['tx'].previous)}px) translateY(${this.renderedStyles['ty'].previous}px) scale(${this.renderedStyles['scale'].previous})`;
this.DOM.el.style.opacity = this.renderedStyles['opacity'].previous;
// loop...
requestAnimationFrame(() => this.render());
}
}