Slightly improve scroll behaviour
- Scrolling can snap the section to the screen
This commit is contained in:
parent
dbab8f5770
commit
9ead073ba3
@ -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. */
|
||||
|
||||
@ -60,6 +60,6 @@
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<script src="../js/snap.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
117
js/snap.js
Normal file
117
js/snap.js
Normal 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);
|
||||
|
||||
})();
|
||||
@ -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. */
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user