From 9ead073ba30d2d268b7e8699b1eed33936a6911f Mon Sep 17 00:00:00 2001 From: Candifloss Date: Sat, 29 Nov 2025 22:25:55 +0530 Subject: [PATCH] Slightly improve scroll behaviour - Scrolling can snap the section to the screen --- css/about.css | 2 + html/about.html | 2 +- js/snap.js | 117 ++++++++++++++++++++++++++++++++++++++++++++++++ scss/about.scss | 2 + 4 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 js/snap.js diff --git a/css/about.css b/css/about.css index a4c819d..41ea68d 100644 --- a/css/about.css +++ b/css/about.css @@ -12,12 +12,14 @@ html, body { font-family: "IosevkaTermSlab Nerd Font Mono", monospace; background: #202020; color: #e0e0e0; + scroll-behavior: smooth; } .sections { height: 100vh; overflow-y: auto; scroll-snap-type: y mandatory; + scroll-snap-stop: always; } /* Each section is exactly 90vh tall. */ diff --git a/html/about.html b/html/about.html index 93a0bc4..59c9720 100644 --- a/html/about.html +++ b/html/about.html @@ -60,6 +60,6 @@ - + diff --git a/js/snap.js b/js/snap.js new file mode 100644 index 0000000..d05180a --- /dev/null +++ b/js/snap.js @@ -0,0 +1,117 @@ +// snap.js +// Full working gesture-based snap-scrolling with wheel, touch and keyboard. +// The trick: group wheel events into GESTURES, not deltas. + +(function () { + + const GESTURE_TIMEOUT = 180; // ms of silence means gesture finished + const ANIMATION_LOCK = 600; // ms lock to prevent double scrollIntoView + + let wheelTimeout = null; // end-of-gesture timer + let cumulativeDelta = 0; // total movement in current gesture + let animating = false; // lock while animation runs + + const sections = Array.from(document.querySelectorAll(".section")); + if (!sections.length) return; + + const ids = sections.map(s => s.id); + + function clamp(i) { + if (i < 0) return 0; + if (i >= ids.length) return ids.length - 1; + return i; + } + + function getCurrentIndex() { + let idx = 0; + let best = Infinity; + sections.forEach((sec, i) => { + const dist = Math.abs(sec.getBoundingClientRect().top); + if (dist < best) { + best = dist; + idx = i; + } + }); + return idx; + } + + function goToSection(index) { + index = clamp(index); + animating = true; + + const target = document.getElementById(ids[index]); + if (!target) return; + + target.scrollIntoView({ behavior: "smooth", block: "start" }); + history.replaceState(null, "", "#" + ids[index]); + + // unlock after animation ends + setTimeout(() => { animating = false }, ANIMATION_LOCK); + } + + // Wheel handling with gesture grouping + function handleWheel(e) { + e.preventDefault(); + if (animating) return; + + cumulativeDelta += e.deltaY; // keep accumulating in this gesture + + if (wheelTimeout) clearTimeout(wheelTimeout); + + // When wheel stops firing, gesture is complete + wheelTimeout = setTimeout(() => { + const current = getCurrentIndex(); + const direction = cumulativeDelta > 0 ? 1 : -1; + goToSection(current + direction); + + // reset gesture state + cumulativeDelta = 0; + wheelTimeout = null; + + }, GESTURE_TIMEOUT); + } + + // Touch gestures + let startY = 0; + + function handleTouchStart(e) { + startY = e.touches[0].clientY; + } + + function handleTouchEnd(e) { + if (animating) return; + + const endY = e.changedTouches[0].clientY; + const diff = startY - endY; + const threshold = 30; + + if (Math.abs(diff) < threshold) return; + + const current = getCurrentIndex(); + const direction = diff > 0 ? 1 : -1; + goToSection(current + direction); + } + + // Keyboard input + function handleKey(e) { + if (animating) return; + + const current = getCurrentIndex(); + if (e.key === "ArrowDown" || e.key === "PageDown") + goToSection(current + 1); + else if (e.key === "ArrowUp" || e.key === "PageUp") + goToSection(current - 1); + else if (e.key === "Home") + goToSection(0); + else if (e.key === "End") + goToSection(ids.length - 1); + } + + const container = document.querySelector(".sections") || document; + + container.addEventListener("wheel", handleWheel, { passive: false }); + container.addEventListener("touchstart", handleTouchStart, { passive: true }); + container.addEventListener("touchend", handleTouchEnd, { passive: true }); + window.addEventListener("keydown", handleKey); + +})(); diff --git a/scss/about.scss b/scss/about.scss index b430433..7d60fa3 100644 --- a/scss/about.scss +++ b/scss/about.scss @@ -8,12 +8,14 @@ html, body { font-family: "IosevkaTermSlab Nerd Font Mono", monospace; background: v.$bg1; color: v.$text; + scroll-behavior: smooth; } .sections { height: 100vh; overflow-y: auto; scroll-snap-type: y mandatory; + scroll-snap-stop: always; } /* Each section is exactly 90vh tall. */