viewport-truth
v1.0.7
Published
Tiny zero‑config VisualViewport-first store for accurate visible viewport size in CSS pixels. Detects virtual keyboard, stabilizes resize/scroll jitter, and is SSR-safe across frameworks.
Maintainers
Readme
viewport-truth
Fastest way to measure the real visible mobile viewport without 100vh glitches, resize/scroll jitter, or keyboard hacks.
Stop guessing mobile viewport sizes. viewport-truth delivers stable, keyboard-aware visible viewport metrics (VisualViewport-first) across iOS Safari, Android Chrome, PWAs, and webviews—framework adapters included, SSR-safe, zero runtime deps.
Demo: tracks the visible viewport (VisualViewport), keeping UI stable when the URL bar / keyboard changes the viewport.
npm i viewport-truth
# or: yarn add viewport-truth
# or: pnpm add viewport-truthQuick Start
Minimal flow: import → create → subscribe → get { width, height, isKeyboardOpen, isStable }.
import { createViewportTruth } from "viewport-truth/vanilla";
const vt = createViewportTruth();
const unsub = vt.subscribe((v) => {
if (!v) return;
console.log(
`visible=${v.width}x${v.height} keyboard=${v.isKeyboardOpen} stable=${v.isStable}`
);
});
// later:
// unsub();
// vt.destroy();Demo snippet (keyboard + URL bar)
Use this in a real page (Vite/Parcel/Next). On mobile: scroll a bit (URL bar), then focus the input (keyboard).
<div id="app" style="padding:16px 16px 96px">
<input
placeholder="Focus me to open keyboard"
style="width:100%;padding:12px;font-size:16px;box-sizing:border-box"
/>
<p style="margin:12px 0 0;color:#444">
Tip: scroll a bit (URL bar animates), then focus the input.
</p>
<div style="height:120vh"></div>
<div id="bar"></div>
</div>
<script type="module">
import { createViewportTruth } from "viewport-truth/vanilla";
const bar = document.getElementById("bar");
Object.assign(bar.style, {
position: "fixed",
left: "0",
right: "0",
bottom: "0",
padding: "10px 12px",
font: "12px/1.35 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
background: "rgba(0,0,0,.86)",
color: "white",
zIndex: "9999",
whiteSpace: "pre",
});
const vt = createViewportTruth();
vt.subscribe((v) => {
if (!v) return;
// Fallback keeps the snippet working even if layoutWidth/layoutHeight aren't present.
const layoutW = v.layoutWidth ?? v.width;
const layoutH = v.layoutHeight ?? v.height;
const lost = Math.max(0, layoutH - v.height);
bar.textContent =
`visible: ${v.width}×${v.height}
layout: ${layoutW}×${layoutH}
lost: ${lost}px
keyboard: ${v.isKeyboardOpen}
stable: ${v.isStable}`;
});
</script>Features
A few concrete, technical reasons it behaves well on mobile:
- Tiny: ~< 2 KB min+gzip (check the Bundlephobia badge).
- Fast updates: emits at most 1 update per animation frame (rAF throttling).
- Zero runtime deps: 0 dependencies at runtime (tree-shakeable ESM).
- Stable signal:
isStableflips after 150ms (default) without geometry changes.
API (short)
Core snapshot fields you’ll typically use:
width,height— visible viewport size (CSS px)layoutWidth,layoutHeight— layout viewport (basis for keyboard detection)isKeyboardOpen— geometry-based keyboard inferenceisStable— “animations settled” signal for UI decisions
Vanilla store:
createViewportTruth()fromviewport-truth/vanilla→ creates a store withsubscribe()anddestroy()
Framework adapters:
- React:
useViewportTruthfromviewport-truth/react - Vue:
useViewportTruthfromviewport-truth/vue - Svelte:
viewportTruthfromviewport-truth/svelte - Solid:
createViewportTruthfromviewport-truth/solid - Angular:
ViewportTruthDirectivefromviewport-truth/angular
Full types and signatures: see dist/*.d.ts (or TypeScript IntelliSense).
Adapter Docs: React • Vue • Svelte • Solid • Angular
Tip: Open links in a new tab with Ctrl+Click (Windows/Linux) or Cmd+Click (macOS).
Links
FAQ • Common pitfalls • Smoke test (clean environment) • Versioning policy
Support the project
“We ate the Geometry Hell for you: jumping
100vh, jitteryresize, modals under the keyboard.
You saved hours (and sanity). A donation is a fair trade for a rock-solid UI and weekends free from debugging.”
If this library saved you time, please consider supporting the development:
- Fiat (Cards/PayPal): via Boosty (one-time or monthly).
- Crypto (USDT/BTC/ETH): view wallet addresses on Telegram.
License
MIT
