当地址栏隐藏iOS / Android / Mobile Chrome时,背景图片会跳转

时间:2014-07-24 22:14:54

标签: android html ios css twitter-bootstrap-3

我目前正在使用Twitter Bootstrap开发响应式网站。

该网站在移动设备/平板电脑/台式机上拥有全屏背景图片。这些图像使用两个div旋转并逐渐消失。

除了一个问题,它几近完美。在Android上使用iOS Safari,Android浏览器或Chrome时,当用户向下滚动页面并导致地址栏隐藏时,背景会略微跳跃。

该网站位于:http://lt2.daveclarke.me/

在移动设备上访问它并向下滚动,您会看到图像调整大小/移动。

我用于后台DIV的代码如下:

#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;
}

欢迎提出所有建议 - 这已经有一段时间了!

21 个答案:

答案 0 :(得分:99)

这个问题是由URL栏缩小/滑动并改变#bg1和#bg2 div的大小引起的,因为它们是100%高度和“固定”。由于背景图像设置为“覆盖”,因此当包含区域较大时,它将调整图像大小/位置。

根据网站的响应性,背景必须扩展。我接受两种可能的解决方案:

1)将#bg1,#bg2高度设置为100vh。理论上,这是一个优雅的解决方案。但是,iOS有一个vh错误(http://thatemil.com/blog/2013/06/13/viewport-relative-unit-strangeness-in-ios-6/)。我尝试使用max-height来防止这个问题,但它仍然存在。

2)视口大小(由Javascript确定)不受URL栏的影响。因此,Javascript可用于根据视口大小在#bg1和#bg2上设置静态高度。这不是最好的解决方案,因为它不是纯CSS,并且页面加载时会有轻微的图像跳转。但是,考虑到iOS的“vh”错误(在iOS 7中似乎没有修复),这是唯一可行的解​​决方案。

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

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

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

另外,我在iOS和Android中看到了这些调整大小的URL栏的问题。我理解其目的,但他们确实需要思考他们带给网站的奇怪功能和破坏。最新的更改是,您无法再使用滚动技巧“隐藏”iOS或Chrome上的页面加载网址栏。

编辑:虽然上面的脚本可以很好地保持背景不会调整大小,但是当用户向下滚动时会产生明显的差距。这是因为它将背景大小保持为屏幕高度的100%减去URL栏。如果我们向高度添加60px,正如瑞士所说,这个问题就消失了。这确实意味着当URL栏存在时我们无法看到背景图像的底部60px,但它可以防止用户看到间隙。

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

答案 1 :(得分:59)

我发现Jason的回答并不适合我,而且我还在跳跃。 Javascript确保页面顶部没有间隙,但只要地址栏消失/重新出现,背景仍然会跳跃。因此,除了Javascript修复,我还将transition: height 999999s应用于div。这会创建一个持续时间太长的过渡,它实际上会冻结元素。

答案 2 :(得分:22)

我的网站标题上也有类似的问题。

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

这将最终导致Android chrome上的滚动滚动体验,因为.header-container将在url-bar隐藏并且手指从屏幕移除后重新缩放。

<强> CSS-解决方案:

添加以下两行将阻止url-bar隐藏并且仍然可以进行垂直滚动:

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

答案 3 :(得分:14)

问题可以通过媒体查询和一些数学来解决。这是一个面向移民的解决方案:

@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%);
}

由于当网址栏消失时,vh会发生变化,因此您需要以另一种方式确定高度。值得庆幸的是,视口的宽度是恒定的,移动设备只有几个不同的宽高比;如果你可以确定宽度和宽高比,一点数学将给你视口高度与vh 的工作方式完全相同。这是过程

1)为您要定位的宽高比创建一系列媒体查询。

  • 使用设备宽高比而不是宽高比,因为后者会在网址消失时调整大小

  • 我在设备宽高比中添加了'max'来定位最流行之间恰好跟随的任何宽高比。它不会那么精确,但它们只适用于少数用户,并且仍然非常接近正确的vh。

  • 请记住使用水平/垂直的媒体查询,因此对于肖像,您需要翻转数字

2)对于每个媒体查询,将要素的垂直高度百分比乘以宽高比的倒数。

  • 由于您知道宽度和宽度与高度的比例,您只需将您想要的百分比(在您的情况下为100%)乘以高度/宽度的比率。

3)您必须确定网址栏高度,然后从高度减去该值。我还没有找到精确的测量结果,但我在景观中使用了9%的移动设备,而且看起来工作得相当好。

这不是一个非常优雅的解决方案,但其他选项也不是很好,考虑到它们是:

  • 让您的网站对用户造成错误,

  • 元素尺寸不合适,或

  • 使用javascript进行某些基本样式,

缺点是某些设备可能具有与最流行的不同的网址高度或纵横比。但是,使用这种方法,如果只有少数设备遭受几个像素的加/减,这对我来说似乎比刷网时调整网站的每个人都好。

为了更容易,我还创建了一个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%);
  }
}

答案 4 :(得分:12)

此处的所有答案均使用window高度,受网址栏的影响。大家都忘记了screen身高吗?

这是我的jQuery解决方案:

$(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);
  }
});

添加.css()部分正在将不可避免的顶部对齐绝对定位元素更改为底部对齐,因此根本没有跳跃。虽然,我想没有理由不直接将它添加到普通的CSS中。

我们需要用户代理嗅探器,因为桌面上的屏幕高度没有帮助。

此外,这是假设#background是填充窗口的固定位置元素。

对于JavaScript纯粹主义者(警告 - 未经测试):

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;
}

编辑:很抱歉,这并不能直接解决您在多个背景下遇到的具体问题。但这是搜索固定背景在移动设备上跳跃的问题时的第一个结果之一。

答案 5 :(得分:9)

当我试图创建一个覆盖整个视口的入口屏幕时,我也遇到了这个问题。不幸的是,接受的答案不再适用。

1)每次视口大小更改时,高度设置为100vh的元素都会调整大小,包括由(dis)显示的URL栏引起的情况。

2)$(window).height()返回的值也会受到网址栏大小的影响。

一个解决方案是&#34;冻结&#34; the answer by AlexKempton中建议使用transition: height 999999s的元素。缺点是这有效地禁用了对所有视口大小变化的适应性,包括由屏幕旋转引起的变化。

所以我的解决方案是使用JavaScript手动管理视口更改。这使我可以忽略可能由URL栏引起的小变化,并且只对大的变化做出反应。

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);

编辑:我实际上将这项技术与height: 100vh一起使用。页面从一开始就正确呈现,然后javascript启动并开始手动管理高度。这样,在页面加载时(甚至之后),根本没有闪烁。

答案 6 :(得分:8)

我目前使用的解决方案是检查userAgent上的$(document).ready,看看它是否是违规浏览器之一。如果是,请按照以下步骤操作:

  1. 将相关高度设置为当前视口高度,而不是&#39; 100%&#39;
  2. 存储当前视口水平值
  3. 然后,在$(window).resize上,只有在新的水平视口尺寸与其初始值不同时才更新相关的高度值
  4. 存放新的水平&amp;垂直值
  5. 或者,您也可以仅在超出地址栏高度时测试允许垂直调整大小。

    哦,地址栏确实会影响$(window).height。请参阅:Mobile Webkit browser (chrome) address bar changes $(window).height(); making background-size:cover rescale every time JS "Window" width-height vs "screen" width-height?

答案 7 :(得分:4)

我发现了一个非常简单的解决方案,没有使用Javascript:

&#xA;&#xA;
 转换:高度1000000s轻松;&#xA; -webkit-transition:height 1000000s轻松;&#xA; -moz-transition:高度1000000s轻松;&#xA; -o-transition:高度1000000s轻松;&#xA;  
&#xA;&#xA;

所有这一切都是为了延迟运动,使其速度非常慢而且不会引人注意。

&#xA;

答案 8 :(得分:3)

这是我解决此问题的解决方案,我已直接在代码上添加了注释。 我已经测试了这个解决方案并且工作正常。希望我们对每个人都有用同样的问题。

//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
  }); 
};

答案 9 :(得分:3)

我创建了一个使用VH单元的vanilla javascript解决方案。几乎任何地方使用VH都受地址栏的影响,最小化滚动。为了修复页面重绘时显示的jank,我在这里得到了这个js,它将使用VH单位抓取所有元素(如果你给它们类.vh-fix),并给它们内联的像素高度。基本上在我们想要的高度冻结它们。您可以在旋转或视口大小更改时执行此操作以保持响应。

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'
  }
}

这解决了我的所有用例,希望它有所帮助。

答案 10 :(得分:3)

我的解决方案涉及一些javascript。在div上保持100%或100vh(这将避免div在初始页面加载时不出现)。然后在页面加载时,抓住窗口高度并将其应用于相关元素。避免跳跃,因为现在你的div上有一个静态高度。

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

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

答案 11 :(得分:2)

这是我尝试过的最佳解决方案(最简单)。

...这并不能保留地址栏的原生体验

例如,您可以将CSS的高度设置为100%,然后在加载文档时使用javascript将高度设置为px中窗口高度的0.9 *。

例如使用jQuery:

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

不幸的是,没有任何东西可以用于纯CSS:P但也是minfull vhvw在iPhone上有问题 - 使用百分比。

答案 12 :(得分:1)

我用javascript设置我的宽度元素。在我设置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");

这对我有用。我不使用jquery等。因为我希望它尽快加载。

答案 13 :(得分:1)

花了几个小时才了解这个问题的所有细节,并找出最佳实施方案。事实证明,截至2016年4月,只有iOS Chrome出现此问题。我试过Android Chrome和iOS Safari--没有这个修复就没问题。因此,我使用的iOS Chrome修复程序是:

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

答案 14 :(得分:1)

如果背景允许,一个简单的答案,为什么不将background-size:设置为只用媒体查询覆盖设备宽度的内容,并在:after位置旁边使用:fixed;入侵here

IE:背景尺寸:901px;任何小于900像素的屏幕?效果不理想或反应迟钝,但是当我使用抽象BG时,在移动<480px上对我来说很有吸引力。

答案 15 :(得分:0)

我正在使用此解决方法: 修正bg1在页面加载时的高度:

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

然后将一个resize事件监听器附加到Window,它执行以下操作: 如果调整大小后的高度差异小于60px(任何超过url bar高度),则不执行任何操作,如果大于60px,则将bg1的高度再次设置为其父级的高度!完整代码:

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:这个bug很刺激!

答案 16 :(得分:0)

类似于@AlexKempton的回答。 (不能评论抱歉)

我一直在使用长转换延迟来阻止元素调整大小。

例如:

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

与此相关的是它会阻止元素的大小调整,包括在设备上旋转,但是,如果这是一个问题,你可以使用一些JS来检测orientationchange并重置高度。

答案 17 :(得分:0)

对于那些想要倾听窗口的实际内部高度和垂直滚动的人,当Chrome移动浏览器将URL栏从显示转换为隐藏,反之亦然时,我找到的唯一解决方案是设置间隔函数,并测量window.innerHeight与其先前值的差异。

这介绍了这段代码:

&#13;
&#13;
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);
&#13;
&#13;
&#13;

我希望这会很方便。有谁知道更好的解决方案?

答案 18 :(得分:0)

我遇到类似问题时提出的解决方案是每次window.innerHeight事件被触发时将元素的高度设置为touchmove

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);
    }
});

这使得元素始终完全覆盖可用屏幕的100%,不多也不少。即使在地址栏隐藏或显示期间也是如此。

然而,遗憾的是我们必须提出那种简单position: fixed应该有用的补丁。

答案 19 :(得分:0)

借助iOS中CSS自定义属性(变量)的支持,您可以使用JS设置这些属性,并且只能在iOS上使用它们。

[ArgumentException: An item with the same key has already been added.]
   System.ThrowHelper.ThrowArgumentException(ExceptionResource resource) +52
   System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add) +13007142
   System.Collections.Generic.CollectionExtensions.ToDictionaryFast(TValue[] array, Func`2 keySelector, IEqualityComparer`1 comparer) +116
   System.Web.Mvc.ModelBindingContext.get_PropertyMetadata() +149
   System.Web.Mvc.DefaultModelBinder.BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor) +176
   System.Web.Mvc.DefaultModelBinder.BindProperties(ControllerContext controllerContext, ModelBindingContext bindingContext) +101
   System.Web.Mvc.DefaultModelBinder.BindComplexElementalModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Object model) +55
   System.Web.Mvc.DefaultModelBinder.BindComplexModel(ControllerContext controllerContext, ModelBindingContext bindingContext) +1209
   System.Web.Mvc.DefaultModelBinder.BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) +333
   System.Web.Mvc.ControllerActionInvoker.GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor) +343
   System.Web.Mvc.ControllerActionInvoker.GetParameterValues(ControllerContext controllerContext, ActionDescriptor actionDescriptor) +105
   System.Web.Mvc.Async.<>c__DisplayClass3_1.<BeginInvokeAction>b__0(AsyncCallback asyncCallback, Object asyncState) +640
   System.Web.Mvc.Async.WrappedAsyncResult`1.CallBeginDelegate(AsyncCallback callback, Object callbackState) +14
   System.Web.Mvc.Async.WrappedAsyncResultBase`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +128
   System.Web.Mvc.Async.AsyncControllerActionInvoker.BeginInvokeAction(ControllerContext controllerContext, String actionName, AsyncCallback callback, Object state) +346
   System.Web.Mvc.<>c.<BeginExecuteCore>b__152_0(AsyncCallback asyncCallback, Object asyncState, ExecuteCoreState innerState) +27
   System.Web.Mvc.Async.WrappedAsyncVoid`1.CallBeginDelegate(AsyncCallback callback, Object callbackState) +30
   System.Web.Mvc.Async.WrappedAsyncResultBase`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +128
   System.Web.Mvc.Controller.BeginExecuteCore(AsyncCallback callback, Object state) +494
   System.Web.Mvc.<>c.<BeginExecute>b__151_1(AsyncCallback asyncCallback, Object callbackState, Controller controller) +16
   System.Web.Mvc.Async.WrappedAsyncVoid`1.CallBeginDelegate(AsyncCallback callback, Object callbackState) +20
   System.Web.Mvc.Async.WrappedAsyncResultBase`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +128
   System.Web.Mvc.Controller.BeginExecute(RequestContext requestContext, AsyncCallback callback, Object state) +403
   System.Web.Mvc.Controller.System.Web.Mvc.Async.IAsyncController.BeginExecute(RequestContext requestContext, AsyncCallback callback, Object state) +16
   System.Web.Mvc.<>c.<BeginProcessRequest>b__20_0(AsyncCallback asyncCallback, Object asyncState, ProcessRequestState innerState) +54
   System.Web.Mvc.Async.WrappedAsyncVoid`1.CallBeginDelegate(AsyncCallback callback, Object callbackState) +30
   System.Web.Mvc.Async.WrappedAsyncResultBase`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +128
   System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object state) +427
   System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, Object state) +48
   System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, Object extraData) +16
   System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +103
   System.Web.HttpApplication.ExecuteStepImpl(IExecutionStep step) +48
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +159
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`);
}

答案 20 :(得分:0)

我的答案是给所有来这里的人(像我一样)找到由隐藏地址裸露/浏览器界面引起的错误的答案。

隐藏的地址栏导致调整大小事件触发。但是与其他调整大小事件不同,例如切换到横向模式,这不会更改窗口的宽度。因此,我的解决方案是挂接到resize事件中,并检查宽度是否相同。

// 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
    // ...
});