【问题标题】:Is it possible to determine where a scroll will end up using javascript? If so, how?是否可以使用 javascript 确定滚动的最终位置?如果是这样,怎么做?
【发布时间】:2016-08-14 01:49:50
【问题描述】:

我有一种情况,例如,如果用户的滚动会导致 scrollTop 发生 1000 像素的变化,我想提前知道。

完美的例子是 iCalendar 对用户滚动的控制。无论您如何在 iCalendar 应用程序中滚动,您最多可以滚动到下个月或上个月。

我目前有一个非常骇人听闻的解决方案来限制滚动行为,它只考虑用户当前滚动的位置。

MyConstructor.prototype._stopScroll = function(){

    //Cache the previous scroll position and set a flag that will control
    //whether or not we stop the scroll
    var previous = this._container.scrollTop;
    var flag     = true;

    //Add an event listener that stops the scroll if the flag is set to true
    this._container.addEventListener('scroll', function stop(){
        if(flag) {
            this._container.scrollTop = previous;
        }
    }.bind(this), false);

    //Return a function that has access to the stop function and can remove it
    //as an event listener
    return function(){
        setTimeout(function(){
            flag = false;
            this._container.removeEventListener('scroll', stop, false);
        }.bind(this), 0);
    }.bind(this);
};

这种方法有效,并且会停止正在进行的滚动,但它并不顺畅,我很想知道是否有更好的方法来完成此操作。

这个问题的关键是我能提前知道卷轴将在哪里结束。谢谢!!!

【问题讨论】:

  • 您可能想提一下,您指的是惯性/动量滚动,而不是普通的鼠标滚动。我不知道有什么方法可以做到这一点。
  • 使用像Scrollify这样的插件怎么样?
  • @Anthony 这实际上可能工作得很好。我没有使用过scrollify,但我一定会进一步探索它。感谢您的意见!
  • 添加了实现 C,请记住动画代码不是答案的一部分,您必须自己编写动画,其中包括正确使用实现 C 中的注册速度数组
  • @seahorsepip 明天下班后我会更详细地查看事情并与您联系。感谢您的辛勤工作,您的支持是当之无愧的。

标签: javascript jquery html dom scroll


【解决方案1】:

编辑:刚刚在github上找到了以下项目:

https://github.com/jquery/jquery-mousewheel

我尝试了演示,它能够报告我的触摸板和鼠标滚动速度。它还能够在没有任何位置固定黑客的情况下停止滚动:D

我会在接下来的几天里看看我是否可以写任何报告滚动速度、方向、速度、设备等的东西。希望我能够制作一些可以覆盖所有滚动交互的 jquery 插件。

当我有更多关于这个主题的信息时,我会更新这篇文章。


无法预测鼠标滚动的最终位置。

另一方面,触摸屏/触摸板滑动具有一定的速度,在用户停止滑动后会减慢速度,就像汽车受到推动后开始减速一样。

可悲的是,每个浏览器/操作系统/驱动程序/触摸屏/触摸板/等都有它自己的实现来减缓速度,所以我们无法预测。


但我们当然可以编写自己的实现。

我们有 3 个可以实现的实现:

A.方向

B.方向和速度

C.方向、速度和速度


iCalender 可能使用实现 A。


实现 A:

将滚动方向输出到控制台,用户可以滚动 +/- 1px 在检测到方向之前。

Demo on JSFiddle

Demo with animation on JSFiddle

(function iDirection() {
    var preventLoop = true;
    var currentScroll = scrollTop();
    function scroll() {
        if(preventLoop) {
            //Get new scroll position
            var newScroll = scrollTop();

            //Stop scrolling
            preventLoop = false;
            freeze(newScroll);

            //Check direction
            if(newScroll > currentScroll) {
                console.log("scrolling down");
                //scroll down animation  here
            } else {
               console.log("scrolling up");
                //scroll up animation here
            }
            /*
            Time in milliseconds the scrolling is disabled,
            in most cases this is equal to the time the animation takes
            */
            setTimeout(function() {
                //Update scroll position
                currentScroll = newScroll;

                //Enable scrolling
                unfreeze();

                /*
                Wait 100ms before enabling the direction function again
                (to prevent a loop from occuring).
                */
                setTimeout(function() {
                    preventLoop = true;
                }, 100);
            }, 1000);
        }
    }
    $(window).on("scroll", scroll);
})();


实施 B:

将滚动方向、距离和平均速度输出到控制台,用户可以滚动在distance 变量中设置的像素数量。

如果用户快速滚动,他们可能会滚动更多像素。

Demo on JSFiddle

(function iDirectionSpeed() {
    var distance = 50; //pixels to scroll to determine speed
    var preventLoop = true;
    var currentScroll = scrollTop();
    var currentDate = false;
    function scroll() {
        if(preventLoop) {
            //Set date on scroll
            if(!currentDate) {
                currentDate = new Date();
            }

            //Get new scroll position
            var newScroll = scrollTop();

            var scrolledDistance = Math.abs(currentScroll - newScroll);

            //User scrolled `distance` px or scrolled to the top/bottom
            if(scrolledDistance >= distance || !newScroll || newScroll == scrollHeight()) {
                //Stop scrolling
                preventLoop = false;
                freeze(newScroll);

                //Get new date
                var newDate = new Date();

                //Calculate time
                var time = newDate.getTime() - currentDate.getTime();

                //Output speed
                console.log("average speed: "+scrolledDistance+"px in "+time+"ms");

                /*
                To calculate the animation duration in ms:
                x: time
                y: scrolledDistance
                z: distance you're going to animate

                animation duration = z / y * x
                */

                //Check direction
                if(newScroll > currentScroll) {
                    console.log("scrolling down");
                    //scroll down animation  here
                } else {
                   console.log("scrolling up");
                    //scroll up animation here
                }

                /*
                Time in milliseconds the scrolling is disabled,
                in most cases this is equal to the time the animation takes
                */

                setTimeout(function() {
                    //Update scroll position
                    currentScroll = newScroll;

                    //Unset date
                    currentDate = false;

                    //Enable scrolling
                    unfreeze();

                    /*
                    Wait 100ms before enabling the direction function again
                    (to prevent a loop from occuring).
                    */
                    setTimeout(function() {
                        preventLoop = true;
                    }, 100);
                }, 1000);
            }
        }
    }
    $(window).on("scroll", scroll);
})();


实现 C:

将滚动方向、距离和速度输出到控制台,用户可以滚动distance 变量中设置的像素数量。

如果用户快速滚动,他们可能会滚动更多像素。

Demo on JSFiddle

(function iDirectionSpeedVelocity() {
    var distance = 100; //pixels to scroll to determine speed
    var preventLoop = true;
    var currentScroll = [];
    var currentDate = [];
    function scroll() {
        if(preventLoop) {
            //Set date on scroll
            currentDate.push(new Date());

            //Set scrollTop on scroll
            currentScroll.push(scrollTop());

            var lastDate = currentDate[currentDate.length - 1];
            var lastScroll = currentScroll[currentScroll.length - 1];

            //User scrolled `distance` px or scrolled to the top/bottom
            if(Math.abs(currentScroll[0] - lastScroll) >= distance || !lastScroll || lastScroll == scrollHeight()) {
                //Stop scrolling
                preventLoop = false;
                freeze(currentScroll[currentScroll.length - 1]);

                //Total time
                console.log("Time: "+(lastDate.getTime() - currentDate[0].getTime())+"ms");

                //Total distance
                console.log("Distance: "+Math.abs(lastScroll - currentScroll[0])+"px");

                /*
                Calculate speeds between every registered scroll
                (speed is described in milliseconds per pixel)
                */
                var speeds = [];
                for(var x = 0; x < currentScroll.length - 1; x++) {
                    var time = currentDate[x + 1].getTime() - currentDate[x].getTime();
                    var offset = Math.abs(currentScroll[x - 1] - currentScroll[x]);
                    if(offset) {
                        var speed = time / offset;
                        speeds.push(speed);
                    }
                }

                //Output array of registered speeds (milliseconds per pixel)
                console.log("speeds (milliseconds per pixel):");
                console.log(speeds);

                /*
                We can use the array of speeds to check if the speed is increasing
                or decreasing between the first and last half as example
                */ 
                var half = Math.round(speeds.length / 2);
                var equal = half == speeds.length ? 0 : 1;
                var firstHalfSpeed = 0;
                for(var x = 0; x < half; x++ ) {
                    firstHalfSpeed += speeds[x];
                }
                firstHalfSpeed /= half;
                var secondHalfSpeed = 0;
                for(var x = half - equal; x < speeds.length; x++ ) {
                    secondHalfSpeed += speeds[x];
                }
                secondHalfSpeed /= half;
                console.log("average first half speed: "+firstHalfSpeed+"ms per px");
                console.log("average second half speed: "+secondHalfSpeed+"ms per px");
                if(firstHalfSpeed < secondHalfSpeed) {
                    console.log("conclusion: speed is decreasing");
                } else {
                    console.log("conclusion: speed is increasing");
                }

                //Check direction
                if(lastScroll > currentScroll[0]) {
                    console.log("scrolling down");
                    //scroll down animation  here
                } else {
                   console.log("scrolling up");
                    //scroll up animation here
                }

                /*
                Time in milliseconds the scrolling is disabled,
                in most cases this is equal to the time the animation takes
                */
                setTimeout(function() {
                    //Unset scroll positions
                    currentScroll = [];

                    //Unset dates
                    currentDate = [];

                    //Enable scrolling
                    unfreeze();

                    /*
                    Wait 100ms before enabling the direction function again
                    (to prevent a loop from occuring).
                    */
                    setTimeout(function() {
                        preventLoop = true;
                    }, 100);
                }, 2000);
            }
        }
    }
    $(window).on("scroll", scroll);
})();


上述实现中使用的辅助函数:

//Source: https://github.com/seahorsepip/jPopup
function freeze(top) {
    if(window.innerWidth > document.documentElement.clientWidth) {
        $("html").css("overflow-y", "scroll");
    }
    $("html").css({"width": "100%", "height": "100%", "position": "fixed", "top": -top});
}
function unfreeze() {
    $("html").css("position", "static");
    $("html, body").scrollTop(-parseInt($("html").css("top")));
    $("html").css({"position": "", "width": "", "height": "", "top": "", "overflow-y": ""});
}
function scrollTop() {
    return $("html").scrollTop() ? $("html").scrollTop() : $("body").scrollTop();
}
function scrollHeight() {
    return $("html")[0].scrollHeight ? $("html")[0].scrollHeight : $("body")[0].scrollHeight;
}

刚刚看过 cmets 中提到的 scrollify,它是 10kb,需要挂钩每个简单的事件:触摸、鼠标滚动、键盘按钮等。

这似乎不太适合未来,谁知道未来可能的用户交互会导致滚动?

另一方面,onscroll 事件总是会在页面滚动时触发,所以我们只需在其上挂钩动画代码即可,无需担心任何输入设备交互。

【讨论】:

  • @Kaiido 这和我写的几乎一样(除了花哨的画布),但遗憾的是这在触摸屏、键盘导航等上不起作用。所以我决定让它使用滚动事件,因为这是由一切触发的:P
  • 可悲的是,它在 Windows 上只显示 2 种速度(快或慢):/ 我写了很多过去在滚动时触发的东西,我了解到为每个单个事件(触摸,滚动等)变得烦人并且变得很多工作:P 编写上面的实现也需要一些摆弄才能开始工作,因为没有真正的方法可以正确停止滚动,我不得不使用我以前使用的定位技巧.
  • 鼠标滚轮插件看起来很有前途,但一个 webkit 错误阻止它在 Safari 中工作,这是有问题的。
  • 我刚刚为插件编写了一个用于触摸屏滚动的钩子,以便也可以使用 onmousewheel 处理:D 遗憾的是,触摸板添加排队的滚轮/滚动事件仍然存在问题:/ 但也许我有onmousedown/up 的想法,因为当用户快速滚动时不再触摸触摸板。
  • @Robert 谢谢,当我有时间用 onmousewheel 完成新示例并让我的排队滚动检测方法正常工作时,我会更新这个答案。触摸屏滑动到鼠标滚轮代码已经完成,但我正在尝试修复一个小错误。
【解决方案2】:

正如@seahorsepip 所说,如果不使用 JavaScript 添加自定义行为,通常不可能知道滚动将在哪里结束。 MDN 文档没有列出任何访问排队滚动事件的方法:https://developer.mozilla.org/en-US/docs/Web/Events/scroll

我发现这些信息很有帮助: Normalizing mousewheel speed across browsers

它突出了根据用户输入了解页面将转到何处的难度。我的建议是当代码预测达到阈值时触发滚动到 Y 事件。在您的示例中,如果滚动在 250 毫秒的时间窗口内移动了 1000 像素的页面 800,则将滚动设置为该 1000 像素标记并切断滚动 500 毫秒。

https://developer.mozilla.org/en-US/docs/Web/API/window/scrollTo

【讨论】:

  • 触摸屏和鼠标滚动并不是我在回答中显示的那么大的问题,但触摸板滚动是邪恶的。快速触摸板滚动将触发滚动事件近 3 秒 D:这意味着如果您像我的示例中那样停止滚动然后动画 500 毫秒然后启用滚动,它将在动画后滚动 2.5 秒:/
【解决方案3】:

我不太确定我是否有你要找的东西。我有一次项目,我必须控制滚动。那时我已经覆盖了默认的滚动事件,之后您可以为“一个”滚动设置自定义距离。另外添加了 jQuery 动画以滚动到特定位置。 在这里你可以看看:http://c-k.co/zw1/ 如果这就是你要找的东西,你可以联系我,我会看看我对我自己的东西还有多少了解

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-04-02
    • 2012-06-19
    • 1970-01-01
    • 1970-01-01
    • 2021-06-30
    • 2020-03-25
    • 1970-01-01
    • 2015-11-02
    相关资源
    最近更新 更多