Performance-optimized viewport dimensions and element rect caching
npm install viewport-helperA performance-optimized TypeScript library for caching viewport dimensions and element rectangles to prevent layout reflows and improve web application performance.
> [!WARNING]
> This library is still in early development and may not be suitable for production use. It is recommended to use it in a development environment and testing only. Before it reaches 1.0.0, it may undergo significant changes and deprecations.
> [!NOTE]
> This library is designed for use in web applications and is not intended for use in server-side rendering or Node.js environments. It gracefully fails if it doesn't detect a window object.
- ๐ Performance-optimized - Caches viewport units (lvh, svh) and element rects to avoid costly reflows
- ๐ฑ Mobile-friendly - Handles viewport changes on mobile devices without firing on scroll
- โก Smart caching - Automatically invalidates caches on scroll, resize, or element changes
- ๐ฏ TypeScript support - Full type safety with comprehensive TypeScript definitions
- ๐ Event-driven - Listen for meaningful viewport changes with custom events
- ๐งน Memory-safe - Automatic cleanup of disconnected elements to prevent memory leaks
- ๐ฆ ESM/CJS support - Compatible with both ES Modules and CommonJS
``bash`
npm install viewport-helper
`typescript
import viewportHelper from "viewport-helper";
// Get cached viewport dimensions (no reflow!)
const height = viewportHelper.lvh(); // 100lvh in pixels
const smallHeight = viewportHelper.svh(50); // 50svh in pixels
const width = viewportHelper.innerWidth();
// Get all dimensions at once
const { lvh, svh, innerWidth, innerHeight } = viewportHelper.getAll();
// Track element rectangles efficiently
const element = document.querySelector(".my-element") as HTMLElement;
const rect = viewportHelper.rect(element); // Cached until element changes
// Listen for viewport changes
viewportHelper.addEventListener("resize", (event) => {
console.log("Viewport changed:", event.detail);
});
`
#### lvh(size?: number, round?: boolean): number
Gets the cached large viewport height (lvh) in pixels.
`typescript`
const fullHeight = viewportHelper.lvh(); // 100lvh (default)
const halfHeight = viewportHelper.lvh(50); // 50lvh
const preciseHeight = viewportHelper.lvh(100, false); // Unrounded
#### svh(size?: number, round?: boolean): number
Gets the cached small viewport height (svh) in pixels.
`typescript`
const fullHeight = viewportHelper.svh(); // 100svh (default)
const quarterHeight = viewportHelper.svh(25); // 25svh
#### innerWidth(): number
Gets the cached window.innerWidth value.
`typescript`
const width = viewportHelper.innerWidth();
#### innerHeight(): number
Gets the cached window.innerHeight value.
`typescript`
const height = viewportHelper.innerHeight();
#### getAll(): object
Gets all cached viewport dimensions at once.
`typescript`
const dimensions = viewportHelper.getAll();
// Returns: { lvh: number, svh: number, innerWidth: number, innerHeight: number }
#### rect(element: HTMLElement): DOMRect
Gets the cached bounding rectangle for an element. Safe to call repeatedly in animation loops.
`typescript
const element = document.querySelector(".box") as HTMLElement;
// First call measures and caches
const rect = viewportHelper.rect(element);
// Subsequent calls return cached values until invalidated
requestAnimationFrame(() => {
const cachedRect = viewportHelper.rect(element); // No reflow!
console.log(Position: ${cachedRect.x}, ${cachedRect.y});`
});
#### untrack(element: HTMLElement): void
Stops tracking an element's bounding rectangle and removes it from cache.
`typescript`
viewportHelper.untrack(element);
#### clearTracked(): void
Clears all tracked elements from the cache.
`typescript`
viewportHelper.clearTracked();
#### resize Event
Fired when viewport dimensions actually change (not on mobile scroll).
`typescript
viewportHelper.addEventListener("resize", (event) => {
const { lvh, svh, innerWidth, innerHeight, changed } = event.detail;
if (changed.lvh) {
console.log("Large viewport height changed to:", lvh);
}
if (changed.svh) {
console.log("Small viewport height changed to:", svh);
}
});
`
Event Detail Interface:
`typescript`
interface ViewportHelperResizeDetail {
lvh: number;
svh: number;
innerWidth: number;
innerHeight: number;
changed: {
lvh: boolean;
svh: boolean;
innerWidth: boolean;
innerHeight: boolean;
};
}
#### destroy(): void
Cleans up all resources used by the size manager.
`typescript`
// Call during app teardown
viewportHelper.destroy();
`typescript
import viewportHelper from "viewport-helper";
const animateElement = (element: HTMLElement) => {
requestAnimationFrame(() => {
// No layout reflow - values are cached!
const rect = viewportHelper.rect(element);
const viewportHeight = viewportHelper.lvh();
// Perform calculations safely
if (rect.top > viewportHeight) {
element.style.transform = "translateY(-100%)";
}
});
};
`
`typescript
import viewportHelper from "viewport-helper";
const updateLayout = () => {
const isMobile = viewportHelper.innerWidth() < 768;
const height = isMobile ? viewportHelper.svh() : viewportHelper.lvh();
document.documentElement.style.setProperty(
"--viewport-height",
${height}px,
);
};
viewportHelper.addEventListener("resize", updateLayout);
updateLayout(); // Initial setup
`
`typescript
import viewportHelper from "viewport-helper";
const handleScroll = () => {
document.querySelectorAll(".parallax").forEach((element) => {
// Cached rect - no performance penalty!
const rect = viewportHelper.rect(element);
const progress = rect.top / viewportHelper.innerHeight();
element.style.transform = translateY(${progress * 50}px);
});
};
window.addEventListener("scroll", handleScroll, { passive: true });
`
`typescript
import viewportHelper from "viewport-helper";
const positionModal = (modal: HTMLElement) => {
const modalRect = viewportHelper.rect(modal);
const viewportHeight = viewportHelper.lvh();
const viewportWidth = viewportHelper.innerWidth();
// Center modal without causing reflow
modal.style.left = ${(viewportWidth - modalRect.width) / 2}px;${(viewportHeight - modalRect.height) / 2}px
modal.style.top = ;``
};
- Chrome/Edge 88+ (ResizeObserver, lvh/svh units)
- Firefox 89+
- Safari 13.1+
- Eliminates layout reflows when accessing viewport dimensions
- Caches element rectangles until they actually change
- Batches measurements using RequestAnimationFrame
- Automatic cleanup of disconnected DOM elements
- Smart invalidation only when necessary
MIT ยฉ 3dfactor