单页webapp屏幕转换,保持滚动位置

时间:2014-08-12 18:58:00

标签: javascript html css web-applications mobile-website

我正在构建一个针对手机的单页Web应用程序。应用程序应该实现“屏幕”之间的转换(像任何其他移动应用程序,例如Facebook,Twitter),并且这些转换应该是动画的(向左滑动)。每个屏幕都必须保留其在过渡之间的滚动位置。

一个明显的解决方案是:

Viewport
+----------+ 
|+--------+| +--------+ +--------+ +--------+
|| DIV1   || | DIV2   | | DIV3   | | DIV4   |
||        || |        | |        | |        |
||        || |        | |        | |        |
||        || |        | |        | |        |
||        || |        | |        | |        |
|+--------+| +--------+ +--------+ +--------+
+----------+

将不同的屏幕放入容器(DIV1,DIV2,...)中,这些容器的样式适合屏幕(position: absolute; width: 100%; height: 100%; top: 0)并具有overflow-x: scroll。容器彼此相邻,过渡就像设置left属性一样简单。

到目前为止很容易。

问题如下:在此实现中,当用户向下滚动时,地址栏不会在移动浏览器中消失。

我在说这个功能: enter image description here

这是因为移动浏览器仅在用户滚动body时才执行此操作 - 而不是body中的容器。有several suggestions用于解决方案,但它们不适用于所有目标平台(Android Chrome和本机浏览器,iPhone Safari),并且非常黑客。我想保留浏览器的原始行为。

出于这个原因 - 显然 - 需要将滚动留在身体上。这意味着容器必须是“全长”(而不是溢出滚动),仍然彼此相邻。如果你想一想,这就是过渡变得困难的地方。

当从DIV1转换到DIV2时,我当前的解决方案有以下步骤:

  1. 将DIV2的top位置放置到窗口的当前scrollTop
  2. 为DIV1和DIV2的left属性设置动画,以便DIV2填满屏幕
  3. 动画完成后将DIV2的top移至0,以便用户无法向后滚动,而不是在此屏幕的顶部。
  4. 将窗口的scrollTop移动到0
  5. 隐藏DIV1(如果它比DIV2长)
  6. 向后转换回DIV1是相似的。这实际上工作得很好(虽然它非常复杂并且使用转换事件监听器)但不幸的是在iOS Safari下步骤3和4之间存在非常难看的闪烁效果,因为它在步骤3之后立即呈现页面而不等待第4步。

    我正在寻找独立于框架(vanilla JS)的解决方案。

5 个答案:

答案 0 :(得分:2)

如果你加载了jquery,你可以做这样的事情

$(document).ready(function() {
if (navigator.userAgent.match(/Android/i)) {
window.scrollTo(0,0); // reset in case prev not scrolled  
var nPageH = $(document).height();
var nViewH = window.outerHeight;
if (nViewH > nPageH) {
  nViewH -= 250;
  $('BODY').css('height',nViewH + 'px');
}
window.scrollTo(0,1);
}

});

对于Iphone,您必须执行以下链接中提到的内容

http://matt.might.net/articles/how-to-native-iphone-ipad-apps-in-javascript/

和野生动物园

https://developer.apple.com/library/safari/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html

希望它能以某种方式帮助你!!

答案 1 :(得分:1)

你的做法非常正确。由于滚动更改位置,您可能会得到闪烁。诀窍是在滚动时将div更改为position: fixed,然后再将其更改回来。

步骤是:

  1. 保存当前滚动垂直位置
  2. 将div更改为position: fixed
  3. 将div的scrollTop更改为0 - scrollPosition
  4. 开始水平过渡
  5. 过渡后

    1. 使用scrollTo()
    2. 更改窗口的滚动位置
    3. 在div上恢复position: fixed,以便自然的浏览器行为起作用。
    4. 这是一个简单的vanilla javascript示例(也称为fiddle):

      <!DOCTYPE html>
      <html>
          <head>
              <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
              <title></title>
      
              <style type="text/css">
                  body {
                      margin: 0;
                      padding: 0;
                  }
      
                  .container {
                      position: absolute;
                      overflow: hidden;
                      width: 320px;
                      height: 5000px;
                  }
      
                  .screen {
                      position: absolute;
                      width: 320px;
                      height: 5000px;
                      transition: left 0.5s;
                  }
      
                  #screen1 {
                      background: linear-gradient(red, yellow);
                  }
      
                  #screen2 {
                      left: 320px;
                      background: linear-gradient(green, blue);
                  }
      
                  #button {
                      position: fixed;
                      left: 20px;
                      top: 20px;
                      width: 100px;
                      height: 50px;
                      background-color: white;
                      color: black;
                  }
              </style>
          </head>
          <body>
              <div class="container">
                  <div id="screen1" class="screen"></div>
                  <div id="screen2" class="screen"></div>
              </div>
      
              <div id="button">transition</div>
      
      
              <script type="text/javascript">
                  var screenActive = 1;
                  var screen1 = document.getElementById('screen1');
                  var screen2 = document.getElementById('screen2');
                  var screen1ScrollTop = 0;
                  var screen2ScrollTop = 0;
      
                  function onClick()
                  {
                      console.log('getting the event');
      
                      if ( screenActive === 1 ) {
                          // will show screen 2
                          screen1ScrollTop = document.body.scrollTop;
      
                          screen1.style.position = 'fixed';
                          screen2.style.position = 'fixed';
                          screen1.style.top = (0 - screen1ScrollTop) + 'px';
                          screen2.style.top = (0 - screen2ScrollTop) + 'px';
      
                          screenActive = 2;
                          screen1.style.left = '-320px';
                          screen2.style.left = '0px';
                      }
                      else {
                          // will show screen 1
                          screen2ScrollTop = document.body.scrollTop;
      
                          screen1.style.position = 'fixed';
                          screen2.style.position = 'fixed';
                          screen1.style.top = (0 - screen1ScrollTop) + 'px';
                          screen2.style.top = (0 - screen2ScrollTop) + 'px';
      
                          screenActive = 1;
                          screen1.style.left = '0px';
                          screen2.style.left = '320px';
                      }
                  }
      
                  function onTransitionEnd(event)
                  {
                      if ( screenActive === 1 ) {
                          window.scrollTo(0, screen1ScrollTop);
                      }
                      else {
                          window.scrollTo(0, screen2ScrollTop);
                      }
      
                      screen1.style.position = 'absolute';
                      screen1.style.top = '0px';
      
                      screen2.style.position = 'absolute';
                      screen2.style.top = '0px';
                  }
      
                  screen1.addEventListener('webkitTransitionEnd', onTransitionEnd);
                  document.getElementById('button').addEventListener('click', onClick);
              </script>
      
          </body>
      </html>
      

      在此示例中,我使用了transitionEnd事件。请记住,如果您在两个动画div上都有此事件,则该事件将触发两次。解决方法是:

      • 如果时间相同,只需在一个div上使用该事件(在示例中使用)
      • 将事件用作所有div,但只是对事件的div进行更改
      • 使用所有div内部的容器制作动画。所以你只需要一个活动。
      • 如果您无法使用transitionEnd事件使用requestAnimationFrame()并通过js手动设置动画

      在本例中,我还使用固定高度的容器进行过渡。如果您的div具有不同的高度,则必须在转换后更改容器高度。理想情况下,从position: fixed恢复之前。

      请注意,将div更改为position: fixed即使它位于包含overflow: hidden的容器中也会显示。在移动webapp的情况下,这不会是一个问题,因为div是在屏幕之外。在PC上你可能需要将另一个div放在另一个div上以隐藏转换的那个。

答案 2 :(得分:0)

使用window.scrollTo(0,1);可以使导航栏消失。这是一个黑客,但它的工作原理。

答案 3 :(得分:0)

为什么不:

<body>
     <div id=header>Header</div>
     <div id=content>Scrollable Content</div>
     <div id=footer>Footer</div>
</body>

然后是CSS:

#header,#footer,#content{
    left:0%;
    right:0%;
}
#header,#footer{
    position:fixed;
}
#header{
    top:0%;
    height:30px;
}
#footer{
    bottom:0%;
    height:30px;
}
#content{
    position:absolute;
    top:30px;
    height:1000px; /*Whatever you need it to be*/
}

触摸屏会响应<body>标记,而非<div>,因此在position:fixed#header设置#footer可让他们保持相对于<body>的位置窗口,无论滚动位置如何,然后当用户滚动内容时,他们滚动{{1}}

编辑:我已将此作为示例实现:

https://www.museri.com/M

访问您的移动设备。

答案 4 :(得分:0)

我想我弄清楚了,这很棘手。

简而言之:在我提出的问题中,我描述了当前在iOS中闪烁的解决方案。在第3点,您需要将position: fixed添加到DIV2。这样它就会#34;坚持&#34;并且你避免在第4点闪烁。然后你需要延迟4点几毫秒(setTimeout,500毫秒为我工作但可能更小)并在{I}之后再次将position: absolute设置为DIV2 {1}}。我不确定你是否需要延迟,但没有它,屏幕仍然会闪烁。

如果有兴趣,我可以稍后发布PoC。

作为旁注,我发现如果回答的人没有完全阅读问题或者完全忽略某些标准(框架独立性,保持原始滚动行为),那么大多数人都会感到非常失望。他们中的大多数人建议我已经在问题中专门链接的解决方案是不可接受的。有些人甚至在被投票时收回。

编辑: dreamlab 在我发布解决方案前几分钟回答。两种解决方案都使用window.scrollTo。他的解决方案也更加详细。他值得赏金。