最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

html - Stick an element horizontally only works on desktop, not mobile - Stack Overflow

programmeradmin3浏览0评论

Please note: You can use JavaScript (even though the bounty says otherwise), I am open to it, thank you. Also the main issue is not the scroll bar width, but the white space to the right of the header in mobile view.

Trying to make a layout that has a header, that scroll vertically off the page like normal. But if one uses the HTML scrollbars (important that it's the HTML scroll bars, so one does not have to hunt for the horizontally scrolls bars, the HTML scroll bars are always shown at the bottom of the viewport), with the header stuck horizontally. It's a layout for where the content is wider than the viewport, but one wish to have the header always shown horizontally, but allow it to scroll vertically off the screen for screen space.

When one scrolls to the right, next to the header is generally a blank open space, I wish for the header to stay sticky horizontally (but as mentioned before scroll vertically).

Here you can see when I scroll horizontally on desktop, the golden header stays in the place and fills the full width.

But as soon as you switch to mobile it no longer works, you can see the white gap here when I scroll horizontally, instead of the header remaining stuck in place:

This code snippet does not switch to mobile view, so you have to copy and paste this into a file and try it for yourself if you wish to see what's going on in mobile:

<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0"><!-- For mobile device scaling -->
<style>
    * {
        box-sizing: border-box;
    }
    html {
        overflow-y: scroll;
        width: max-content;
    }
    body {
        margin: 0;
    }
    header {
        background: goldenrod;
        border-right: 1px solid red;
        position: sticky;
        left: 0;
        width: calc(100vw - 15px);
    }
    main {
        width: 120vw;
        height: 120vh;
        background: gray;
    }
</style>
</head>

<body>
    <header> Header
    </header>
    <main>
        <div>Main stuff</div>
        <div>Paragraph that is very wide, so wide it should go beyond the width of the viewport, loooooooooooooooooooooooooooooooooooooooong</div>
    </main>
</body>

</html>

This code snippet does NOT switch to mobile view and show the issue, just provided for completeness.

* {
  box-sizing: border-box;
}

html {
  overflow-y: scroll;
  width: max-content;
}

body {
  margin: 0;
}

header {
  background: goldenrod;
  border-right: 1px solid red;
  position: sticky;
  left: 0;
  width: calc(100vw - 12px);
}

main {
  width: 200vw;
  height: 120vh;
  background: gray;
}
<header>Header, should stick horizontally, scroll vertically as usual
</header>
<main>

  <div>Main stuff</div>
  <div>Paragraph that is very wide, so wide it should go beyond the width of the viewport, loooooooooooooooooooooooooooooooooooooooong</div>
</main>

Please note: You can use JavaScript (even though the bounty says otherwise), I am open to it, thank you. Also the main issue is not the scroll bar width, but the white space to the right of the header in mobile view.

Trying to make a layout that has a header, that scroll vertically off the page like normal. But if one uses the HTML scrollbars (important that it's the HTML scroll bars, so one does not have to hunt for the horizontally scrolls bars, the HTML scroll bars are always shown at the bottom of the viewport), with the header stuck horizontally. It's a layout for where the content is wider than the viewport, but one wish to have the header always shown horizontally, but allow it to scroll vertically off the screen for screen space.

When one scrolls to the right, next to the header is generally a blank open space, I wish for the header to stay sticky horizontally (but as mentioned before scroll vertically).

Here you can see when I scroll horizontally on desktop, the golden header stays in the place and fills the full width.

But as soon as you switch to mobile it no longer works, you can see the white gap here when I scroll horizontally, instead of the header remaining stuck in place:

This code snippet does not switch to mobile view, so you have to copy and paste this into a file and try it for yourself if you wish to see what's going on in mobile:

<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0"><!-- For mobile device scaling -->
<style>
    * {
        box-sizing: border-box;
    }
    html {
        overflow-y: scroll;
        width: max-content;
    }
    body {
        margin: 0;
    }
    header {
        background: goldenrod;
        border-right: 1px solid red;
        position: sticky;
        left: 0;
        width: calc(100vw - 15px);
    }
    main {
        width: 120vw;
        height: 120vh;
        background: gray;
    }
</style>
</head>

<body>
    <header> Header
    </header>
    <main>
        <div>Main stuff</div>
        <div>Paragraph that is very wide, so wide it should go beyond the width of the viewport, loooooooooooooooooooooooooooooooooooooooong</div>
    </main>
</body>

</html>

This code snippet does NOT switch to mobile view and show the issue, just provided for completeness.

* {
  box-sizing: border-box;
}

html {
  overflow-y: scroll;
  width: max-content;
}

body {
  margin: 0;
}

header {
  background: goldenrod;
  border-right: 1px solid red;
  position: sticky;
  left: 0;
  width: calc(100vw - 12px);
}

main {
  width: 200vw;
  height: 120vh;
  background: gray;
}
<header>Header, should stick horizontally, scroll vertically as usual
</header>
<main>

  <div>Main stuff</div>
  <div>Paragraph that is very wide, so wide it should go beyond the width of the viewport, loooooooooooooooooooooooooooooooooooooooong</div>
</main>

Share Improve this question edited Jan 22 at 16:57 isherwood 61.1k16 gold badges121 silver badges169 bronze badges asked Jan 15 at 11:04 run_the_racerun_the_race 2,4183 gold badges54 silver badges84 bronze badges 3
  • What devices exhibit this behavior? My Samsung A20 and S23 show a gap at top right corner but it's as wide as the scrollbar (relatively not 1 to 1 scale) which is odd since mobile scale doesn't have scrollbars. – zer00ne Commented Jan 20 at 17:39
  • Because his CSS code subtracts 15px from the header‘s width. – Aaron3219 Commented Jan 21 at 9:45
  • I have a CAT S53 shows the white block, and Chrome Version 131.0.6778.108 in mobile view shows a huge white block, can remove the subtract 15px to confirm the white block. – run_the_race Commented Jan 22 at 15:01
Add a comment  | 

5 Answers 5

Reset to default 1 +500

I had similar issue with sticky not working properly on mobile. I had to change the viewport meta part to:

<meta name="viewport" content="height=device-height, width=device-width, initial-scale=1.0, minimum-scale=1.0, target-densitydpi=device-dpi">

In your special case, we have to add some extra CSS code with media query. The explanation inside the comments can be read:

<html>
    <head>
        <meta name="viewport" content="height=device-height, width=device-width, initial-scale=1.0, minimum-scale=1.0, target-densitydpi=device-dpi">
        <style>
        * {
            box-sizing: border-box;
        }
        html {
            overflow-y: scroll;
            width: max-content;
        }
        body {
            margin: 0;
        }
        header {
            background: goldenrod;
            border-right: 1px solid red;
            position: sticky;
            left: 0;
            width: calc(100vw - 15px);
        }
        main {
            width: calc(120vw);
            height: 120vh;
            background: gray;
        }
        @media (max-width: 480px){ /* mobile breakpoint - can be whatever you wish */
            header{
                padding-right: 15px;  /* since there are 15 pixels less from the width */
                box-sizing: content-box; /* to add the padding to the entire width */
            }
        }
    </style>
    </head>

    <body>
        <header> Header
        </header>
        <main>
            <div>Main stuff</div>
            <div>Paragraph that is very wide, so wide it should go beyond the width of the viewport, loooooooooooooooooooooooooooooooooooooooong</div>
        </main>
    </body>

    </html>

In action, can be viewed here: https://www.sanci.hu/stack/79357957.edit.htm

Indeed, in the snippet it's not relevant, but here it is:

<html>

<head>
  <meta name="viewport" content="height=device-height, width=device-width, initial-scale=1.0, minimum-scale=1.0, target-densitydpi=device-dpi">
  <style>
    * {
      box-sizing: border-box;
    }

    html {
      overflow-y: scroll;
      width: max-content;
    }

    body {
      margin: 0;
    }

    header {
      background: goldenrod;
      border-right: 1px solid red;
      position: sticky;
      left: 0;
      width: calc(100vw - 15px);
    }

    main {
      width: calc(120vw);
      height: 120vh;
      background: gray;
    }

    @media (max-width: 480px) { /* mobile breakpoint - can be whatever you wish */
      header {
        padding-right: 15px; /* since there are 15 pixels less from the width */
        box-sizing: content-box; /* to add the padding to the entire width */
      }
    }
  </style>
</head>

<body>
  <header> Header
  </header>
  <main>
    <div>Main stuff</div>
    <div>Paragraph that is very wide, so wide it should go beyond the width of the viewport, loooooooooooooooooooooooooooooooooooooooong</div>
  </main>
</body>

</html>

Requirements Recap:

  1. The header should vertically scroll with the content.
  2. The header should stick to the left side of the screen.
  3. Scrollbars must always be visible, fixed to the bottom and right side.
  4. No JavaScript.

Plain and simple: Achieving all your requirements purely with CSS and HTML is not possible. This issue is well-known since viewport units (vw, vh, svh, etc.) were introduced in 2012.

A couple of solutions you might want to consider:


Solution 1: Calculate and Subtract Scrollbar Width

As suggested by others already, a common approach is to dynamically calculate the scrollbar width and adjust the header's width accordingly. The solutions proposed by others so far are outdated and can be improved. I propose the following:

(function() {
  const scrollbarSize = window.innerWidth - document.documentElement.clientWidth;
  document.getElementById("header").style.setProperty("width", `calc(100vw - ${scrollbarSize}px)`);
})();
* {
  box-sizing: border-box;
}
html {
  overflow-y: scroll;
  width: max-content;
}
body {
  margin: 0;
}
header {
  background: goldenrod;
  position: sticky;
  left: 0;
}
main {
  width: 200vw;
  height: 120vh;
  background: gray;
}
<header id="header">Header</header>
<main>
  <div>Main stuff</div>
  <div>
    Paragraph that is very wide, so wide it should go beyond the width of the
    viewport, loooooooooooooooooooooooooooooooooooooooong
  </div>
</main>


Solution 2: Set Scrollbar Size as a CSS Variable

A more elegant approach is to set the scrollbar width as a CSS variable, making it reusable throughout your CSS code:

(function() {
  const scrollbarSize = window.innerWidth - document.documentElement.clientWidth;
  document.querySelector(':root').style.setProperty("--scrollbarSize", `${scrollbarSize}px`);
})();
:root {
  /* Use this variable anywhere you want! */
  --scrollbarSize: 0;
}

* {
  box-sizing: border-box;
}
html {
  overflow-y: scroll;
  width: max-content;
}
body {
  margin: 0;
}
header {
  background: goldenrod;
  position: sticky;
  left: 0;
  width: calc(100vw - var(--scrollbarSize));
}
main {
  width: 200vw;
  height: 120vh;
  background: gray;
}
<header id="header">Header</header>
<main>
  <div>Main stuff</div>
  <div>
    Paragraph that is very wide, so wide it should go beyond the width of the
    viewport, loooooooooooooooooooooooooooooooooooooooong
  </div>
</main>


While it's understandable that you sometimes want to avoid JavaScript, CSS alone cannot fully address these requirements. Using a small JavaScript snippet to calculate scrollbar width and adjust styles dynamically is the most reliable approach.


Edit 1:

Tested on iPhone (Chrome & Edge):

<html>

<head>
  <meta name="viewport" content="initial-scale=1.0, minimum-scale=1.0">
  <style>
    * {
      box-sizing: border-box;
    }

    html {
      overflow-y: scroll;
      width: max-content;
      max-width: 100%;
    }

    body {
      max-width: 100%;
      margin: 0;
    }

    header {
      background: goldenrod;
      position: sticky;
      left: 0;
    }

    main {
      width: 200vw;
      height: 120vh;
      background: gray;
    }
  </style>
</head>

<body>
  <main>
    <header id="header">Header</header>
    <div>Main stuff</div>
    <div>
      Paragraph that is very wide, so wide it should go beyond the width of the
      viewport, loooooooooooooooooooooooooooooooooooooooong
    </div>
  </main>

  <script>
    (function () {
      const scrollbarSize = window.innerWidth - document.documentElement.clientWidth;
      document.getElementById("header").style.setProperty("width", `calc(100vw - ${scrollbarSize}px)`);
    })();</script>
</body>

</html>

The problem is the scrollbar, on mobile devices the scroll bar does not take any space on the right side of the screen.

To do that you have to use javascript, this is an example:

    document.addEventListener("DOMContentLoaded", function () {
        function getScrollBarWidth() {
            let el = document.createElement("div");
            el.style.cssText = "overflow:scroll; visibility:hidden; position:absolute;";
            document.body.appendChild(el);
            let width = el.offsetWidth - el.clientWidth;
            el.remove();
            return width;
        }

        let siteHeader = document.getElementById("siteHeader");
        siteHeader.style.width = `calc(100vw - ${getScrollBarWidth()}px)`;
    });
* {
    box-sizing: border-box;
}

html {
    overflow-y: scroll;
    width: max-content;
}

body {
    margin: 0;
}

header {
    background: goldenrod;
    border-right: 1px solid red;
    position: sticky;
    left: 0;
}
#siteHeader {
    width: 100vw;
}

main {
    width: 200vw;
    height: 120vh;
    background: gray;
}
<header id="siteHeader">Header, should stick horizontally, scroll vertically as usual</header>
<main>
    <div>Main stuff</div>
    <div>Paragraph that is very wide, so wide it should go beyond the width of the viewport, loooooooooooooooooooooooooooooooooooooooong</div>
</main>

Credits to this post for the getScrollBarWidth function

Ok, here is a way to achive this with Javascript.

The idea is to use the visualViewPort dimensions (window.visualViewport.width) to set the width of the Header element.

const setHeaderWidth = () => {
    const scrollbarSize = window.visualViewport.width;
    document.getElementsByTagName("header")[0]
            .style.width = scrollbarSize + 'px';
}

window.onresize = setHeaderWidth;
setHeaderWidth();

CSS is same except that, I removed the width from the Header element.

Here is the complete code:

<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0"><!-- For mobile device scaling -->
<style>
    * {
        box-sizing: border-box;
    }
    html {
        overflow-y: scroll;
        width: max-content;
    }
    body {
        margin: 0;
    }
    header {
        background: goldenrod;
        border-right: 4px solid red;
        position: sticky;
        left: 0;
    }
    main {
        width: 120vw;
        height: 120vh;
        background: gray;
    }
</style>
</head>

<body>
    <header> Header
    </header>
    <main>
        <div>Main stuff</div>
        <div>Paragraph that is very wide, so wide it should go beyond the width of the viewport, loooooooooooooooooooooooooooooooooooooooong</div>
    </main>

    <script>
      const setHeaderWidth = () => {
        const scrollbarSize = window.visualViewport.width;
        document.getElementsByTagName("header")[0]
                .style.width = scrollbarSize + 'px';
      }

      window.onresize = setHeaderWidth;
      setHeaderWidth();
    </script>
</body>

</html>

Here is how it looks on the Desktop:

Here is how it looks on the Mobile:

Update: I added the picture from the real device.

Here is the video scrolling in mobile:

You’re close. I have moved width: max-content to the body and removed the overflow-y: scroll. I also allowed main to size itself automatically and added some content which is both wide and high ... enough to require significant scroll in both directions.

The code below works for me on iOS devices ... here is a hosted version for testing.

<!doctype html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
    body {
      width: max-content;
      margin: 0;
    }
    header {
      background: goldenrod;
      border-right: 1px solid red;
      position: sticky;
      left: 0;
      width: calc(100vw - 15px);
    }
    main {
      background: gray;
    }
    .wide, .long {
      display: flex;
      gap: 1em;
      padding: 1em;
    }
    .long {
      flex-direction: column;
      padding-top: 0;
    }
    .wide > *, .long > * {
      flex-shrink: 0;
      width: 300px;
      height: 150px;
      background: #555;
      color: white;
      font-weight: bold;
      display: flex;
      justify-content: center;
      align-items: center;
    }
  </style>
</head>
<body>
  <header>Header</header>
  <main>
    <div>Main stuff</div>
    <div class="wide">
      <span>1</span>
      <span>2</span>
      <span>3</span>
      <span>4</span>
      <span>5</span>
      <span>6</span>
      <span>7</span>
      <span>8</span>
      <span>9</span>
      <span>10</span>
    </div>
    <div class="long">
      <span>2</span>
      <span>3</span>
      <span>4</span>
      <span>5</span>
      <span>6</span>
      <span>7</span>
      <span>8</span>
      <span>9</span>
      <span>10</span>
    </div>
  </main>
</body>
</html>

Ignore any answers which claim Javascript is required ... it is not.

发布评论