Skip to content

Commit

Permalink
chore: More cleanup for cursor exp
Browse files Browse the repository at this point in the history
  • Loading branch information
kiosion committed Mar 6, 2024
1 parent afed4e7 commit 53a5837
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 89 deletions.
10 changes: 6 additions & 4 deletions svelte-app/src/components/experiments/mag-cursor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,17 @@
const buttons = ['Button One', 'Button Two', 'Button Three'];
onMount(() => (containerRect = container?.getBoundingClientRect()));
const updateRect = () => (containerRect = container?.getBoundingClientRect());
onMount(updateRect);
</script>

<svelte:window on:resize={() => (containerRect = container?.getBoundingClientRect())} />
<svelte:window on:resize={updateRect} on:scroll={updateRect} />

<!-- svelte-ignore a11y-mouse-events-have-key-events -->
<article
class="relative my-6 block cursor-none rounded-lg bg-dark/5 px-7 py-6 shadow-2xl shadow-dark/5 transition-shadow focus-within:shadow-dark/10 hover:shadow-dark/10 dark:bg-light/5 dark:shadow-light/5"
on:mouseover={() => (containerRect = container?.getBoundingClientRect())}
on:mouseover={updateRect}
bind:this={container}
>
<header class="w-full pb-2">
Expand All @@ -30,7 +32,7 @@
<Divider />
</header>

<CursorTarget distance={38} let:active let:offset>
<CursorTarget distance={40} let:active let:offset>
<Link
href="{BASE_GIT_URL}/blob/main/svelte-app/src/components/experiments/mag-cursor.svelte"
class="focus-outline absolute right-4 top-4 z-10 cursor-none rounded-sm p-2"
Expand Down
182 changes: 98 additions & 84 deletions svelte-app/src/components/experiments/mag-cursor/cursor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,49 +3,60 @@
import { tweened } from 'svelte/motion';
import { get } from 'svelte/store';
import { activeTarget, cursorTargets } from '$components/experiments/mag-cursor/utils';
import {
activeTarget,
BASE_CURSOR_INNER_SIZE,
BASE_CURSOR_SIZE,
BASE_TWEEN_MS,
BASE_TWEEN_MS_FAST,
cursorTargets,
findDistance
} from '$components/experiments/mag-cursor/utils';
import type { CursorTarget } from '$components/experiments/mag-cursor/utils';
export let containerRect: DOMRect | undefined = undefined;
const size = 34,
innerSize = 4,
_lastPosition = { clientX: 0, clientY: 0 };
let cursor = { x: 0, y: 0, width: size, height: size, borderRadius: size / 2 },
innerCursor = {
x: 0,
y: 0,
width: innerSize,
height: innerSize,
borderRadius: innerSize / 2
},
mouseDown = false,
hide = true;
const cursorTween = tweened(cursor, {
duration: 275,
easing: cubicOut
}),
innerCursorTween = tweened(innerCursor, {
duration: 125,
easing: cubicOut
});
const findDistance = ({ x, y }: { x: number; y: number }, rect: DOMRect) => {
const hd = x < rect.left ? rect.left - x : x > rect.right ? x - rect.right : 0,
vd = y < rect.top ? rect.top - y : y > rect.bottom ? y - rect.bottom : 0;
return Math.max(hd, vd);
};
let mouseDown = false,
hidden = true;
const _lastPosition = { clientX: 0, clientY: 0 },
cursor = tweened(
{
x: 0,
y: 0,
width: BASE_CURSOR_SIZE,
height: BASE_CURSOR_SIZE,
borderRadius: BASE_CURSOR_SIZE / 2
},
{
duration: BASE_TWEEN_MS,
easing: cubicOut
}
),
innerCursor = tweened(
{
x: 0,
y: 0,
width: BASE_CURSOR_INNER_SIZE,
height: BASE_CURSOR_INNER_SIZE,
borderRadius: BASE_CURSOR_INNER_SIZE / 2
},
{
duration: BASE_TWEEN_MS_FAST,
easing: cubicOut
}
);
const updateCursorPosition =
const update =
(targets: CursorTarget[]) => (e?: { clientX: number; clientY: number }) => {
const { clientX: x, clientY: y } = e ?? _lastPosition;
const x = e?.clientX || _lastPosition.clientX,
y = e?.clientY || _lastPosition.clientY;
_lastPosition.clientX = x;
_lastPosition.clientY = y;
if (typeof x === 'number' && typeof y === 'number') {
_lastPosition.clientX = x;
_lastPosition.clientY = y;
}
if (
containerRect &&
Expand All @@ -61,9 +72,8 @@
let closestDistance = Infinity,
closestTarget: CursorTarget | undefined = undefined;
for (let i = 0; i < targets.length; ++i) {
const target = targets[i],
rect = target.element?.getBoundingClientRect();
for (const target of targets) {
const rect = target.element?.getBoundingClientRect();
if (!rect) {
continue;
Expand All @@ -77,27 +87,27 @@
}
}
if (hide) {
if (hidden) {
setVis(true);
}
if (closestTarget === undefined || closestDistance >= closestTarget.snapDist) {
if (!closestTarget || closestDistance >= closestTarget.snapDist) {
get(activeTarget)?.offset?.set([0, 0]);
activeTarget.set(undefined);
cursorTween.set({
cursor.set({
x,
y,
width: size * (mouseDown ? 0.9 : 1),
height: size * (mouseDown ? 0.9 : 1),
borderRadius: size * (mouseDown ? 0.9 : 1)
width: BASE_CURSOR_SIZE * (mouseDown ? 0.9 : 1),
height: BASE_CURSOR_SIZE * (mouseDown ? 0.9 : 1),
borderRadius: (BASE_CURSOR_SIZE * (mouseDown ? 0.9 : 1)) / 2
});
innerCursorTween.set({
innerCursor.set({
x,
y,
width: innerSize * (mouseDown ? 3 : 1),
height: innerSize * (mouseDown ? 3 : 1),
borderRadius: innerSize * (mouseDown ? 3 : 1)
width: BASE_CURSOR_INNER_SIZE * (mouseDown ? 3 : 1),
height: BASE_CURSOR_INNER_SIZE * (mouseDown ? 3 : 1),
borderRadius: (BASE_CURSOR_INNER_SIZE * (mouseDown ? 3 : 1)) / 2
});
return;
Expand All @@ -106,9 +116,13 @@
const rect = closestTarget.element?.getBoundingClientRect();
// this fucking sucks but idrc for this proj
targets
.filter((t) => t.id !== closestTarget?.id)
.forEach((t) => t.offset?.set([0, 0]));
for (const target of targets) {
if (target.id === closestTarget.id) {
continue;
}
target.offset?.set([0, 0]);
}
if (!rect) {
return;
Expand All @@ -117,42 +131,42 @@
activeTarget.set(closestTarget);
if (closestTarget.snap !== true) {
cursorTween.set({
cursor.set({
x,
y,
width: size * (mouseDown ? 1.2 : 1.5),
height: size * (mouseDown ? 1.2 : 1.5),
borderRadius: size * (mouseDown ? 1.2 : 1.5)
width: BASE_CURSOR_SIZE * (mouseDown ? 1.2 : 1.5),
height: BASE_CURSOR_SIZE * (mouseDown ? 1.2 : 1.5),
borderRadius: (BASE_CURSOR_SIZE * (mouseDown ? 1.2 : 1.5)) / 2
});
innerCursorTween.set({
innerCursor.set({
x,
y,
width: innerSize * (mouseDown ? 3 : 2),
height: innerSize * (mouseDown ? 3 : 2),
borderRadius: innerSize * (mouseDown ? 3 : 2)
width: BASE_CURSOR_INNER_SIZE * (mouseDown ? 3 : 2),
height: BASE_CURSOR_INNER_SIZE * (mouseDown ? 3 : 2),
borderRadius: (BASE_CURSOR_INNER_SIZE * (mouseDown ? 3 : 2)) / 2
});
return;
}
cursorTween.set({
x: rect.left + rect.width / 2 + (x - (rect.left + rect.width / 2)) * 0.14,
y: rect.top + rect.height / 2 + (y - (rect.top + rect.height / 2)) * 0.14,
const rectCenter = [rect.left + rect.width / 2, rect.top + rect.height / 2];
cursor.set({
x: rectCenter[0] + (x - rectCenter[0]) * 0.14,
y: rectCenter[1] + (y - rectCenter[1]) * 0.14,
width: rect.width + closestTarget.size + (mouseDown ? 8 : 0),
height: rect.height + closestTarget.size * 0.5 + (mouseDown ? 8 : 0),
borderRadius: 8 * (mouseDown ? 2 : 1)
});
closestTarget.offset?.set([
(x - (rect.left + rect.width / 2)) * 0.075,
(y - (rect.top + rect.height / 2)) * 0.075
]);
// offset should be exponential - i.e., the further the x,y from the center of the target, the more the offset
closestTarget.offset?.set([(x - rectCenter[0]) * 0.08, (y - rectCenter[1]) * 0.08]);
innerCursorTween.set({
innerCursor.set({
x,
y,
width: innerSize * (mouseDown ? 2 : 3),
height: innerSize * (mouseDown ? 2 : 3),
borderRadius: innerSize * (mouseDown ? 1 : 3)
width: BASE_CURSOR_INNER_SIZE * (mouseDown ? 2 : 4),
height: BASE_CURSOR_INNER_SIZE * (mouseDown ? 2 : 4),
borderRadius: (BASE_CURSOR_INNER_SIZE * (mouseDown ? 2 : 4)) / 2
});
};
Expand All @@ -166,36 +180,36 @@
target?.element?.click();
}
updateCursorPosition(targets)(e);
update(targets)(e);
};
const setVis = (visible: boolean) => {
hide = !visible;
hidden = !visible;
!visible && get(activeTarget)?.offset?.set([0, 0]);
!visible && activeTarget.set(undefined);
};
$: updateCursorPosition($cursorTargets)();
$: update($cursorTargets)();
</script>

<svelte:window
on:mousemove={updateCursorPosition}
on:mousemove={updateCursorPosition($cursorTargets)}
on:mousemove={update($cursorTargets)}
on:mousedown={handleClick}
on:mouseup={handleClick}
on:scroll={update($cursorTargets)}
/>

{#if !hide}
{#if !hidden}
<div
class="pointer-events-none fixed left-0 top-0 z-10 shadow-lg {$activeTarget !==
undefined
? 'bg-dark/10 shadow-dark/5 dark:bg-light/10 dark:shadow-light/5'
: 'bg-dark/15 shadow-transparent dark:bg-light/15'} transition-colors"
style={`
transform: translate(calc(${$cursorTween.x}px - 50%), calc(${$cursorTween.y}px - 50%));
width: ${$cursorTween.width}px;
height: ${$cursorTween.height}px;
border-radius: ${$cursorTween.borderRadius}px;
transform: translate(calc(${$cursor.x}px - 50%), calc(${$cursor.y}px - 50%));
width: ${$cursor.width}px;
height: ${$cursor.height}px;
border-radius: ${$cursor.borderRadius}px;
`}
/>
<div
Expand All @@ -204,10 +218,10 @@ border-radius: ${$cursorTween.borderRadius}px;
? 'bg-dark/20 dark:bg-light/20'
: 'bg-dark/40 dark:bg-light/40'}"
style={`
transform: translate(calc(${$innerCursorTween.x}px - 50%), calc(${$innerCursorTween.y}px - 50%));
width: ${$innerCursorTween.width}px;
height: ${$innerCursorTween.height}px;
border-radius: ${$innerCursorTween.borderRadius}px;
transform: translate(calc(${$innerCursor.x}px - 50%), calc(${$innerCursor.y}px - 50%));
width: ${$innerCursor.width}px;
height: ${$innerCursor.height}px;
border-radius: ${$innerCursor.borderRadius}px;
`}
/>
{/if}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import {
activeTarget,
BASE_TWEEN_MS,
cursorTargets,
DEFAULT_NO_SNAP_DIST,
DEFAULT_SNAP_DIST,
Expand All @@ -29,7 +30,7 @@
snapDist: distance,
size,
offset: tweened([0, 0], {
duration: 275,
duration: BASE_TWEEN_MS,
easing: cubicOut
})
}
Expand Down
13 changes: 13 additions & 0 deletions svelte-app/src/components/experiments/mag-cursor/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,15 @@ export type CursorTarget = {
size: number;
};

export const BASE_CURSOR_SIZE = 36;
export const BASE_CURSOR_INNER_SIZE = 4;

export const DEFAULT_SNAP_DIST = 52;
export const DEFAULT_NO_SNAP_DIST = 20;

export const BASE_TWEEN_MS = 250;
export const BASE_TWEEN_MS_FAST = 125;

export const activeTarget = writable<CursorTarget | undefined>(undefined);
export const cursorTargets = writable<CursorTarget[]>([]);

Expand All @@ -38,3 +44,10 @@ export const getFirstPositionedChild = (el: HTMLElement): HTMLElement | undefine

return firstChild;
};

export const findDistance = ({ x, y }: { x: number; y: number }, rect: DOMRect) => {
const hd = x < rect.left ? rect.left - x : x > rect.right ? x - rect.right : 0,
vd = y < rect.top ? rect.top - y : y > rect.bottom ? y - rect.bottom : 0;

return Math.max(hd, vd);
};
4 changes: 4 additions & 0 deletions svelte-app/src/components/layouts/scroll-container.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
role="none"
data-test-id="scroll-container"
bind:this={element}
on:scroll={(e) => {
// dispatch what looks like a scroll event to the window
window.dispatchEvent(new CustomEvent('scroll', { detail: e }));
}}
>
<slot {element} />
</div>

0 comments on commit 53a5837

Please sign in to comment.