Working with Hero Image Height in Mobile Browsers


Nowadays, setting a hero image to full height is as easy as attaching a height: 100vh CSS rule. While this works great on desktop browsers, things are a bit more complicated on mobile devices due to their disappearing address bar.

Mobile Device Viewport Height is Unpredictable

While mobile browsers honor the height 100vh CSS rule, styling elements at the top of mobile pages is problematic due to the the URL Address Bar. The bar is present on Apple and Android devices, any only disappears when scrolling down the page.

Address Bar visible at the top of the page
Address Bar is hidden after scrolling down

The images above show a hero background video that is intended to stretch the entire height of the mobile viewport. The intended design is shown on the right image, and is only realized when the Address Bar is hidden. The image on the left shows what the page looks like with the Address Bar visible; the hero video is cut off at the bottom, and the blue text overlay is not vertically centered.

A Pure CSS Solution

Fortunately a CSS-only solution with pretty good browser support has emerged. It takes advantage of CSS Intrinsic Sizing, a new concept to me that I am excited to work with.

Data on support for the intrinsic-width feature across the major browsers from

The CSS-only solution allows for easy vertical alignment of text overlay content, and absolute positioning of other UI elements based on the true height of the hero image.

Here is a set of cross-browser CSS rules that will set the element height to the match a mobile device’s viewport height (also works on desktop):

img {
  height: 100vh;  // fallback and unsupported browsers
  height: -moz-available;  // Mozilla-based browsers
  height: -webkit-fill-available; //  WebKit-based browsers
  height: fill-available;

JavaScript Solutions

I came across different JavaScript fixes that could be used to set hero image height, or help position UI elements properly based on true viewport height. While these solutions are generally not as clean as applying the CSS rules above, they may be helpful in certain circumstances.

Dynamically Setting the Image Height

JavaScript can be used to set the hero image height to match the viewport height at page load. In the code snippet below, Line 4 sets the image height, while the surrounding function declaration and event listener ensures the image size is reset if the screen/window is resized.

// wrap in function and bind to window resize events
const setHeroHeight = () => {
  // set the image height to viewport height
  document.querySelector('.hero-image').style.height = window.innerHeight
window.addEventListener('resize', setHeroHeight)

// call at page load

Positioning UI Elements with JavaScript

The absolute positioning of UI elements can be tricky if the hero image stretches longer than the viewport. Elements may be cut off whenever the Address Bar was visible.

Fixed positioning can be applied to UI elements until the bottom of their parent hero image is scrolled into view. At that point, absolute positioning is applied to the navigation elements (to ensure the remain in the hero image). The desired effect is:

This was fairly straightforward to accomplish using a JavaScript scroll event listener:

// select the node containing the UI navigation elements for callback function closure
const navigationContainerElem = document.getElementById('navContainer')
window.addEventListener('scroll', function(e) {
  if (window.scrollY > 0) {
    // if user has scrolled past 0, absolute position elements inside the hero image = 'absolute'
  } else {
    // otherwise set fixed positioning if still at top of the page = 'fixed'

Here is the same logic, but with IntersectionObservers instead. While there may be a performance benefit over traditional event listeners, I found the toggling between styles to be a bit more noticeable:

// select the node containing the UI navigation elements for callback function closure
const navigationContainerElem = document.getElementById('navContainer')

// configure intersection observer to fire when hero image no longer fully visible
const observer = new IntersectionObserver(function(entries) {
    entries.forEach(entry => {
      if (entry.intersectionRatio > 0) {
        // complete hero image no longer visible - absolute position navigation elements inside the hero image = 'absolute'
      } else {
        // complete hero image is visible - set fixed positioning since at top of the page = 'fixed'
  threshold: [0,.01]

// observe the DOM element right AFTER the hero image
const aboutMeElem = document.getElementById('about-me')

Subscribe by Email

Enter your email address below to be notified about updates and new posts.


Loading comments..

No responses yet

Leave a Reply

Your email address will not be published. Required fields are marked *