Slightly improve scroll behaviour

- Scrolling can snap the section to the screen
This commit is contained in:
Candifloss 2025-11-29 22:25:55 +05:30
parent dbab8f5770
commit 9ead073ba3
4 changed files with 122 additions and 1 deletions

View File

@ -12,12 +12,14 @@ html, body {
font-family: "IosevkaTermSlab Nerd Font Mono", monospace; font-family: "IosevkaTermSlab Nerd Font Mono", monospace;
background: #202020; background: #202020;
color: #e0e0e0; color: #e0e0e0;
scroll-behavior: smooth;
} }
.sections { .sections {
height: 100vh; height: 100vh;
overflow-y: auto; overflow-y: auto;
scroll-snap-type: y mandatory; scroll-snap-type: y mandatory;
scroll-snap-stop: always;
} }
/* Each section is exactly 90vh tall. */ /* Each section is exactly 90vh tall. */

View File

@ -60,6 +60,6 @@
</section> </section>
</main> </main>
<script src="../js/snap.js"></script>
</body> </body>
</html> </html>

117
js/snap.js Normal file
View File

@ -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);
})();

View File

@ -8,12 +8,14 @@ html, body {
font-family: "IosevkaTermSlab Nerd Font Mono", monospace; font-family: "IosevkaTermSlab Nerd Font Mono", monospace;
background: v.$bg1; background: v.$bg1;
color: v.$text; color: v.$text;
scroll-behavior: smooth;
} }
.sections { .sections {
height: 100vh; height: 100vh;
overflow-y: auto; overflow-y: auto;
scroll-snap-type: y mandatory; scroll-snap-type: y mandatory;
scroll-snap-stop: always;
} }
/* Each section is exactly 90vh tall. */ /* Each section is exactly 90vh tall. */