fix(dashboard): don't fight user pinch-zoom in viewport override

visualViewport.resize fires on pinch-zoom, not just window-size change.
The previous viewport-override would re-assert initial-scale=1.0 on
every pinch event, snapping the user's zoom back to 1.0 instantly.
Restrict the listener to orientationchange + initial settle timers
so pinch-zoom is left alone after page load.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-05-22 10:59:37 -07:00
parent c3890a9b43
commit d2e31dc107

View File

@@ -2,7 +2,46 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<meta name="viewport" id="viewport-meta" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<script>
// Android Chrome (multi-window, split-screen, freeform, certain WebView
// configs) can cache device-width at a smaller value than the actual
// window, leaving the page rendered into a small upper-left canvas
// while position:fixed elements pin to the much larger visual viewport.
// window.innerWidth returns the (broken) layout viewport, so re-asserting
// width=innerWidth changes nothing. visualViewport.width returns the
// actual window — re-assert against that on resize/orientation so Chrome
// recomputes the layout at the correct size.
(function () {
var meta = document.getElementById('viewport-meta');
if (!meta || typeof window === 'undefined') return;
var apply = function () {
var vv = window.visualViewport;
if (!vv) return;
var w = Math.round(vv.width);
var inner = window.innerWidth;
// Only override when layout viewport is meaningfully smaller than
// the visual viewport (the bug we're fixing). Otherwise keep the
// standard `width=device-width` so phones with the keyboard up etc.
// behave normally.
if (w > inner + 8) {
meta.setAttribute('content', 'width=' + w + ', initial-scale=1.0, viewport-fit=cover');
} else {
meta.setAttribute('content', 'width=device-width, initial-scale=1.0, viewport-fit=cover');
}
};
// Only fire on events that genuinely change the window — NOT on
// visualViewport.resize, which also fires on pinch-zoom and would
// snap the user's zoom back to 1.0 every gesture.
window.addEventListener('orientationchange', apply);
apply();
// Re-check shortly after load — Chrome's initial reading sometimes
// settles a frame or two later in multi-window.
setTimeout(apply, 100);
setTimeout(apply, 500);
setTimeout(apply, 1500);
})();
</script>
<script>
// Append ?vpdebug to the URL to show a fixed overlay with the live
// viewport / element dimensions. Diagnostic only — remove once the
@@ -22,8 +61,10 @@
}
}
};
var tick = 0;
var update = function () {
ensureAttached();
tick += 1;
var f = function (el) {
if (!el) return 'none';
var r = el.getBoundingClientRect();
@@ -55,8 +96,14 @@
'pcCls ' + pcCls + '\n' +
'mq768 ' + window.matchMedia('(max-width: 768px)').matches + '\n' +
'dialogs ' + dialogs + ' focus ' + focused + '\n' +
'sx ' + window.scrollX + ' sy ' + window.scrollY + '\n' +
'sx ' + window.scrollX + ' sy ' + window.scrollY + ' tick ' + tick + '\n' +
'docCW ' + document.documentElement.clientWidth + ' bodyCW ' + document.body.clientWidth + '\n' +
'docSW ' + document.documentElement.scrollWidth + ' bodySW ' + document.body.scrollWidth + '\n' +
'ua ' + (navigator.userAgent || '').slice(0, 80);
try {
localStorage.setItem('vpdebug:last', box.textContent);
localStorage.setItem('vpdebug:lastAt', String(Date.now()));
} catch (e) {}
};
var attach = function () {
if (!document.body) {