admin管理员组文章数量:1307121
I have a situation where, for example, if a user's scroll will result in a 1000 px change in scrollTop I'd like to know ahead of time.
The perfect example is iCalendar's control over a user's scroll. No matter how hard you scroll in the iCalendar application, the farthest you can scroll is to the next or previous month.
I currently have a very hackish solution to limit scroll behavior, which only takes into account where the user's scroll currently is.
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);
};
This approach works, and will stop a scroll in progress, but it is not smooth and I'd love to know if there's a better way to acplish this.
The key to this question is can I know ahead of time where a scroll will end up. Thanks!!!
I have a situation where, for example, if a user's scroll will result in a 1000 px change in scrollTop I'd like to know ahead of time.
The perfect example is iCalendar's control over a user's scroll. No matter how hard you scroll in the iCalendar application, the farthest you can scroll is to the next or previous month.
I currently have a very hackish solution to limit scroll behavior, which only takes into account where the user's scroll currently is.
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);
};
This approach works, and will stop a scroll in progress, but it is not smooth and I'd love to know if there's a better way to acplish this.
The key to this question is can I know ahead of time where a scroll will end up. Thanks!!!
Share Improve this question edited Aug 14, 2016 at 14:20 Robert asked Aug 14, 2016 at 1:49 RobertRobert 9911 gold badge15 silver badges24 bronze badges 14- 1 You may want to mention that you're referring to inertia/momentum scrolling, not normal mouse scrolling. I don't know of any way to do this. – David Gilbertson Commented Aug 14, 2016 at 2:12
- 1 What about using a plugin like Scrollify? – Anthony Commented Aug 17, 2016 at 15:39
- 1 @Anthony That may actually work really well. I haven't worked with scrollify, but I will definitely explore it further. Thanks for the input! – Robert Commented Aug 17, 2016 at 15:57
- 1 Added implementation C, keep in mind the animation code is not part of the answer, you have to write the animation yourself that includes the correct use of the array of registered speeds in implementation C – seahorsepip Commented Aug 18, 2016 at 1:55
- 1 @seahorsepip I'll look things over in more detail tomorrow after work and get back with you. Thanks for the hard work, your upvotes are well deserved. – Robert Commented Aug 18, 2016 at 2:00
3 Answers
Reset to default 6 +50Edit: Just found the following project on github:
https://github./jquery/jquery-mousewheel
I tried the demo and it's able to report my touchpad and mouse scroll speed. Also it able to stop scrolling without any position fixed hacks :D
I'll have a look in the next few days and see if I can write anything that reports scroll speed, direction, velocity, device etc. Hopefully I'm able to make some jquery plugin that can override all scrolling interaction.
I'll update this post when I've got more info on this subject.
It's impossible to predict where a mouse scroll will end up.
A touchscreen/touchpad swipe on the other hand has a certain speed that will slow down after the user stopped swiping, like a car that got a push and starts slowing down afterwards.
Sadly every browser/os/driver/touchscreen/touchpad/etc has it's own implementation for that slowing down part so we can't predict that.
But we can of course write our own implementation.
We got 3 implementations that could be made:
A. Direction
B. Direction and speed
C. Direction, speed and velocity
iCalender probably uses implementation A.
Implementation A:
Outputs scroll direction to console, user is able to scroll +/- 1px before the direction is detected.
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);
})();
Implementation B:
Outputs scroll direction, distance and average speed to console, user is able to scroll the amount of pixels set in the
distance
variable.If the user scrolls fast they might scroll a few more pixels though.
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);
})();
Implementation C:
Outputs scroll direction, distance and speeds to console, user is able to scroll the amount of pixels set in the
distance
variable.If the user scrolls fast they might scroll a few more pixels though.
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);
})();
Helper functions used in above implementations:
//Source: https://github./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;
}
Just had a look at scrollify mentioned in the ments, it's 10kb and needs to hook at every simple event: touch, mouse scroll, keyboard buttons etc.
That doesn't seem very future proof, who know what possible user interaction can cause a scroll in the future?
The onscroll event on the other hand will always be triggered when the page scrolls, so let's just hook the animation code on that without worrying about any input device interaction.
As @seahorsepip states, it is not generally possible to know where a scroll will end up without adding custom behavior with JavaScript. The MDN docs do not list any way to access queued scroll events: https://developer.mozilla/en-US/docs/Web/Events/scroll
I found this information helpful: Normalizing mousewheel speed across browsers
It highlights the difficulty of knowing where the page will go based on user input. My suggestion is to trigger a scroll to Y event when the code predicts the threshold is reached. In your example, if the scroll has moved the page 800 of 1000 pixels in a time window of 250ms, then set the scroll to that 1000 pixel mark and cut off the scroll for 500ms.
https://developer.mozilla/en-US/docs/Web/API/window/scrollTo
is easy to use event listener to do it. Here is a React example:
/**
* scroll promise
*/
const scrollPromiseCallback = useCallback((func:Function) => {
return new Promise((resolve, reject) => {
func(resolve, reject)
})
}, [])
/**
* scroll callback
*/
const scrollCallback = useCallback((scrollContainer, onScrollEnd, resolve) => {
/** 防抖时间 */
const debounceTime = 200
/** 防抖计时器 */
let timer = null
const listener = () => {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
scrollContainer.removeEventListener('scroll', listener)
resolve(true)
onScrollEnd?.()
}, debounceTime)
}
scrollContainer.addEventListener('scroll', listener)
}, [])
const scrollTo = useCallback((props:IUseScrollToProps) => {
return scrollPromiseCallback((resolve, reject) => {
const {
scrollContainer = window, top = 0, left = 0, behavior = 'auto',
} = props
scrollCallback(scrollContainer, props?.onScrollEnd, resolve)
scrollContainer.scrollTo({
top,
left,
behavior,
})
})
}, [scrollCallback, scrollPromiseCallback])
本文标签:
版权声明:本文标题:jquery - Is it possible to determine where a scroll will end up using javascript? If so, how? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1741002419a2322282.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论