I want to do something for every 27px the window is scrolled. I've written the following code, placed inside a scroll event handler:
scrollSpy++
if (scrollSpy > 27) {
scrollSpy = 0;
console.log("27px scrolled");
}
But somehow it's not working.
When I scroll really slow, the events are triggered quite regularly (although I can't tell if it's really after 27px), but when I scroll fast, the event seems to be triggered random/not at all.
I guess the error lies in the assumption that the scroll event is triggered for every px scrolled, because that would be a requirement for the scrollSpy++ method to work.
But of course, I am not sure if that's really the error. How do I do this correctly?
Note: This question is no duplicate of this question, because there the accepted answer is geared towards reel.
Edit:
So based on the answers, I've written this if statement:
if (window.pageYOffset % 27 === 0) {
// do something
}
But its still not working. When I scroll fast the function fails. So it has something to do with how the scroll event is triggered. Do you know any solution for this? setTimeout or setInterval maybe?
You are correct in your assumption. You should check how much
document.documentElement.scrollTop + document.body.scrollTop
changes between calls of the function. If it increases, the user is scrolling down, and vice versa.
Use this knowledge to determine when to do whatever you're doing.
No, scroll events aren’t triggered for every pixel scrolled. Anyways, you can just use window.pageYOffset instead, with fallbacks as necessary as described in the MDN documentation for pageYOffset, and check in which direction and by how much that value changes on each scroll event.
Related
I am trying to make an Alarm clock UI using react and I am stuck at this component where users can scroll or swipe through hours, minutes etc.. I tried some methods but failed.
I tried on scroll, on wheel, but my problem is I just can't get the accurate value which user sees(like the 03:30 PM).
I don't want you to help me with code, just need to know how to approach this.
I would approach this problem like this
let's address the challenges here
Scrolling to the desired points only
Looped scrolling
Auto pick the Value - without clicking basically.
Scrolling to the desired points only
This concept is called snap scrolling, you can use some library for that avoid writing its logic by yourself because then you will end up handling a lots of edge cases.
Looped scrolling
You need to handle this using basic JS logic you can provide some extra buffer elements at the end and at the starting refer to this example
https://codepen.io/lemmin/pen/bqNBpK
window.onscroll = function () {
// Horizontal Scroll.
var y = document.body.getBoundingClientRect().top;
page.scrollLeft = -y;
// Looping Scroll.
var diff = window.scrollY - dummy_x;
if (diff > 0) {
window.scrollTo(0, diff);
}
else if (window.scrollY == 0) {
window.scrollTo(0, dummy_x);
}
}
Auto pick the Value
one very basic approach could be, you can get the scroll offset on change of scroll, and as you have the height of every entity you can get the element that is in focus(or highlighted to the user).
Below is one more approach, you can create a selector div then you can check for the overlap on-scroll-stop whichever element is within this div, you can get its value.
personally, I think the first approach will be simpler and more stable. I have seen people using both types of approaches.
let me know if could help with anything else.
I have a site where I have each section as 100vh so it fills the height of the screen perfectly. The next step I wanted to implement was disabling the regular scrolling, and on scroll force the screen to jump smoothly to the top of the next 100vh section. Here is the example of this animation / feature:
https://www.quay.com.au/
I was having a hard time finding any answers for this as most things just deal with smooth scrolling when clicking on anchors, not actually forcing div relocation when the user scrolls up / down.
I just wanted to know what code I would need do this...
Thanks, been using stack overflow for a while but first post, let me know if there is anything I can do to make this more clear.
disclaimer: this solution needs some testing and probably a bit of improvements, but works for me
if you don't want to use a plugin and prefer a vanilla JavaScript solution I hacked together a small example how this can be achieved with JS features in the following codepen:
https://codepen.io/lehnerchristian/pen/QYPBbX
but the main part is:
function(e) {
console.log(e);
const delta = e.deltaY;
// check which direction we should scroll
if (delta > 0 && currentlyVisible.nextElementSibling) {
// scroll downwards
currentlyVisible = currentlyVisible.nextElementSibling;
} else if (delta < 0 && currentlyVisible.previousElementSibling) {
// scroll upwards
currentlyVisible = currentlyVisible.previousElementSibling;
} else {
return false;
}
// perform scroll
currentlyVisible.scrollIntoView({ behavior: 'smooth' });
e.preventDefault();
e.stopPropagation();
}
what it does is that it listens for the wheel event and then calls the callback, which intercepts the scroll event. inside the callback the direction is determined and then Element.scrollIntoView() is called to let the browser do the actual scrolling
check https://caniuse.com/#search=scrollintoview for browser support, if you're going for this solution
Basically I want to disable target from being scrolled to top if the if condition inside handleScroll function is true. So in other words If the condition is true. the user should not be able to scroll to top of the target element anymore and to be able to scroll to bottom of the element.
Also i don't want overflow hidden workarounds if possible.
target.addEventListener('scroll', e => this.handleScroll(e, sectionRect, offset, target));
handleScroll(event, sectionRect, offset, target) {
if ((sectionRect.top - offset) < target.scrollTop)
console.log('dont scroll', event);
},
I dont think its possible you can see more about scroll event here and also i recomend see this part:
"Since scroll events can fire at a high rate, the event handler
shouldn't execute computationally expensive operations such as DOM
modifications. Instead, it is recommended to throttle the event using
requestAnimationFrame, setTimeout or customEvent, as follows:"
and this part:
"In iOS UIWebViews, scroll events are not fired while scrolling is
taking place; they are only fired after the scrolling has completed.
See Bootstrap issue #16202. Safari and WKWebViews are not affected by
this bug."
Maybe creating a custom scroll can be the answer for you.
I have 2 divs (left and right) and i want to scroll the left based on the right.
https://jsfiddle.net/3jdsazhg/2/
This works fine on desktop, but when i change to mobile, it's not smooth anymore...
This can be noticed very easily, by changing
_left.style.top = _content.scrollTop - (_content.scrollTop * ratioLeftRight) + 'px';
to
_left.style.top = _content.scrollTop + 'px';
Where it should act as a fixed positioned div
I would like to know the exact reason why this isn't smooth... I know that it's not the animation. Simple animation on the div is smooth, the issue comes up when it's based on scroll.
How can i make this animation smooth?
It's probably choppy because it's being fired ALOT when being scrolled, in fact i'm pretty sure IOS mobile pauses the javascript execution whilst the user is scrolling.
Instead I'd suggest using an interval, you could tweak the time between each interval to what feels good for your use-case.
Although it may seem intensive that it's firing this logic every X millisecond when using the scroll event you could be firing the event off hundreds of times per second, which is going to be far more intensive and noticeable to a user using a device with limit processing power.
(function () {
var interval = null,
//Currently set at 0.4 seconds, play with the code
//and change this value to see what works best for
//this use-case
time_between_interval = 400;
setInterval(scrollLogic, time_between_interval);
function scrollLogic () {
//The function body of what you're assigning
//to the scroll event.
}
//I have omitted clearing the interval but you would want to do that, perhaps on page change, or something.
//clearInterval(interval);
})();
I finally managed to think out a solution.
From my point of view, i'm guessing the mobile view fires the scroll event less often and because we are scrolling the wrapper, we first scroll the whole page and then scroll back with js the left part and because it's different from the desktop version, this issue becomes visible...
The solution was to change the left side to fixed position, and substract from the top instead of adding to it.
_left.style.top = -(_content.scrollTop * ratioLeftRight) + 'px';
Situation:
Suppose we are reading the content somewhere down the page that is built to be responsive. Suppose also that we resize the browser window to a smaller size and that some content above get extended down due to the thinner width, hence making the whole page longer. Then as we resize, whatever content we are looking at will get pushed down the page accordingly.
Example:
Suppose we were to look at the Helper classes section in this page. Then shrinking/expanding the window a sufficient amount moves the bit we were reading down/up the current view.
Prompt:
Is there any way we can fix this? I.e. maintain our current view of the page regardless of what happens to the contents above it when we resize the window.
Thoughts:
I am thinking that we could at least start with javascript and put an event on window resize. Then automatically scroll the page to the top-most element that was in our view on event fire. I don't know how this will affect the performance, however, especially in bigger pages.
There's also the problem of refering to the top-most element in current view. The top of our current view might be cutting off the top portion of some elements, not to mention that there's usually more than 1 element layered on top of one another at any point within the page. The notion of top-most element I've mentioned is not very well-defined :(
Also rather than a problem of responsive design in general, instead it seems to me like this is a problem with the default scrolling behaviour of web browsers? Or perhaps I am missing some circumstances where the current behaviour is desirable.
Edit 2 4
Updated fiddle (see fullscreen result) based on Rick Hitchcock's solution's solution.
With jQuery:
//onresize:
var scrollAmount;
if (topNode.getBoundingClientRect().top >= 0) {
scrollAmount = $(topNode).offset().top - topNode.getBoundingClientRect().top;
} else {
scrollAmount = $(topNode.offset().bottom - topNode.getBoundingClientRect().bottom;
}
$(window).scrollTop(scrollAmount);
The fiddle is acting a bit weird even in the same browsers, I've uploaded the same script using a free hosting here.
Still need to incorporate the IE, Opera and Safari fix for elementFromPoint.
Edit 3
Thanks for all the help, Rick Hitchcock. Welcome to stackoverflow, by the way :)
The discussion is turning into cross-browser compatibility issues so I've accepted your answer since we've pretty much got the answer to the original question. I'll still be fixing up my implementation though. The focus being cross-browser issues, topNode criteria, and topNode cut-off handling.
An edge case
While playing around with it, I noticed that when we were at the bottom of the page in a small viewport, then switch to a larger viewport (let us assume now that some more elements that were originally above the element we saw now came into view due to shorter container from wider viewport) the window cannot always lock the topNode to the top of the viewport in such a case since we've reached the scroll bottom. But then switching back to the small viewport now uses a new topNode that got into the viewport during the switch.
Although this should be expected from the behaviour being implemented, it is still a weird side-effect on scroll bottom.
I will also be looking into this in due course. Initially, I am thinking of simply adding a check for scroll bottom before we update topNode. I.e. to keep the old topNode when we've reached scroll bottom until we've scrolled up again. Not sure how this will turn out yet. I'll make sure to see how Opera handle this as well.
Here's what I've come up with:
(function(){
var topNode;
window.onscroll=function() {
var timer;
(function(){
clearTimeout(timer);
timer= setTimeout(
function() {
var testNode;
topNode= null;
for(var x = 0 ; x < document.body.offsetWidth ; x++) {
testNode= document.elementFromPoint(x,2);
if(!topNode || testNode.offsetTop>topNode.offsetTop) {
topNode = testNode;
}
}
},
100
)
}
)();
}
window.onresize=function() {
var timer;
(function(){
clearTimeout(timer);
if(topNode) {
timer= setTimeout(function(){topNode.scrollIntoView(true)},10);
}
}
)();
}
}
)();
If there were a window.onbeforeresize() function, this would be more straightforward.
Note that this doesn't take into account the scrolled position of the element's textNode. We could handle that if only the height of the window were resized. But resizing the width would generally cause reformatting.
This works in Chrome, Firefox, IE, and Safari.
Edit
How it works
The code's closures make variables private, and the timers prevent the code from running constantly during scrolling/resizing. But both tend to obfuscate the code, so here's another version, which may aid in understanding. Note that the onscroll timer is required in IE, because elementFromPoint returns null when it used in onscroll event.
var topNode;
window.onscroll=function() {
setTimeout(
function() {
var testNode;
topNode= null;
for(var x = 0 ; x < document.body.offsetWidth ; x++) {
testNode= document.elementFromPoint(x,2);
if(!topNode || testNode.offsetTop>topNode.offsetTop) {
topNode = testNode;
}
}
},
100
)
}
window.onresize=function() {
if(topNode) {
topNode.scrollIntoView(true)
}
}
topNode maintains the screen's top-most element as the window scrolls.
The function scans the screen left to right, along the 3rd row: document.elementFromPoint(x,2)*
It doesn't scan along the 1st row, because when IE does scrollIntoView, it pushes the element down a couple pixels, making the top-most screen element the previous element. (Figured this out through trial and error.)
When the window is resized, it simply positions topNode at the top of the screen.
[*Originally, onscroll scanned left to right along the 11th row (in pixels) until it found an element with just one child. The child would often be a textNode, but that wouldn't always be the case. Example:
<div><ul><li>...<li>...<li>...</ul></div>
The div has only one child – the ul. If the window were scrolled to the 50th li, scanning left to right would incorrectly return the div due to the inherent padding of lis.
The original code has been updated.
]