PartsiPadOS Cursor

iPad Cursor

Zero-dependency, component-based iPadOS style cursor, applicable to

Hover input below to see input cursor

Link back home

$lib/ipad/ipad.ts

import { cursorTarget } from './cursorStore';

function get__store(store: any) {
	let $val;
	store.subscribe(($: any) => ($val = $))();
	return $val;
}

export function ipad(element: HTMLElement, options?: any | null) {
	let move = options?.move;

	const transitionOnCancelSnap =
		'transform .4s cubic-bezier(0.175, 0.885, 0.32, 1.275), background-color 0.3s';
	const transitionOnSnap = 'transform .1s ease-out, background-color 0.8s ease-in-out';
	const damp = 0.15;

	const drag = (e: MouseEvent) => {
		if (!e.currentTarget) return;
		const target = e.currentTarget as HTMLElement;
		if (get__store(cursorTarget) !== target) {
			cursorTarget.set(target);
		}
		if (typeof move == 'undefined' || move == false) return;
		const rect = target.getBoundingClientRect();

		const x = e.clientX - (rect.left + rect.width / 2);
		const y = e.clientY - (rect.top + rect.height / 2);
		// console.log(x, y);
		const pos = {
			x: x * damp,
			y: y * damp
		};
		startSnap(target, pos);
	};

	const stop = (e: any) => {
		cancelSnap(e.currentTarget);
		cursorTarget.set(null);
	};

	function cancelSnap(element: HTMLElement) {
		element.style.transform = `translate3d(0, 0, 0)`;
		element.style.transition = transitionOnCancelSnap;
	}

	function startSnap(element: HTMLElement, position: any) {
		element.style.transform = `translate3d(${position.x}px, ${position.y}px, 0) scale(1.05)`;
		element.style.transition = transitionOnSnap;
	}

	element.addEventListener('mousemove', drag);
	element.addEventListener('mouseout', stop);

	return {
		update(options?: any | null) {
			move = options.move;
			// tooltip.innerHTML = options.content;
		},
		destroy() {
			// cleanup
		}
	};
}