Skip to content

Commit 5f2dd13

Browse files
committed
Feature detect the scroll logic.
Sigh.
1 parent 7444121 commit 5f2dd13

File tree

2 files changed

+62
-6
lines changed

2 files changed

+62
-6
lines changed

src/lib/components/InvertedScroller.svelte

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
<script lang="ts">
2+
import { getRenderingEngine } from '$lib/featuredetect.js';
3+
import { onMount } from 'svelte';
4+
25
let {
36
//
47
scrollContainer = $bindable({} as HTMLDivElement),
@@ -12,7 +15,8 @@
1215
});
1316
}
1417
15-
let targetScrollTop = $state(0);
18+
let scrollBehavior: ScrollBehavior;
19+
let targetScrollTop = 0;
1620
let scrollTopUpdateTask = -1;
1721
1822
function onScrollCapture() {
@@ -38,15 +42,43 @@
3842
This appears to work pretty reliably in Chromium and Firefox (haven't tested Safari), so I'm happy with it.
3943
*/
4044
41-
// Calling scrollTo while a previous call is still animating will cancel the current animation.
42-
// This is how we trick the browser into giving the same _feel_ as a normal scroll would have.
45+
let newTargetScrollTop = targetScrollTop - e.deltaY; // Note that the scroll direction is inverted.
46+
47+
const maxScroll = scrollContainer.scrollHeight - scrollContainer.clientHeight;
48+
if (newTargetScrollTop < 0) {
49+
newTargetScrollTop = 0; // Min.
50+
} else if (newTargetScrollTop > maxScroll) {
51+
newTargetScrollTop = maxScroll; // Max.
52+
}
53+
54+
if (newTargetScrollTop == targetScrollTop) {
55+
return; // Don't call scrollTo() unnecessarily.
56+
}
4357
44-
targetScrollTop = targetScrollTop - e.deltaY; // Note that the scroll direction is inverted.
58+
targetScrollTop = newTargetScrollTop;
4559
scrollContainer.scrollTo({
46-
top: targetScrollTop,
47-
behavior: 'smooth'
60+
top: newTargetScrollTop,
61+
behavior: scrollBehavior
4862
});
4963
}
64+
65+
onMount(() => {
66+
// Allowlist specific rendering engines for smooth scrolling.
67+
// Some browsers have a built-in debounce that won't start the animation until our final scrollTo() call
68+
// which prevents our scrolling implementation from working correctly.
69+
//
70+
// So we allow for smooth scrolling if the browser behaves how we want it to.
71+
switch (getRenderingEngine()) {
72+
case 'webkit': // a.k.a safari
73+
case 'gecko': // a.k.a firefox
74+
scrollBehavior = 'smooth';
75+
break;
76+
77+
default:
78+
scrollBehavior = 'instant';
79+
break;
80+
}
81+
});
5082
</script>
5183

5284
<div

src/lib/featuredetect.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { browser } from '$app/environment';
2+
3+
declare type RenderingEngine = 'webkit' | 'blink' | 'gecko' | 'unknown';
4+
5+
let cachedRenderingEngine: RenderingEngine | null = null;
6+
export function getRenderingEngine(): RenderingEngine {
7+
if (!browser) return 'unknown';
8+
9+
if (!cachedRenderingEngine) {
10+
const userAgent = navigator.userAgent || '';
11+
12+
if (userAgent.includes('Chrome/') || userAgent.includes('Chromium/')) {
13+
cachedRenderingEngine = 'blink';
14+
} else if (userAgent.includes('AppleWebKit/') || userAgent.includes('Safari/')) {
15+
cachedRenderingEngine = 'webkit';
16+
} else if (userAgent.includes('Gecko/')) {
17+
cachedRenderingEngine = 'gecko';
18+
} else {
19+
cachedRenderingEngine = 'unknown';
20+
}
21+
}
22+
23+
return cachedRenderingEngine;
24+
}

0 commit comments

Comments
 (0)