npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

fixed-vh-polyfill

v1.0.3

Published

A lightweight JavaScript/TypeScript utility that stabilizes viewport height units (vh, svh, lvh) across browsers, preventing unintended reflows and scroll jitter.

Readme

📱 Fixed VH Polyfill

A lightweight JavaScript/TypeScript utility that stabilizes viewport height units (vh, svh, lvh) across browsers, preventing unintended reflows and scroll jitter.

This tool provides consistent viewport-relative unit values for mobile web apps, preventing unintended reflows and scroll jitter with minimal setup.


⚡ Quick start

📦 Installation

npm install fixed-vh-polyfill

📝 Usage

import { FixedVhPolyfill } from 'fixed-vh-polyfill';
document.addEventListener('DOMContentLoaded', () => FixedVhPolyfill.init());

🎨 CSS

.fullscreen {
  height: calc(100 * var(--fvh, 1vh));
}

🛑 The Problem & Solution

What's the Problem?

  • The vh unit is a CSS unit relative to the viewport height. However, different browsers interpret viewport height differently when UI elements like the address bar appear or disappear, which can cause unintended reflows and scroll jitter.
  • On mobile devices, the viewport height can also be affected by the visibility of the keyboard, depending on the OS or browser version.
  • Since 2022, svh and lvh units were introduced to address some of these issues. Yet, some browsers still have inconsistencies. For example, while Safari on the latest iOS works correctly, certain in-app or third-party WebKit-based browsers do not reliably interpret svh and lvh.
  • Additionally, there are various edge cases across different devices and environments.

How Fixed VH Polyfill Solves This

  • Ensures consistent values for viewport-relative units.
  • Prevents unintended reflows and scroll jitter.
  • Automatically detects whether it’s needed and enables/disables itself to avoid unnecessary resource usage.

🤔 Why Fixed VH Polyfill?

✨ Key Features & Advantages

  • 🌟 Supports modern units: Unlike traditional polyfills, it ensures consistent behavior for lvh and svh, accurately reflecting your web app design intentions. For example, use svh to guarantee content fully fits within the viewport.
  • 🤖 Intelligent Self-Optimization: Measures viewport unit behavior across multiple events to determine if it’s actually needed. Once stable viewport units are detected, it automatically cleans up and stops running to save resources.
  • Resource Efficient: Only runs when necessary, with minimal performance impact.
  • 🪶 Lightweight: Small footprint with zero dependencies.
  • 🔌 Easy Integration: Simply import and initialize—it works automatically.
  • 🖌️ Customizable: Allows custom CSS variable names to prevent conflicts.
  • 🌐 Broad Compatibility: Works across all modern browsers, including mobile.
  • 🪄 SPA Friendly: Provides a cleanup method to avoid memory leaks in SPA rendering environments.

🚀 Getting Started

📦 Installation

npm install fixed-vh-polyfill

📝 Usage

Since this polyfill interacts with the DOM, it's important to initialize it after the DOM is fully loaded. Wrap the init() call inside a DOMContentLoaded event listener.

1. Initialization Examples:

ESM:

import { FixedVhPolyfill } from 'fixed-vh-polyfill';

document.addEventListener('DOMContentLoaded', () => {
  FixedVhPolyfill.init();
});

or via CDN:

<script type="module">
    import { FixedVhPolyfill } from 'https://cdn.jsdelivr.net/npm/fixed-vh-polyfill/+esm';
    
    document.addEventListener('DOMContentLoaded', () => {
        FixedVhPolyfill.init();
    });
</script>

CJS (Node.js):

const { FixedVhPolyfill } = require('fixed-vh-polyfill');

// Ensure this code runs after the DOM is loaded if used in a browser context
document.addEventListener('DOMContentLoaded', () => {
  FixedVhPolyfill.init();
});

UMD (Browser via CDN):

<script src="https://cdn.jsdelivr.net/npm/fixed-vh-polyfill/dist/index.umd.js"></script>
<script>
  document.addEventListener('DOMContentLoaded', () => {
    window.FixedVhPolyfill.init();
  });
</script>

2. In HTML/CSS:

Once initialized, use the configured CSS custom properties in your CSS. The value is 1/100 of the stable viewport height.

The 1vh, 1lvh, or 1svh fallback ensures your layout remains sensible if the polyfill hasn't loaded or is disabled.

The available CSS custom properties depend on your browser's viewport unit support:

  • --fvh: Available in all browsers that support vh units

    .fullscreen {
    /* fvh: Initial viewport height when page first loads */
    height: calc(100 * var(--fvh, 1vh));
    }
  • --lvh, --svh: Available in browsers that support lvh/svh units (modern browsers)

    .fullscreen.large {
    /* lvh: Fixed large viewport height (browser UI hidden) */
    height: calc(var(--lvh, 1lvh) * 100);
    }
    .fullscreen.small {
    /* svh: Fixed small viewport height (browser UI visible) */
    height: calc(var(--svh, 1svh) * 100);
    }

Progressive Enhancement

  • Fallback Strategy:

    /* Use this pattern for maximum browser compatibility */
    .fullscreen.fallback {
        height: 100vh;                        /* fallback for very old browsers */
        height: 100lvh;                       /* native lvh for modern browsers */
        height: calc(100 * var(--lvh, 1lvh)); /* polyfilled stable version */
    }

Which Unit Should I Use?

  • --fvh (Fixed Viewport Height = vh): General purpose stable viewport height, good for most use cases
  • --lvh (Large Viewport Height): Use when you want content to fit the full available space when browser UI is hidden (typically after scrolling down)
  • --svh (Small Viewport Height): Use when you want content to always be visible even when browser UI is shown (initial page load, scrolling up)

3. Framework Integration Examples

React:

import { useEffect } from "react";
import { FixedVhPolyfill } from "fixed-vh-polyfill";

function App() {
  useEffect(() => {
    FixedVhPolyfill.init();
    // Optionally call cleanup on unmount in SPAs
    return () => FixedVhPolyfill.cleanup();
  }, []);

  return (
    <div className="fullscreen">
      {/* ... */}
    </div>
  );
}

export default App;

Next.js

import { useEffect } from 'react';
import { FixedVhPolyfill } from 'fixed-vh-polyfill';

function App() {
  useEffect(() => {
    if (typeof window !== 'undefined') {
      FixedVhPolyfill.init();
    }
    return () => FixedVhPolyfill.cleanup();
  }, []);
  return <div className="fullscreen">{/* ... */}</div>;
}

Vue 3

<script setup>
import { onMounted, onBeforeUnmount } from "vue";
import { FixedVhPolyfill } from "fixed-vh-polyfill";

onMounted(() => {
  FixedVhPolyfill.init();
});
onBeforeUnmount(() => {
  FixedVhPolyfill.cleanup();
});
</script>

<template>
  <div class="fullscreen">
    <!-- ... -->
  </div>
</template>

💡 Works perfectly in SPAs!
Just call init() when your component mounts and cleanup() when it unmounts to avoid memory leaks.

⚙️ Configuration (Options)

You can pass an options object to the init() method to customize its behavior. Here is an example with all available options:

document.addEventListener('DOMContentLoaded', () => {
  FixedVhPolyfill.init({
    // Custom CSS variable names
    fvhPropertyName: '--my-fixed-vh',
    lvhPropertyName: '--my-stable-lvh',
    svhPropertyName: '--my-stable-svh',

    // Enable debug mode
    debugMode: true
  });
});

Available Options

| Option | Type | Default | Description | | :---------------- | :-------- | :-------- | :------------------------------------------------------------------------------------------------------ | | fvhPropertyName | string | '--fvh' | The CSS custom property for a stable vh unit, fixed to the viewport height at initial page load. | | lvhPropertyName | string | '--lvh' | The CSS custom property for the stable "large viewport height" (lvh). | | svhPropertyName | string | '--svh' | The CSS custom property for the stable "small viewport height" (svh). | | debugMode | boolean | false | When true, displays an overlay with the polyfill's internal state for debugging. |

🐞 Debug Mode

When debugMode is set to true in the configuration, a small, draggable element will be added to the bottom-right of the screen. This element displays the real-time internal state of the polyfill, which is useful for visually confirming how the polyfill is operating on a target device.

Enable overlay debug UI:

FixedVhPolyfill.init({ debugMode: true });

📈 Performance Impact

  • Lazy Detection: Only 5 measurements needed
  • Debounced Updates: 200ms debouncing prevents excessive updates
  • Auto-Cleanup: Removes listeners when not needed
  • Memory Efficient: Uses requestAnimationFrame for smooth updates

👨‍💻 Author

🤝 License

This project is licensed under the MIT License.