在触摸设备上检查scrollTop

18

我有一个函数,用于检查用户的滚动位置,以便在他们滚过页眉后使菜单固定。

function checkScrolling() {
    if( $(window).scrollTop() > $('.masthead').height() ) { // we check against ACTUAL height
        $('.menu').addClass('fixed');
    }else {
        $('.menu').removeClass('fixed');
    }
}

这里是桌面和触摸屏事件监听器:

$(document).bind('touchstart touchend touchcancel touchleave touchmove', function(e){
    checkScrolling();
});

$(window).scroll(function(){
    checkScrolling();
});

然而,触摸事件只能在触摸移动期间可靠地使菜单固定。如果我用向上或向下的快速滑动滚动,菜单在变为固定或非固定之前会有一些延迟。

有什么想法可以解决这个问题吗?在此处查看代码示例:http://dev.driz.co.uk/mobileMasthead.html(已根据以下某些答案进行了修改,但在 iPad 或 iPhone 上仍无法正常工作)

更新: 阅读相关主题的内容后发现,像检查滚动位置之类的 JS 操作不会在滚动期间发生...但是......我注意到http://www.facebook.com/home/在我的 iPad 上测试时没有这个问题。因此,肯定有可能实现这种效果,而不使用诸如 iScroll 或类似的自定义 JavaScript 滚动条。


为什么您使用触摸事件而不是 touchmove ?我认为 touchmoveonScroll 应该能够满足您需要检查粘性导航的场景。如果尝试使用 $('body').bind()$(window).bind() 而不是 $(document).bind(),是否会得到不同的结果?我在定位页面的较小部分时使用了 touchmove,并且没有延迟返回信息。 - Jason Lydon
无论是touchmove还是scroll,都没有延迟。问题在于scrollTop值没有改变,因为它直到用户移动位置后才会被认可。无论我绑定什么,情况都是一样的。 - Cameron
确保在滚动时JavaScript仍在运行。我曾经在(Android 2.x)遇到过类似的问题。只有当我停止滚动时,滚动事件才会被捕获。不确定这些天是否都支持。只需添加一个HTML元素,该元素始终以毫秒为单位更新时间,并仔细检查JavaScript本身是否正在运行。这取决于操作系统/浏览器。 - Erik Nijland
也许我没有理解你的目标,但是你考虑过一开始就将标头位置固定,并允许内容在另一个DIV元素下滚动吗?或者,在进行滚动检查时,固定其位置直到用户停止滚动?这只是一些想法... - exoboy
如果你想要更好的性能,就不要使用jQuery了。所有这些功能都可以轻松地使用原生JavaScript实现。 - avetisk
9个回答

7
也许我误解了问题,但为了确保高速滚动的功能,为什么不采用纯CSS方式(即伪造“花哨”的效果):

老派方法(快速但原始)

HTML

<div id="menu2"></div>
    <div class="scroll" id="scroller">
        <div id="overlay"></div>
        <div id="menu"></div>
    </div>

使用简单的CSS:

html, body { overflow: hidden; height: 100% }
body { margin:0; padding:0; }

 .scroll {
     -webkit-overflow-scrolling: touch;
     height:960px;
     width:640px;
 }

 #menu2 {
     width:640px;
     height:20px;
     background:red;
     position:absolute;
     z-index:-1;
 }

 #menu {
     width:100%;
     height:20px;
     background:red;
     z-index:0;
 }

你可以在这里看到一个运行的示例。

这可能有点原始:但是嘿!它有效! :)

新颖但慢速的方法

查看提供的所有其他答案。

但请注意,众所周知,在移动设备上结合JavaScript和滚动会导致许多速度问题。

这就是为什么我认为简单的CSS方法可能更好。

如果您想了解有关JavaScript和移动设备上的滚动(以及其他功能)的信息,则有两篇文章值得推荐阅读:


我在iPhone上测试过了,但我非常确定它在iPad上也能正常工作,因为这只是一些CSS。 - Jean-Paul

5
Facebook不使用JavaScript,而是使用纯CSS:
position: -webkit-sticky;

如果我没记错的话,这会使元素在滚动时始终固定在其父容器的顶部。


我的浏览器不支持该CSS(最新版Chrome),但Facebook网站可以正常工作。所以我会认为他们同时使用JS和CSS?另外,这怎么能解决我的问题呢?因为如果用户滚动过标题栏,我仍然需要应用sticky定位,而标题栏的高度取决于其内容。谢谢。 - Cameron
@Cameron 当然他们会在不支持的浏览器中使用JS :) 你菜单的父元素是body,所以无论滚动多远,它都会始终固定在页面顶部。这正是你的脚本所做的。 - a better oliver
@Cameron 我刚刚检查了你页面的源代码。不需要使用 sticky 类。只需将样式添加到常规类中,让浏览器决定即可 ;) 这样你也不需要 Modernizr.csspositionsticky 了。你可以查看我的示例 - a better oliver

2
您只需要将滚动事件附加到自定义容器,而不是窗口、文档或正文。在iOS上,您无法在这些硬件加速的窗口滚动行为期间编程地做出反应。以下是一个示例:一个包装器。
<div id="content">

一些不太相关的CSS:

html,body,#content {
    width:100%;
    height:100%;
}
#content {
    background-color: lightblue;
    overflow:scroll;
}

将监听器附加到容器:
function checkScrolling() {
    if ($('#content').scrollTop() > mastheadHeight) {
        menu.addClass('fixed');
    } else {
        menu.removeClass('fixed');
    }
}

$('#content').scroll(function () {
    checkScrolling();
});

您可以在此处查看JS-only回退的工作原理:

http://jsfiddle.net/jaibuu/YqPzS/

直接URL:http://fiddle.jshell.net/jaibuu/YqPzS/show/


问题在于,当浏览器完成了灵活滚动时,jQuery滚动事件只会在触摸设备上触发。 - Fabian S.

2
我曾尝试在移动版网站上实现粘性标题,但遇到了一整套问题。
首要问题是移动设备上的滚动事件不像桌面设备那样频繁触发。通常情况下,在页面停止时才会触发滚动事件。我不知道确切原因,但我可以猜测这是因为移动浏览器将这些任务暂时“发送”给GPU,而CPU无法及时更新JS对象与画布发生的变化(但这只是我的猜测)。新版本的iOS使这方面得到改善,可能滚动事件在iPhone上能正常工作。
您应该使用触摸事件。这使得您需要为支持触摸输入的设备编写单独的代码版本。因此,必须寻找可靠的测试平台的方法。
但是,触摸事件也很棘手,特别是在Android上。我认为,在touchmove事件的回调函数中,我将能够确定当前坐标并从那个点开始继续操作。
但是,在Android上存在这个问题: https://code.google.com/p/android/issues/detail?id=5491,简而言之,在触摸操作开始时,touchmove只会触发一两次,之后不再触发。这使得它完全无用。人们建议使用preventDefault(),但您将无法再滚动。
因此,我最终想到了重新从头开始使用CSS变换来实现滚动。以下是我的成果: http://jsbin.com/elaker/23 在您的设备上打开该链接,在您的桌面上打开http://jsbin.com/elaker/23/edit:您将能够编辑代码,并且它会实时更新您的设备,非常方便。
注意: 我想提醒您,这种CSS滚动方法还有一些已知问题尚未解决:例如,有时无法控制滚动超出顶部或底部边界,或者如果您只是轻触(不移动),它仍然会滚动。此外,臭名昭著的URL栏将不会隐藏。因此,还有工作要做。

1
处理滚动事件的一个好方法是不要将检查附加到滚动事件上,这需要大量资源,并且在旧浏览器上效果不佳。幸运的是,如果只执行时间循环,就可以获得更多的控制权。从代码上看,它看起来像这样:(Twitter使用)
var MyMenu = $('#menu'),
    didScroll = false;

$(window).scroll(function() {
    didScroll = true;
});

setInterval(function() {
    if ( didScroll ) {
        didScroll = false;
        //Do your check here
        checkScrolling();
    }
}, 180);

你应该将$('.masthead').height()放在checkScrolling函数之外(更高的作用域),因为这种操作需要大量资源,总是要求jQuery“选择你的元素”并计算其大小最终会使你的应用程序变得缓慢。
  var headerHeight = $('.masthead').height()
  function checkScrolling()
  .....

最后一件事,你可以更改间隔属性的值(现在是180(略大于60hz-屏幕刷新时间),如果你希望你的应用程序表现更好但略微不准确,你可以将这个值增加)

感谢John Resig和twitter: http://ejohn.org/blog/learning-from-twitter/

希望有所帮助 Ajios!


1
在这里尝试了一下:http://dev.driz.co.uk/mobileMasthead.html,如果你在iPad或iPhone上测试,那么你会发现菜单只有在你停止滚动后才会固定或取消固定。所以似乎使用setInterval并没有做出任何不同。 - Cameron

1
setScrollTop: function(inTop) {
    var top = Math.max(0,inTop);

    if (wm.isMobile) { // should always be true for touch events 
        top = Math.min(top, this.listNode.clientHeight - this.listNodeWrapper.clientHeight);

        if (dojo.isWebKit) {
            this.listNode.style.WebkitTransform = "translate(0,-" + top + "px)";
        } else if (dojo.isMoz) {
            this.listNode.style.MozTransform = "translate(0,-" + top + "px)";
        } else if (dojo.isOpera) {
            this.listNode.style.OTransform = "translate(0,-" + top + "px)";
        } else if (dojo.isIE) {
            this.listNode.style.MsTransform = "translate(0,-" + top + "px)";
        }
        this.listNode.style.transform = "translate(0,-" + top + "px)";
        this._scrollTop = top;
        this._onScroll();
    } else {
        this.listNode.scrollTop = top + "px";
    }
},

1
我刚刚测试了您的函数效率。您应该尝试这个。
function checkScrolling() {         
    if( $(window).scrollTop() > mastheadHeight )menu.css('position','fixed');
    else menu.css('position','');
}

这将减少addClass和removeClass的函数调用,从而降低执行时间。如果您想进一步减少执行时间,则最好使用纯JavaScript。

1
$(document).ready(function(){
     var p = $("#stop").offset().top;

    $(window).scroll(function(){
        if(p<$(window).scrollTop()){
            console.log("div reached");
            $("#stop").css({position:"fixed",top:0});
        }
        else{
            console.log("div out");
            $("#stop").css({position:"static"});
        }

    })
});

我认为这会对你有所帮助。
完整的代码在jsfiddle.net上这里
我已经在http://alexw.me/ipad2/上使用在线ipad2模拟器中的safari测试了它,并且它在那里运行良好。因此,我认为它也可以在真实的iPad上运行。

1

您需要触摸事件才能触发它吗?现代设备应该返回$(window).scroll()事件和scrollTop值。在旧版Android和iOS5之前(我想是这样),position:fixed:的效果不如预期,因为视口的工作方式。但在所有新版本中已经得到了纠正。

为了进一步调试设备,我强烈推荐使用Adobe Edge Inspect。您可以console.log scrollTop并查看您关心的设备是否没有任何技巧地正确工作。您将获得其免费版本所需的一切。


问题在于scrollTop值直到滚动停止后才会改变。不知何故,Facebook.com/home已经解决了这个问题,而没有使用自定义滚动条。 - Cameron

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接