patx/demo
optimize mobile scroll
Commit 8ebb1ca · patx · 2026-08-16T02:34:11-04:00
Comments
No comments yet.
Diff
diff --git a/docs/voldservices/hero-scrub-mobile.mp4 b/docs/voldservices/hero-scrub-mobile.mp4
new file mode 100644
index 0000000..0410b21
Binary files /dev/null and b/docs/voldservices/hero-scrub-mobile.mp4 differ
diff --git a/docs/voldservices/index.html b/docs/voldservices/index.html
index a579466..2be7ff9 100644
--- a/docs/voldservices/index.html
+++ b/docs/voldservices/index.html
@@ -1270,8 +1270,9 @@
<video
id="hero-video"
poster="https://gitman.io/patx/demo/raw/docs/voldservices/img/hero-scrub-poster.jpg"
- muted playsinline preload="auto"
+ muted playsinline webkit-playsinline autoplay preload="auto"
aria-hidden="true" tabindex="-1">
+ <source media="(max-width: 720px)" src="https://gitman.io/patx/demo/raw/docs/voldservices/hero-scrub-mobile.mp4" type="video/mp4" />
<source src="https://gitman.io/patx/demo/raw/docs/voldservices/hero-scrub.mp4" type="video/mp4" />
</video>
</div>
@@ -1926,6 +1927,8 @@
var heroMedia = heroSticky.querySelector('.hero-media');
var heroVideo = document.getElementById('hero-video');
var heroVideoReady = false;
+ var heroVideoPrimed = false;
+ var heroVideoPrimePending = false;
var heroVideoVisible = false;
var heroVideoRevealPending = false;
var heroVideoProgress = 0;
@@ -1984,11 +1987,57 @@
}
}
- heroVideo.addEventListener('loadedmetadata', function () {
+ function finishHeroVideoPrime() {
heroVideo.pause();
+ heroVideo.autoplay = false;
+ heroVideo.removeAttribute('autoplay');
+ heroVideoPrimePending = false;
+ heroVideoPrimed = true;
heroVideoReady = true;
+ window.removeEventListener('touchstart', primeHeroVideo);
syncHeroVideo(heroVideoProgress, true);
- });
+ }
+
+ function usePausedSeekFallback() {
+ heroVideo.pause();
+ heroVideo.autoplay = false;
+ heroVideo.removeAttribute('autoplay');
+ heroVideoPrimePending = false;
+ heroVideoReady = true;
+ syncHeroVideo(heroVideoProgress, true);
+ }
+
+ // iOS may load metadata for a muted video without activating its decoder.
+ // A brief hidden play/pause primes frame presentation so later paused
+ // currentTime changes repaint while the user scrolls. Muted inline play
+ // is allowed automatically; the first touch provides a second chance
+ // when Low Power Mode or a browser policy blocks autoplay.
+ function primeHeroVideo() {
+ if (reduceMotion || heroVideoPrimed || heroVideoPrimePending || heroVideo.readyState < 1) return;
+
+ heroVideoPrimePending = true;
+ heroVideo.muted = true;
+ heroVideo.defaultMuted = true;
+
+ var playAttempt = heroVideo.play();
+ if (playAttempt && typeof playAttempt.then === 'function') {
+ playAttempt.then(finishHeroVideoPrime).catch(usePausedSeekFallback);
+ } else {
+ finishHeroVideoPrime();
+ }
+ }
+
+ if (reduceMotion) {
+ heroVideo.autoplay = false;
+ heroVideo.removeAttribute('autoplay');
+ heroVideo.pause();
+ } else {
+ heroVideo.addEventListener('loadedmetadata', primeHeroVideo);
+ window.addEventListener('touchstart', primeHeroVideo, { passive: true });
+
+ // Cached media can reach metadata before this bottom-of-page script runs.
+ if (heroVideo.readyState >= 1) primeHeroVideo();
+ }
// If the user scrolls again while a seek is being decoded, catch the
// playhead up to the most recent position as soon as that frame lands.
@@ -1999,6 +2048,8 @@
heroVideo.addEventListener('error', function () {
heroVideoReady = false;
+ heroVideoPrimed = false;
+ heroVideoPrimePending = false;
heroVideoVisible = false;
heroVideoRevealPending = false;
heroMedia.classList.remove('is-video-ready');