ChatGPT解决这个技术问题 Extra ChatGPT

Background image jumps when address bar hides iOS/Android/Mobile Chrome

I'm currently developing a responsive site using Twitter Bootstrap.

The site has a full screen background image across mobile/tablet/desktop. These images rotate and fade through each, using two divs.

It's nearly perfect, except one issue. Using iOS Safari, Android Browser or Chrome on Android the background jumps slightly when a user scrolls down the page and causes the address bar to hide.

The site is here: http://lt2.daveclarke.me/

Visit it on a mobile device and scroll down and you should see the image resize/move.

The code I'm using for the background DIV is as follows:

#bg1 {
    background-color: #3d3d3f;
    background-repeat:no-repeat;
    background-attachment:fixed;
    background-position:center center;
    -webkit-background-size: cover;
    -moz-background-size: cover;
    -o-background-size: cover;
    background-size: cover; position:fixed;
    width:100%;
    height:100%;
    left:0px;
    top:0px;
    z-index:-1;
    display:none;
}

All suggestions welcome - this has been doing my head in for a while!!

Your link requires auth... also, show us some code pls!
New link added & shown code for the background positioning etc.
If you are experiencing this issue, you might want to check developers.google.com/web/updates/2016/12/url-bar-resizing. This issue has (sort of) been addressed now, but still affects elements that have position: fixed.
@AdamReis Finally! Thanks for posting

R
Roko C. Buljan

This issue is caused by the URL bars shrinking/sliding out of the way and changing the size of the #bg1 and #bg2 divs since they are 100% height and "fixed". Since the background image is set to "cover" it will adjust the image size/position as the containing area is larger.

Based on the responsive nature of the site, the background must scale. I entertain two possible solutions:

1) Set the #bg1, #bg2 height to 100vh. In theory, this an elegant solution. However, iOS has a vh bug (http://thatemil.com/blog/2013/06/13/viewport-relative-unit-strangeness-in-ios-6/). I attempted using a max-height to prevent the issue, but it remained.

2) The viewport size, when determined by Javascript, is not affected by the URL bar. Therefore, Javascript can be used to set a static height on the #bg1 and #bg2 based on the viewport size. This is not the best solution as it isn't pure CSS and there is a slight image jump on page load. However, it is the only viable solution I see considering iOS's "vh" bugs (which do not appear to be fixed in iOS 7).

var bg = $("#bg1, #bg2");

function resizeBackground() {
    bg.height($(window).height());
}

$(window).resize(resizeBackground);
resizeBackground();

On a side note, I've seen so many issues with these resizing URL bars in iOS and Android. I understand the purpose, but they really need to think through the strange functionality and havoc they bring to websites. The latest change, is you can no longer "hide" the URL bar on page load on iOS or Chrome using scroll tricks.

EDIT: While the above script works perfectly for keeping the background from resizing, it causes a noticeable gap when users scroll down. This is because it is keeping the background sized to 100% of the screen height minus the URL bar. If we add 60px to the height, as swiss suggests, this problem goes away. It does mean we don't get to see the bottom 60px of the background image when the URL bar is present, but it prevents users from ever seeing a gap.

function resizeBackground() {
    bg.height( $(window).height() + 60);
}

Thanks for this. I'll give it a whirl tomorrow and let you know how I get on :)
This is wonderful. I've implemented your second solution, which worked nearly perfectly with just a few pixel gap between the footer & the background image. I changed your 4th line of code to: bg.height(jQuery(window).height() + 60); which works beautifully across all my devices! Thanks :)
the iOS vh bug is fixed in iOS8 but the problem on Android isn't. I'm using vhunits and of course 100vh is bigger when the address bar is hidden. It's stupid that this jump occurs after the scrolling stops. It just looks buggy. I ended up using transition on the height to make it seem intentional.
I was able to solve this problem by moving the background from the body to a wrapper <div> wrapping the entire page's content. No jumping backgrounds on iOS or Android!
The JS solution is not working for me (Android). Confirmed that the script is setting the height, but the issue persists.
A
AlexKempton

I found that Jason's answer wasn't quite working for me and I was still getting a jump. The Javascript ensured there was no gap at the top of the page but the background was still jumping whenever the address bar disappeared/reappeared. So as well as the Javascript fix, I applied transition: height 999999s to the div. This creates a transition with a duration so long that it virtually freezes the element.


You sir deserve +1 for the real out-of-the box thinking. Unfortunately this prevents adjusting the div size to any screen size changes, including for examples those caused by screen rotation.
+1 for creativity! May have to use this for now as the issue compounds if you use vw or vh to set the font size inside of that parent container.
@tobik It is still possible to reduce the jump caused by viewport change by setting a 0.5 second or so transition
try to rotate the phone now
@tobik this actually makes it more a feature than a bug ;) good thinking
m
mat

I've got a similar issue on a header of our website.

html, body {
    height:100%;
}
.header {
    height:100%;
}

This will end up in a jumpy scrolling experience on android chrome, because the .header-container will rescale after the url-bar hides and the finger is removed from screen.

CSS-Solution:

Adding the following two lines, will prevent that the url-bar hides and vertical scrolling is still possible:

html {
    overflow: hidden;
}
body {
    overflow-y: scroll;
    -webkit-overflow-scrolling:touch;
}

Yeah this does solve it like you say, but it sucks that the nav bar is always showing. On some phones this can take up a chunk of valuable screen space!
That's true, to prevent this, you can additionally use a JavaScript-Workaround. But this example works without JS at all.
Scroll is disabled completely when I tried this solution.
Thanks, that worked great for my situation! Should be mentioned for anyone else that might miss it that the html and body tags have to be set to 100% height for this to work (I initially just skimmed your answer at first and jumped to the first mention of "solution").
-webkit-overflow-scrolling should not be used on production sites. developer.mozilla.org/en-US/docs/Web/CSS/…
J
Jordan Los

The problem can be solved with a media query and some math. Here's a solution for a portait orientation:

@media (max-device-aspect-ratio: 3/4) {
  height: calc(100vw * 1.333 - 9%);
}
@media (max-device-aspect-ratio: 2/3) {
  height: calc(100vw * 1.5 - 9%);
}
@media (max-device-aspect-ratio: 10/16) {
  height: calc(100vw * 1.6 - 9%);
}
@media (max-device-aspect-ratio: 9/16) {
  height: calc(100vw * 1.778 - 9%);
}

Since vh will change when the url bar dissapears, you need to determine the height another way. Thankfully, the width of the viewport is constant and mobile devices only come in a few different aspect ratios; if you can determine the width and the aspect ratio, a little math will give you the viewport height exactly as vh should work. Here's the process

1) Create a series of media queries for aspect ratios you want to target.

use device-aspect-ratio instead of aspect-ratio because the latter will resize when the url bar dissapears

I added 'max' to the device-aspect-ratio to target any aspect ratios that happen to follow in between the most popular. THey won't be as precise, but they will be only for a minority of users and will still be pretty close to the proper vh.

remember the media query using horizontal/vertical , so for portait you'll need to flip the numbers

2) for each media query multiply whatever percentage of vertical height you want the element to be in vw by the reverse of the aspect ratio.

Since you know the width and the ratio of width to height, you just multiply the % you want (100% in your case) by the ratio of height/width.

3) You have to determine the url bar height, and then minus that from the height. I haven't found exact measurements, but I use 9% for mobile devices in landscape and that seems to work fairly well.

This isn't a very elegant solution, but the other options aren't very good either, considering they are:

Having your website seem buggy to the user,

having improperly sized elements, or

Using javascript for some basic styling,

The drawback is some devices may have different url bar heights or aspect ratios than the most popular. However, using this method if only a small number of devices suffer the addition/subtraction of a few pixels, that seems much better to me than everyone having a website resize when swiping.

To make it easier, I also created a SASS mixin:

@mixin vh-fix {
  @media (max-device-aspect-ratio: 3/4) {
    height: calc(100vw * 1.333 - 9%);
  }
  @media (max-device-aspect-ratio: 2/3) {
    height: calc(100vw * 1.5 - 9%);
  }
  @media (max-device-aspect-ratio: 10/16) {
    height: calc(100vw * 1.6 - 9%);
  }
  @media (max-device-aspect-ratio: 9/16) {
    height: calc(100vw * 1.778 - 9%);
  }
}

Very creative! Not a fan of the 9% guess though.
Very simple and smart! You can add a parameter to vh-fix to simulate the original behavior: @mixin vh-fix($vh: 100) => height: calc(100vw * (1.333 * $vh / 100) - 9%)
The above should be: height: calc(100vw * (1.333 * #{$vh} / 100) - 9%)
For my use case, this worked really well, and even better once I removed the -9%.
c
cr0ybot

All of the answers here are using window height, which is affected by the URL bar. Has everyone forgotten about screen height?

Here's my jQuery solution:

$(function(){

  var $w = $(window),
      $background = $('#background');

  // Fix background image jump on mobile
  if ((/Android|iPhone|iPad|iPod|BlackBerry/i).test(navigator.userAgent || navigator.vendor || window.opera)) {
    $background.css({'top': 'auto', 'bottom': 0});

    $w.resize(sizeBackground);
    sizeBackground();
  }

  function sizeBackground() {
     $background.height(screen.height);
  }
});

Adding the .css() part is changing the inevitably top-aligned absolute positioned element to bottom aligned, so there is no jump at all. Although, I suppose there's no reason not to just add that directly to the normal CSS.

We need the user agent sniffer because screen height on desktops would not be helpful.

Also, this is all assuming #background is a fixed-position element filling the window.

For the JavaScript purists (warning--untested):

var background = document.getElementById('background');

// Fix background image jump on mobile
if ((/Android|iPhone|iPad|iPod|BlackBerry/i).test(navigator.userAgent || navigator.vendor || window.opera)) {
  background.style.top = 'auto';
  background.style.bottom = 0;

  window.onresize = sizeBackground;
  sizeBackground();
}

function sizeBackground() {
  background.style.height = screen.height;
}

EDIT: Sorry that this does not directly answer your specific problem with more than one background. But this is one of the first results when searching for this problem of fixed backgrounds jumping on mobile.


I tried a million of these answers and this is by far the best. I originally was trying to use this on just as that had the background. Using a div to wrap the whole page caused other issues. Using an empty div with z-index: -1 as the background seemed to be the best solution combined with the above. I took out the mobile detection since doing this on the desktop doesn't hurt for my site. No math (which can guess the size of the tabs wrong), no handler needed for resize. Background is just the whole screen, anchored to the bottom. Done. Love it!
This solution seems to lock the browser navbar in place on latest Chrome today.
s
simmer

I ran into this issue as well when I was trying to create an entrance screen that would cover the whole viewport. Unfortunately, the accepted answer no longer works.

1) Elements with the height set to 100vh get resized every time the viewport size changes, including those cases when it is caused by (dis)appearing URL bar.

2) $(window).height() returns values also affected by the size of the URL bar.

One solution is to "freeze" the element using transition: height 999999s as suggested in the answer by AlexKempton. The disadvantage is that this effectively disables adaptation to all viewport size changes, including those caused by screen rotation.

So my solution is to manage viewport changes manually using JavaScript. That enables me to ignore the small changes that are likely to be caused by the URL bar and react only on the big ones.

function greedyJumbotron() {
    var HEIGHT_CHANGE_TOLERANCE = 100; // Approximately URL bar height in Chrome on tablet

    var jumbotron = $(this);
    var viewportHeight = $(window).height();

    $(window).resize(function () {
        if (Math.abs(viewportHeight - $(window).height()) > HEIGHT_CHANGE_TOLERANCE) {
            viewportHeight = $(window).height();
            update();
        }
    });

    function update() {
        jumbotron.css('height', viewportHeight + 'px');
    }

    update();
}

$('.greedy-jumbotron').each(greedyJumbotron);

EDIT: I actually use this technique together with height: 100vh. The page is rendered properly from the very beginning and then the javascript kicks in and starts managing the height manually. This way there is no flickering at all while the page is loading (or even afterwards).


I've used something similar, but rather than setting height: 100vh in the initial CSS, I set min-height:100vh and then on window resize I calculate the min-height and set it to the view-port height. This is handy when your site is viewed on tiny screens and the content is too large to fit on the viewport. A debounce/throttle setTimeout on the resize event helps improve performance on some browsers (resize tends to fire very often)
I think that at a certain point I also tried using min-height instead but then I run into a problem when I needed to vertically center the inner content. Somehow vertical centering can get tricky when the outer element has height set to auto. So I came up with a fixed minimum height that should be enough to contain any kind of possible content and set it in the initial CSS. Since min-height has a higher priority than height, it works pretty well.
Ah yeah, I had that same problem, but fixed it by changing the jumbotron to display: table and then setting the internal container to display: table-cell with vertical-align: middle. Worked like a charm.
Really? Because I tried the same approach but I failed. I don't really remember what the problem was but this might be relevant: stackoverflow.com/questions/7790222/…
Just make sure you avoid percentage padding or margins. Caused a few weird rendering issues on IE8, although modern browsers were OK.
C
Community

The solution I'm currently using is to check the userAgent on $(document).ready to see if it's one of the offending browsers. If it is, follow these steps:

Set the relevant heights to the current viewport height rather than '100%' Store the current viewport horizontal value Then, on $(window).resize, only update the relevant height values if the new horizontal viewport dimension is different from it's initial value Store the new horizontal & vertical values

Optionally, you could also test permitting vertical resizes only when they are beyond the height of the address bar(s).

Oh, and the address bar does affect $(window).height. See: Mobile Webkit browser (chrome) address bar changes $(window).height(); making background-size:cover rescale every time JS "Window" width-height vs "screen" width-height?


I haven't tried this but would your first point not work for iOS? (thatemil.com/blog/2013/06/13/…)
I was fixing he value in the first step, but I'm surprised that you found Jason's response to solve the problem. As noted I found, along with the other user in the earlier question I linked, that the $(window).height() method value DOES change when the address bar in chrome on android is or is not visible. I threw together this little site that just reveals the $(window).height() result in an alert, and it definitely provides different values on my nexus 4 in chrome when the address bar is/is not visible: stormy-meadow-5908.herokuapp.com
This 100% fixes the issue on my Android device although I'm seeing a similar issue on my iPad which this doesn't appear to fix which I'm still looking into.
Are you able to provide some code for your solution and I'll try it out?
P
Pav Sidhu

I found a really easy solution without the use of Javascript:

transition: height 1000000s ease;
-webkit-transition: height 1000000s ease;
-moz-transition: height 1000000s ease;
-o-transition: height 1000000s ease;

All this does is delay the movement so that it's incredibly slow that it's not noticeable.


@Tyguy7 It works fine whenever your height is not set to a fixed value, i.e. 100%, 100vh. IE8 is the only major browser which doesn't support transitions.
Great and simple solution!
This works great until you're on a device where you can change from portrait to landscape. When this happens the design is ruined.
@NagyNick that wasn't such a big issue for me. You could detect the orientation change in Javascript and adjust the background image.
T
Todd Miller

My solution involved a bit of javascript. Keep the 100% or 100vh on the div (this will avoid the div not appearing on initial page load). Then when the page loads, grab the window height and apply it to the element in question. Avoids the jump because now you have a static height on your div.

var $hero = $('#hero-wrapper'),
    h     = window.innerHeight;

$hero.css('height', h);

G
Geek Devigner

I created a vanilla javascript solution to using VH units. Using VH pretty much anywhere is effected by address bars minimizing on scroll. To fix the jank that shows when the page redraws, I've got this js here that will grab all your elements using VH units (if you give them the class .vh-fix), and give them inlined pixel heights. Essentially freezing them at the height we want. You could do this on rotation or on viewport size change to stay responsive.

var els = document.querySelectorAll('.vh-fix')
if (!els.length) return

for (var i = 0; i < els.length; i++) {
  var el = els[i]
  if (el.nodeName === 'IMG') {
    el.onload = function() {
      this.style.height = this.clientHeight + 'px'
    }
  } else {
    el.style.height = el.clientHeight + 'px'
  }
}

This has solved all my use cases, hope it helps.


l
lino

this is my solution to resolve this issue, i have added comments directly on code. I've tested this solution and works fine. Hope we will be useful for everyone has the same problem.

//remove height property from file css because we will set it to first run of page
//insert this snippet in a script after declarations of your scripts in your index

var setHeight = function() {    
  var h = $(window).height();   
  $('#bg1, #bg2').css('height', h);
};

setHeight(); // at first run of webpage we set css height with a fixed value

if(typeof window.orientation !== 'undefined') { // this is more smart to detect mobile devices because desktop doesn't support this property
  var query = window.matchMedia("(orientation:landscape)"); //this is is important to verify if we put 
  var changeHeight = function(query) {                      //mobile device in landscape mode
    if (query.matches) {                                    // if yes we set again height to occupy 100%
      setHeight(); // landscape mode
    } else {                                                //if we go back to portrait we set again
      setHeight(); // portrait mode
    }
  }
  query.addListener(changeHeight);                          //add a listner too this event
} 
else { //desktop mode                                       //this last part is only for non mobile
  $( window ).resize(function() {                           // so in this case we use resize to have
    setHeight();                                            // responsivity resisizing browser window
  }); 
};

You are calling setHeight() inside of changeHeight Method if the query matches and also if it does not match. So just remove the if statement as it is not necessary.
佚名

This is the best solution (simplest) above everything I have tried.

...And this does not keep the native experience of the address bar!

You could set the height with CSS to 100% for example, and then set the height to 0.9 * of the window height in px with javascript, when the document is loaded.

For example with jQuery:

$("#element").css("height", 0.9*$(window).height());

)

Unfortunately there isn't anything that works with pure CSS :P but also be minfull that vh and vw are buggy on iPhones - use percentages.


J
Johan Hoeksma

I set my width element, with javascript. After I set the de vh.

html

<div id="gifimage"><img src="back_phone_back.png" style="width: 100%"></div>

css

#gifimage {
    position: absolute;
    height: 70vh;
}

javascript

imageHeight = document.getElementById('gifimage').clientHeight;

// if (isMobile)       
document.getElementById('gifimage').setAttribute("style","height:" + imageHeight + "px");

This works for me. I don't use jquery ect. because I want it to load as soon as posible.


l
littlequest

It took a few hours to understand all the details of this problem and figure out the best implementation. It turns out as of April 2016 only iOS Chrome has this problem. I tried on Android Chrome and iOS Safari-- both were fine without this fix. So the fix for iOS Chrome I'm using is:

$(document).ready(function() {
   var screenHeight = $(window).height();
   $('div-with-background-image').css('height', screenHeight + 'px');
});

D
Devraj Gadhavi

Similar to @AlexKempton answer. (couldn't comment sorry)

I've been using long transition delays to prevent the element resizing.

eg:

transition: height 250ms 600s; /*10min delay*/

The tradeoff with this is it prevents resizing of the element including on device rotating, however, you could use some JS to detect orientationchange and reset the height if this was an issue.


E
EasterMedia

Simple answer if your background allows, why not set background-size: to something just covering device width with media queries and use alongside the :after position: fixed; hack here.

IE: background-size: 901px; for any screens <900px? Not perfect or responsive but worked a charm for me on mobile <480px as i'm using an abstract BG.


E
Erik J.

My answer is for everyone who comes here (like I did) to find an answer for a bug caused by the hiding address bare / browser interface.

The hiding address bar causes the resize-event to trigger. But different than other resize-events, like switching to landscape mode, this doesn't change the width of the window. So my solution is to hook into the resize event and check if the width is the same.

// Keep track of window width
let myWindowWidth = window.innerWidth;

window.addEventListener( 'resize', function(event) {

    // If width is the same, assume hiding address bar
    if( myWindowWidth == window.innerWidth ) {
         return;
    }

    // Update the window width
    myWindowWidth = window.innerWidth;

    // Do your thing
    // ...
});

A
AadityaTaparia

I am using this workaround: Fix bg1's height on page load by:

var BG = document.getElementById('bg1');
BG.style.height = BG.parentElement.clientHeight;

Then attach a resize event listener to Window which does this: if difference in height after resizing is less than 60px (anything more than url bar height) then do nothing and if it is greater than 60px then set bg1's height again to its parent's height! complete code:

window.addEventListener("resize", onResize, false);
var BG = document.getElementById('bg1');
BG.style.height = BG.parentElement.clientHeight;
var oldH = BG.parentElement.clientHeight;

function onResize() {
    if(Math.abs(oldH - BG.parentElement.clientHeight) > 60){
      BG.style.height = BG.parentElement.clientHeight + 'px';
      oldH = BG.parentElement.clientHeight;
    }
}

PS: This bug is so irritating!


É
Édouard Mercier

For those who would like to listen to the actual inner height and vertical scroll of the window while the Chrome mobile browser is transition the URL bar from shown to hidden and vice versa, the only solution that I found is to set an interval function, and measure the discrepancy of the window.innerHeight with its previous value.

This introduces this code:

var innerHeight = window.innerHeight; window.setInterval(function () { var newInnerHeight = window.innerHeight; if (newInnerHeight !== innerHeight) { var newScrollY = window.scrollY + newInnerHeight - innerHeight; // ... do whatever you want with this new scrollY innerHeight = newInnerHeight; } }, 1000 / 60);

I hope that this will be handy. Does anyone knows a better solution?


P
Paul

The solution I came up with when having similar problem was to set height of element to window.innerHeight every time touchmove event was fired.

var lastHeight = '';

$(window).on('resize', function () {
    // remove height when normal resize event is fired and content redrawn
    if (lastHeight) {
        $('#bg1').height(lastHeight = '');
    }
}).on('touchmove', function () {
    // when window height changes adjust height of the div
    if (lastHeight != window.innerHeight) {
        $('#bg1').height(lastHeight = window.innerHeight);
    }
});

This makes the element span exactly 100% of the available screen at all times, no more and no less. Even during address bar hiding or showing.

Nevertheless it's a pity that we have to come up with that kind of patches, where simple position: fixed should work.


S
Stephen Saucier

With the support of CSS custom properties (variables) in iOS, you can set these with JS and use them on iOS only.

const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
if (iOS) {
  document.body.classList.add('ios');
  const vh = window.innerHeight / 100;
  document.documentElement.style
    .setProperty('--ios-10-vh', `${10 * vh}px`);
  document.documentElement.style
    .setProperty('--ios-50-vh', `${50 * vh}px`);
  document.documentElement.style
    .setProperty('--ios-100-vh', `${100 * vh}px`);
}
body.ios {
    .side-nav {
        top: var(--ios-50-vh);
    }
    section {
        min-height: var(--ios-100-vh);
        .container {
            position: relative;
            padding-top: var(--ios-10-vh);
            padding-bottom: var(--ios-10-vh);
        }
    }
}

关注公众号,不定期副业成功案例分享
Follow WeChat

Success story sharing

Want to stay one step ahead of the latest teleworks?

Subscribe Now