UNPKG

@lostisworld/svelte-interactive-cursor

Version:

A Svelte component for creating interactive custom cursors. Enhance user experience with customizable cursor effects and animations.

110 lines (109 loc) 4.43 kB
const interactiveCursor = (cursor, options) => { // set default cursor options const { defaultSize, scaleOnActive = [], duration, useDataElementRect = [] } = options; // set initial state const state = $state({ pointerCoords: { x: 0, y: 0 }, isActive: false, isHoveringDataElementRect: false, activeDataElement: null, activeDataName: '', dataElementRect: null }); let currentAnimation; const triggerAreas = document.querySelectorAll('[data-interactive-cursor-area]'); const animateCursor = (target) => { if (target.closest('[data-interactive-cursor]')) { state.activeDataElement = target.closest('[data-interactive-cursor]'); state.activeDataName = state.activeDataElement.getAttribute('data-interactive-cursor') || ''; state.isHoveringDataElementRect = state.activeDataName !== '' && useDataElementRect.includes(state.activeDataName); state.dataElementRect = state.activeDataElement.getBoundingClientRect(); } else { state.activeDataElement = null; state.activeDataName = ''; state.isHoveringDataElementRect = false; } // Get cursor element and set animation options const animationKeyframes = () => { if (state.isHoveringDataElementRect) { return { width: `${state.dataElementRect.width}px`, height: `${state.dataElementRect.height}px`, transform: `translate3D(${state.dataElementRect.left}px, ${state.dataElementRect.top}px, 0) scale3D(1,1,1)` }; } if (scaleOnActive.find((key) => key.element === state.activeDataName)) { // get the active size multiplicator const getActiveSizeMultiplicator = scaleOnActive.find((key) => key.element === state.activeDataName)?.scaleMultiplicator; return { width: `${defaultSize}px`, height: `${defaultSize}px`, transform: `translate3D(${state.pointerCoords.x}px, ${state.pointerCoords.y}px, 0) scale3D(${getActiveSizeMultiplicator ?? 3}, ${getActiveSizeMultiplicator ?? 3}, 1)` }; } return { width: `${defaultSize}px`, height: `${defaultSize}px`, transform: `translate3D(${state.pointerCoords.x}px, ${state.pointerCoords.y}px, 0) scale3D(1,1,1)` }; }; const animationTiming = { duration: duration, fill: 'forwards' }; // animate cursor currentAnimation = cursor.animate(animationKeyframes(), animationTiming); }; // start cursor tracking const startCursorTracking = (event) => { const { clientX, clientY, target } = event; state.pointerCoords = { x: clientX - cursor.offsetWidth / 2, y: clientY - cursor.offsetHeight / 2 }; state.isActive = true; // Get the active data element animateCursor(target); }; // stop cursor tracking const stopCursorTracking = () => { state.pointerCoords = { x: 0, y: 0 }; state.isActive = false; state.activeDataElement = null; state.activeDataName = ''; state.isHoveringDataElementRect = false; currentAnimation?.cancel(); }; // setup event listeners const init = () => { triggerAreas.forEach((triggerArea) => { triggerArea.addEventListener('mousemove', startCursorTracking, { passive: true }); triggerArea.addEventListener('mouseleave', stopCursorTracking); }); }; // cleanup event listeners const cleanup = () => { triggerAreas.forEach((triggerArea) => { triggerArea.removeEventListener('mousemove', startCursorTracking); triggerArea.removeEventListener('mouseleave', stopCursorTracking); }); }; return { get isActive() { return state.isActive; }, get activeDataValue() { return { activeDataName: state.activeDataName, activeDataElement: state.activeDataElement }; }, init, destroy() { cleanup(); } }; }; export { interactiveCursor };