使用jQuery动画scrollTop闪烁

时间:2013-12-09 22:44:46

标签: javascript jquery animation underscore.js

我的问题

我正在为一个客户制作一个垂直网站,当希望在视口中看到大部分元素时,该网站希望窗口“捕捉”到最近的页面。因此,如果页面可见85%,则应滚动至100%可见。

我的问题是,当偶尔滚动到视口的顶部或底部时,视口将“粘住”到第一个或最后一个元素,从而阻止了一些滚动事件并导致高度明显的闪烁。

这里有一个工作小提琴:http://jsfiddle.net/RTzu8/1/ 要重现错误,请使用滚动条滚动到页面底部。然后,用鼠标滚轮向上滚动。你应该看到闪烁。有时需要一些刷新或尝试,但问题是高度可重复的。

我对可能导致此问题的原因感到茫然。请参阅下面的代码,以及我迄今为止试图阻止它的代码。


我的代码

为了实现我的捕捉,我需要检测元素是否是可见的特定百分比。所以,我在下面添加了一个jQuery函数isNearScreen。我已经彻底测试了这个功能,据我所知它可以返回准确的结果。

//Modification of http://upshots.org/javascript/jquery-test-if-element-is-in-viewport-visible-on-screen 
//Returns "true" if element is percent visible within the viewport
$.fn.isNearScreen = function(percent){

    var offset = 1 - percent;

    var win = $(window);

    var viewport = {
        top : win.scrollTop()
    };

    viewport.bottom = viewport.top + win.height();

    var bounds = this.offset();
    bounds.bottom = bounds.top + this.outerHeight();
    bounds.top = bounds.top;

    //If the element is visible
    if(!(viewport.bottom < bounds.top || viewport.top > bounds.bottom)){

        //Get the percentage of the element that's visible
        var percentage = (viewport.bottom - bounds.top) / this.height();

        //If it's greater than percent, but less than 1 + (1 - percent), return true;
        return (percentage > (1 - offset) && percentage < (1 + offset));
    }
    return false;

};

然后我创建了一个snap函数,该函数使用Underscore.js的_.debounce函数,仅触发连续滚动事件的尾端。它在500毫秒超时后触发,我相当(虽然不是100%)确信它正在正确触发。我无法重现控制台日志,这些日志表明多个并发发射。

//Snaps the screen to a page after scroll input has stopped arriving.
var snap = _.debounce(function(event){

    //Check each page view            
    $.each($('.page-contents'), function(index, element){

        //If the page view is 70% of the screen and we are allowed to snap, snap into view
        if($(element).isNearScreen(0.7)){

            $('html,body').animate({
                scrollTop: $(element).offset().top
            }, 300);

        }
    });  
}, 500);

最后,我绑定到窗口的滚动事件

$(window).on('scroll', snap});

(极简化)HTML:

<div class="page">
     <div class="page-contents"></div>
 </div>
 <div class="page">
     <div class="page-contents"></div>
 </div>
 <div class="page">
     <div class="page-contents"></div>
 </div>
 <div class="page">
     <div class="page-contents"></div>
 </div>

和CSS:

.page{
    height: 750px;
    width: 100%;
    margin: 10px 0;
    background: gray;
}

.page-contents{
    height: 100%;
    width: 100%;
}

我尝试了什么

我尝试过以下操作,但没有成功:

  • 在窗口上设置布尔值'preventSnap',检查其状态,如果设置为false,则仅触发snap的动画部分。在动画之后,将其设置为true,然后在500ms后将其设置为false(理论上应该防止双击)。
  • 在运行.stop()动画之前,在元素上调用snap
  • 在运行动画之前,在滚动事件上调用event.preventDefault()
  • 减少并增加_.debounce延迟时间。 有趣的是,较低的_.debounce延迟(200-300毫秒)似乎会加剧问题,并且更高的_.debounce延迟(1000毫秒)似乎可以修复它。然而,这不是一个可接受的解决方案,因为它感觉“长”等待1秒钟的页面“快速”。
  • 更改元素的高度

如果我能提供任何其他信息,请告诉我。我不知所措!

1 个答案:

答案 0 :(得分:1)

我认为这是事件的组合以及_.debounce的工作原理。我注意到在小提琴中(在Chrome中)这些元素在拍完后很久就会“抖动”。如果您将控制台日志放在快照事件处理程序中,即使没有滚动输入,您也可以看到它在捕捉后不断被调用。

这必须是滚动动画本身设置快照,我试图设置一个标志,以防止动画完成后双重捕捉和清除标志 - 但是这不起作用我认为因为_.debounce将事件排队以后发生(在动画结束并清除标志之后)。

那么将此作为快照处理程序的开头添加是什么工作:

var nosnap = false;

var snap = _.debounce(function(event){

    // Don't snap if already animating ...
    if (nosnap) { nosnap = false; return; } 
    nosnap = true;

Fiddle

这可以防止动画直接触发下一个快照事件 - 但是如果在动画期间再次滚动,则会导致问题。

所以,这有点像黑客。理想情况下,您希望能够分辨出导致滚动事件的原因并作出相应的反应,但没有简单的方法可以做到这一点。

我绝对认为你在处理第二个滚动事件时也需要停止动画。

相关问题