Compare commits
2 Commits
f7fff7748a
...
afdd74175a
| Author | SHA1 | Date | |
|---|---|---|---|
|
afdd74175a
|
|||
|
6f8043d2d0
|
@@ -7,17 +7,26 @@
|
|||||||
|
|
||||||
let { smoothCursor = true }: { smoothCursor?: boolean } = $props();
|
let { smoothCursor = true }: { smoothCursor?: boolean } = $props();
|
||||||
|
|
||||||
|
const VELOCITY_WINDOW_MS = 16;
|
||||||
|
const SPEED_THRESHOLD = 9;
|
||||||
|
const REQUIRED_CONSECUTIVE = 3;
|
||||||
|
|
||||||
let mouseX = $state(0);
|
let mouseX = $state(0);
|
||||||
let mouseY = $state(0);
|
let mouseY = $state(0);
|
||||||
let lastMouseX = $state(0);
|
let lastMouseX = 0;
|
||||||
let lastMouseY = $state(0);
|
let lastMouseY = 0;
|
||||||
let isMouseDown = $state(false);
|
let isMouseDown = $state(false);
|
||||||
let isHoveringInteractive = $state(false);
|
let isHoveringInteractive = $state(false);
|
||||||
let dragStartX = $state(0);
|
let dragStartX = 0;
|
||||||
let dragStartY = $state(0);
|
let dragStartY = 0;
|
||||||
let degrees = $state(0);
|
let degrees = $state(0);
|
||||||
let applyRotation = $state(false);
|
let applyRotation = $state(false);
|
||||||
|
|
||||||
|
let lastVelocityTime = 0;
|
||||||
|
let consecutiveHighSpeed = 0;
|
||||||
|
let velocityAccumX = 0;
|
||||||
|
let velocityAccumY = 0;
|
||||||
|
|
||||||
function isInteractive(el: Element | null): boolean {
|
function isInteractive(el: Element | null): boolean {
|
||||||
while (el) {
|
while (el) {
|
||||||
const tag = el.tagName.toLowerCase();
|
const tag = el.tagName.toLowerCase();
|
||||||
@@ -30,7 +39,6 @@
|
|||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
el = el.parentElement;
|
el = el.parentElement;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -43,17 +51,32 @@
|
|||||||
const deltaX = e.pageX - window.pageXOffset - dragStartX;
|
const deltaX = e.pageX - window.pageXOffset - dragStartX;
|
||||||
const deltaY = e.pageY - window.pageYOffset - dragStartY;
|
const deltaY = e.pageY - window.pageYOffset - dragStartY;
|
||||||
|
|
||||||
const velocityX = mouseX - lastMouseX;
|
velocityAccumX += mouseX - lastMouseX;
|
||||||
const velocityY = mouseY - lastMouseY;
|
velocityAccumY += mouseY - lastMouseY;
|
||||||
|
|
||||||
const dist = Math.sqrt(velocityX * velocityX + velocityY * velocityY);
|
const now = performance.now();
|
||||||
|
const dt = now - lastVelocityTime;
|
||||||
|
|
||||||
if (!applyRotation && isMouseDown && dist > 80) applyRotation = true;
|
if (dt >= VELOCITY_WINDOW_MS) {
|
||||||
|
const speed = Math.sqrt(velocityAccumX ** 2 + velocityAccumY ** 2) / dt;
|
||||||
|
|
||||||
|
if (speed > SPEED_THRESHOLD) {
|
||||||
|
consecutiveHighSpeed++;
|
||||||
|
if (!applyRotation && isMouseDown && consecutiveHighSpeed >= REQUIRED_CONSECUTIVE) {
|
||||||
|
applyRotation = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
consecutiveHighSpeed = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
velocityAccumX = 0;
|
||||||
|
velocityAccumY = 0;
|
||||||
|
lastVelocityTime = now;
|
||||||
|
}
|
||||||
|
|
||||||
let newDegrees = (Math.atan2(-deltaX, deltaY) * 180) / Math.PI + 24.3;
|
let newDegrees = (Math.atan2(-deltaX, deltaY) * 180) / Math.PI + 24.3;
|
||||||
|
|
||||||
let diff = (newDegrees - degrees) % 360;
|
let diff = (newDegrees - degrees) % 360;
|
||||||
|
|
||||||
if (diff < -180) diff += 360;
|
if (diff < -180) diff += 360;
|
||||||
if (diff > 180) diff -= 360;
|
if (diff > 180) diff -= 360;
|
||||||
|
|
||||||
@@ -67,26 +90,18 @@
|
|||||||
isHoveringInteractive = isInteractive(el);
|
isHoveringInteractive = isInteractive(el);
|
||||||
|
|
||||||
if (!isMouseDown) {
|
if (!isMouseDown) {
|
||||||
if (isHoveringInteractive) {
|
animate(cursorAdditive, {
|
||||||
animate(cursorAdditive, {
|
opacity: isHoveringInteractive ? 1 : 0,
|
||||||
opacity: 1,
|
duration: 800,
|
||||||
duration: 800,
|
ease: (t: number) => (t - 1) ** 5 + 1,
|
||||||
ease: (t: number) => (t - 1) ** 5 + 1,
|
});
|
||||||
});
|
|
||||||
} else {
|
|
||||||
animate(cursorAdditive, {
|
|
||||||
opacity: 0,
|
|
||||||
duration: 800,
|
|
||||||
ease: (t: number) => (t - 1) ** 5 + 1,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
animate(cursor, {
|
animate(cursor, {
|
||||||
duration: smoothCursor ? $cursorSmoothness : 0,
|
duration: smoothCursor ? $cursorSmoothness : 0,
|
||||||
translateX: mouseX,
|
translateX: mouseX,
|
||||||
translateY: mouseY,
|
translateY: mouseY,
|
||||||
ease: (t: number) => (t - 1) ** 5 + 1,
|
ease: (t: number) => t === 1 ? 1 : 1 - Math.pow(2, -10 * t),
|
||||||
});
|
});
|
||||||
|
|
||||||
animate(cursor, {
|
animate(cursor, {
|
||||||
@@ -107,6 +122,11 @@
|
|||||||
dragStartX = event.clientX;
|
dragStartX = event.clientX;
|
||||||
dragStartY = event.clientY;
|
dragStartY = event.clientY;
|
||||||
|
|
||||||
|
velocityAccumX = 0;
|
||||||
|
velocityAccumY = 0;
|
||||||
|
consecutiveHighSpeed = 0;
|
||||||
|
lastVelocityTime = performance.now();
|
||||||
|
|
||||||
isMouseDown = true;
|
isMouseDown = true;
|
||||||
animate(cursorInner, {
|
animate(cursorInner, {
|
||||||
scale: 0.9,
|
scale: 0.9,
|
||||||
@@ -124,6 +144,10 @@
|
|||||||
const handleMouseUp = () => {
|
const handleMouseUp = () => {
|
||||||
isMouseDown = false;
|
isMouseDown = false;
|
||||||
applyRotation = false;
|
applyRotation = false;
|
||||||
|
consecutiveHighSpeed = 0;
|
||||||
|
velocityAccumX = 0;
|
||||||
|
velocityAccumY = 0;
|
||||||
|
|
||||||
animate(cursorInner, {
|
animate(cursorInner, {
|
||||||
scale: 1,
|
scale: 1,
|
||||||
duration: 500,
|
duration: 500,
|
||||||
|
|||||||
+12
-10
@@ -1,19 +1,21 @@
|
|||||||
export function estimateRefreshRate(): Promise<number> {
|
export async function estimateFrameRate(): Promise<number> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const last = performance.now();
|
const samples: number[] = [];
|
||||||
let frames = 0;
|
let last = performance.now();
|
||||||
|
|
||||||
function loop() {
|
const tick = () => {
|
||||||
const now = performance.now();
|
const now = performance.now();
|
||||||
frames++;
|
samples.push(now - last);
|
||||||
|
last = now;
|
||||||
|
|
||||||
if (now - last >= 1000) {
|
if (samples.length < 60) {
|
||||||
resolve(frames - 2);
|
requestAnimationFrame(tick);
|
||||||
} else {
|
} else {
|
||||||
requestAnimationFrame(loop);
|
const avgDelta = samples.reduce((a, b) => a + b) / samples.length;
|
||||||
|
resolve(Math.round(1000 / avgDelta));
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
requestAnimationFrame(loop);
|
requestAnimationFrame(tick);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Logo from '$assets/logo.png';
|
import Logo from '$assets/logo.png';
|
||||||
import { estimateRefreshRate } from '@/displayUtils';
|
import { estimateFrameRate } from '@/displayUtils';
|
||||||
import {
|
import {
|
||||||
beatmapSets,
|
beatmapSets,
|
||||||
currentLoadingInfo,
|
currentLoadingInfo,
|
||||||
@@ -67,15 +67,15 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const calculateCursorSmoothness = async () => {
|
const calculateCursorSmoothness = async () => {
|
||||||
const refreshRate = await estimateRefreshRate();
|
const refreshRate = await estimateFrameRate();
|
||||||
const hzMin = 60;
|
const hzMin = 60;
|
||||||
const hzMax = 144;
|
const hzMax = 500;
|
||||||
const durationMin = 70;
|
const durationMin = 350;
|
||||||
const durationMax = 180;
|
const durationMax = 600;
|
||||||
|
const clamped = Math.min(Math.max(refreshRate, hzMin), hzMax);
|
||||||
const duration =
|
const duration =
|
||||||
durationMin + ((refreshRate - hzMin) / (hzMax - hzMin)) * (durationMax - durationMin);
|
durationMin + ((clamped - hzMin) / (hzMax - hzMin)) * (durationMax - durationMin);
|
||||||
|
console.log(Math.round(duration));
|
||||||
cursorSmoothness.set(Math.round(duration));
|
cursorSmoothness.set(Math.round(duration));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user