7

I know there have been multiple SO questions about this, but none of them seem to work anymore since the browser teams have made changes since those questions were asked. I'm trying to find the most up-to-date solution to this problem.

Objective

My desire is to have a fixed header stay stationary at the top for an app-like experience when using a mobile web browser, specifically when the user has focus on an input field and the virtual keyboard is showing.

Background context

Recently, the Chrome mobile team changed how they resize their layout and visual viewports for mobile web to be in alignment with Chrome on iOS and mobile Safari on iOS.

This GIF from an article by David Fedor very succinctly demonstrates the current status of most (but not all) mobile browser with regards to the visual viewport being "moved up" when the On-Screen Keyboard (OSK, aka Virtual Keyboard) is shown when a field is focused.

The biggest issue I'm facing is that I cannot get a header element to stay visually present at the top when the OSK is shown. This is a big deal when your top appbar has primary actions for a form like "Save". It makes the UX confusing, which leads to customers leaving in frustration, so this is isn't something trivial in my opinion.

I've tried the VisualViewport API that Chrome team recommended, referencing the env(keyboard-inset-height) and that just doesn't seem to work, which makes me think I'm doing something weird with my layout such that the ENV isn't being set properly.

enter image description here

My code

Nothing too crazy, using CSS grid to control layout.

HTML

<html>
<body>
  <div id="container">
    <header id="header">header</header>
    <main id="main">
      main
      <input type="text" />
    </main>
    <footer id="footer">footer</footer>
  </div>
</body>
</html>

CSS style

body {
  margin: 0;
}

#container {
  height: 100dvh;
  display: grid;
  grid-template:
    "header" 50px
    "main" 1fr
    "footer" 50px
    "keyboard" env(keyboard-inset-height, 0px); // does not seem to work?
}

#header {
  grid-area: header;
}

#main {
  grid-area: main;
  overflow-y: scroll;
}

#footer {
  grid-area: footer;
}

What I get is the following 2 screenshots, the full-page one being the layout as I want it, and the 2nd one being when you focus the cursor into an input near the bottom and it "pushes" the entire visual viewport up such that the header is offscreen.

fullscreen input focused
enter image description here enter image description here

Is there any way currently to ensure that the header stays at the top of the visual viewport always? I cannot find a working solution to this at the moment after the recent Chrome updates.

Dan L
  • 4,319
  • 5
  • 41
  • 74
  • I might come back and attempt to answer this question later. But if I forget, maybe this answer could be of help: https://stackoverflow.com/a/75648985/15291770 It discusses the newer viewport dimensions, such as `svh` which may be handy. **NOTE:** These dimensions are much more widely available than the screenshot suggests, see [caniuse](https://caniuse.com/mdn-css_types_length_viewport_percentage_units_small) – Harrison Aug 07 '23 at 14:11

4 Answers4

1

For Google Chrome 108 or later, set <meta name="viewport" content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content">.
Pay attention to interactive-widget property in the viewport meta tag.

I found the solution here.
Checked on the latest version of Chrome for Android.

body {
  margin: 0;
}

#container {
  height: 100dvh;
  display: grid;
  grid-template:
    "header" 50px
    "main" 1fr
    "footer" 50px
  ;
}

#header {
  grid-area: header;
}

#main {
  grid-area: main;
  overflow-y: scroll;
}

#footer {
  grid-area: footer;
}
<!DOCTYPE html>
<html>
<head>
  <title>Title</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content">
</head>
<body>
<div id="container">
  <header id="header">header</header>
  <main id="main">
    main 2<br>
    <input type="text" /><br>
    <input type="text" /><br>
    <input type="text" /><br>
    <input type="text" /><br>
    <input type="text" /><br>
    <input type="text" /><br>
    <input type="text" /><br>
    <input type="text" /><br>
    <input type="text" /><br>
    <input type="text" /><br>
    <input type="text" /><br>
    <input type="text" /><br>
    <input type="text" /><br>
    <input type="text" /><br>
    <input type="text" /><br>
    <input type="text" /><br>
    <input type="text" /><br>
    <input type="text" /><br>
    <input type="text" /><br>
    <input type="text" /><br>
    <input type="text" /><br>
    <input type="text" /><br>
    <input type="text" /><br>
    <input type="text" /><br>
    <input type="text" /><br>
    <input type="text" /><br>
    <input type="text" /><br>
    <input type="text" /><br>
    <input type="text" /><br>
    <input type="text" /><br>
    <input type="text" /><br>
    <input type="text" /><br>
    <input type="text" /><br>
  </main>
  <footer id="footer">footer</footer>
</div>
</body>
</html>
imhvost
  • 4,750
  • 2
  • 8
  • 10
-2

Have you tried this?:

CSS

#header {
position: sticky;
}

If I understand your question correctly, you only care about the header remaining visible and not the document's body which contains the inputs. It seems like position: sticky; will give you this effect. If you don't want it to 'stick' unless an input is in focus, you could leverage some basic JavaScript to toggle its position value.

MDN docs give a decent explanation. Is this what you're looking to do? https://developer.mozilla.org/en-US/docs/Web/CSS/position

-2

#header, header { 
position: fixed; 
padding: 20px;
top: 0; 
z-index: 99999; 
} 
div.container{ 
background:#eee; 
display:block; 

}
body{ height:2000px;}
<body>
<header>
<div class="container"><h2>Sticky Header</h2></div>
</header>
</body>

also you can use position: absolute; and add media in css.

Zuhair
  • 87
  • 2
-2

CSS:

header {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  z-index: 100;
}

JS:

function debounce(func, delay) {
  let timer;
  return function () {
    clearTimeout(timer);
    timer = setTimeout(func, delay);
  };
}

// Attach debounced event listeners`enter code here`
window.addEventListener('scroll', debounce(onScroll, 50)); // Adjust the delay as needed
window.addEventListener('resize', debounce(onResize, 50)); // Adjust the delay as needed
Rok Benko
  • 14,265
  • 2
  • 24
  • 49
  • You may want to edit your answer to explain why this resolves the asker's question, further guidance can be found in the [Help Center](https://stackoverflow.com/help/how-to-answer) – Harrison Aug 14 '23 at 09:13