bidirectional intersection observer on firefox - javascript

let wheelObserverOptions = {
root: dateWheelNode,
rootMargin: '100px',
}
const dateWheelObserver = new IntersectionObserver(wheelObserverCallback, wheelObserverOptions);
// const dateWheelPastDateObserver = new IntersectionObserver(wheelObserverCallback, wheelObserverOptions);
function wheelObserverCallback(entries, observer) {
entries.forEach(entry => {
if(entry.isIntersecting) {
debugMsg('triggered')
if (entry.target === dateWheelNode.lastChild) loadFutureDates()
if (entry.target === dateWheelNode.firstChild) loadPastDates()
// observer.unobserve(entry.target)
}
});
};
prepending new elements when scrolling the dateWheelNode - which is the parent container
function loadPastDates() {
dateWheelDateFuture.subtract(1,'day')
dateWheelDatePast.subtract(1,'day')
wheelCheckDays(dateWheelDatePast)
}
appending
function loadFutureDates() {
dateWheelDateFuture.add(1,'day')
dateWheelDatePast.add(1,'day')
wheelCheckDays(dateWheelDateFuture)
}
now regardless if prepending or appending this is in the wheelCheckDays() function (which works as intended)
dateWheelNode.scroll(0, dateWheelNode.scrollTop);
dateWheelObserver.observe(dateWheelElementDOM);
this also makes sure regardless of the scrolling direction the elements do not jump and the scrolling remains smooth (works on chrome & edge) and is on the bottom of wheelCheckDays() after
also dateWheelNode gets initial 30 elements when opening it with (and which are not being removed - only if appending/prepending additional ones by scrolling)
for (let i = 0; i <= 30; i++ ) {
if (i === 0) {
wheelCheckDays(dateWheelDateFuture, true)
wheelYearObserver.observe(document.querySelector(`.dateWheelDay${dateWheelDateFuture.format("YYYYMMDD")}`));
wheelMonthObserver.observe(document.querySelector(`.dateWheelDay${dateWheelDateFuture.format("YYYYMMDD")}`));
} else if (i > 0 && i <= 15) {
dateWheelDatePast.subtract(1,'day')
wheelCheckDays(dateWheelDatePast, true)
} else if (i > 15) {
dateWheelDateFuture.add(1,'day')
wheelCheckDays(dateWheelDateFuture, true)
}
}
besides that dateWheelNode has the height set to min(350px, 80%)
other than that wheelCheckDays() function does modify the new elements before prepending/appending it and removes the last or first child of dateWheelNode every time depending on if a element gets appended a prepended
now my problem is that on chrome and edge kinda works as it tends occasionally to stop the scrolling abruptly and sometimes (rarely) scroll infinitely upwards, but on firefox it happens 100% of times and additionally when scrolling down (loadFutureDates()) the scrolling is not smooth at all and does jump dates very fast at once too and when scrolling upwards loadPastDates() it scrolls infinitely and won't stop until scrolling down
what I'm missing here and how to fix the infinite automatic scroll and smooth scrolling (still though not throttling the scrolling in order to be able to switch the dates fast too - just like normal scrolling behaviour)
changing dateWheelNode.scroll(0, dateWheelNode.scrollTop) into dateWheelNode.scroll({top: dateWheelNode.scrollTop, behaviour: 'smoooth'}) does not help with that scrolling either as the infinite scroll upwards happens then on all browsers
PS: it is intentioned being viewed mostly on mobile, but the issues described happens also on desktop
PS2: couldn't really post a working example as the code is a mess and I've cut out the (seemingly) important parts of it and and that's also why the solution/answer shouldn't be a totally different approach (unless the rewriting wouldn't take too much time) and also not over complicated as I'm still a junior dev
UPDATE: I managed to make a a example on jsfiddle
https://jsfiddle.net/DadoIrie/a8cge9o6/34/
best way to test that scrolling issue is just move the scrollbar on top with firefox - it just starts scrolling infinitely - while chrome doesn't even allow to bring the scrollbar on top - bottom works perfectly
UPDATE (temporary ugly solution) well, temporary solution is if (dateWheelNode.scrollTop < 1) document.querySelector('#dateWheelDays').scroll(0, 100); ... which is quite ugly, but at least stops firefox from infinitely auto-scroll upwards
still open for sugestions regarding this

Related

Problem with scrolldown in slow manner using javascript

I needed JavaScript for automatic scroll down in a smooth/slow manner.
I have a form with many radio buttons which is quite similar to survey form.
I used script from the below mentioned link. This link works fine smoothly for scrolling downwards.
But problem comes when you reach the bottom of page and cannot scroll upwards.
I am not so good in JavaScript. Does anyone here has solution or fix to this?
Link to Stack Overflow thread:
Slow down onclick window.scrollBy
function scrollByRate(y, rate)
{
//calculate the scroll height
var scrolling = Math.max( document.getElementsByTagName('html')[0].scrollTop, document.body.scrollTop);
//save the old value as "static" var
arguments.callee.tmp = arguments.callee.tmp || scrolling + y;
//make a little scrolling step
window.scrollBy(0, (arguments.callee.tmp - scrolling) / rate);
//are we arrived? if no, keep going recursively, else reset the static var
if(arguments.callee.tmp - scrolling > 100) setTimeout(function() { scrollByRate(y, rate); }, 10);
else arguments.callee.tmp = undefined;
}
Scrolling down slowly
I can see your approach having a negative impact on performance. It looks like the browser will block until the target scroll destination has been reached.
My suggestion is to use what is out there for smooth scrolling already. The scrollTo method of any scrollable pane (e.g. window object but also a scrollable div for example) has a "behavior" property that you can set to "smooth", e.g.:
window.scrollTo({
top: 100,
left: 100,
behavior: 'smooth'
});
Keep in mind that the compatibility at the time of writing is limited to Chrome, Firefox, Edge and Opera which means you'll have problems on Internet Explorer and Safari (so all Apple products). I myself use a polyfill to get the smooth scrolling back on my application, this one in particular: https://github.com/iamdustan/smoothscroll

How to create forced scrolling to anchors on a website on scroll

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

Maintaining page view on window resize in a responsive website

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.
]

Is there a reliable way to detect scrolling, on all devices, using javascript/jQuery?

I've been struggling with this for a while, and I'm surprised that doing this isn't more straightforward...
I need to detect when the user scrolls a page, either with the mouse, scrollbar or by touch on mobile devices. jQuery has their scroll() function which works alright, but it requires that the page is actually scrolling. I want to detect the scrolling wether the page is scrolling or not (say I reach the end of the page, and there is nowhere left to scroll too.. I still want to know if the user is trying to scroll)
I found another question that had asked something similar, along the lines of detecting scroll input even when the page isn't scrolling, and I got this chunk of code:
if (document.addEventListener) {
document.addEventListener("mousewheel", MouseWheelHandler(), false);
document.addEventListener("DOMMouseScroll", MouseWheelHandler(), false);
} else {
sq.attachEvent("onmousewheel", MouseWheelHandler());
}
function MouseWheelHandler() {
return function (e) {
var e = window.event || e;
var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
if (delta < 0) {
// increase scroll amount
} else {
// decrease scroll amount
}
}
return false;
}
At first, this seemed to do the trick, but I'm finding it doesn't really return balanced results with different types of mice, and didn't work too smoothly with touch events, which is the core aspect of this question.
I'm using this in a project that does a lot of fancy events on scroll, with the actual page not actually scrolling at all... But I'm running into the problem of it being incredibly slow with all my standard mice, but incredibly fast on my Apple Magic Mouse. I know that there will naturally be some difference here, as the magic mice do scroll quicker, but the difference is far more off balance than it is between the mice normally.
I'm hoping there is a way to improve upon this to get a more reliable result, with all sorts of different inputs. Any suggestions?
Edit:
To clarify, in order for an answer to work for me, it needs to work on an element which is not scrollable. I have a page which does not scroll at all, but which has other events that fire when the user scrolls. This means that I cannot use properties that are based on the window's scroll position (such as scrollTop()).
You should use window.onscroll most usage and then create a new listener to deal specifically with top and bottom scroll conditions I would suggest using a mousewheel event for desktop browsers and a specifically coded touch responder like below to detect if they are trying to scroll, what direction and if that is possible at the current window.scrollY value.
var isOverScroll = function isOverScroll ( touchStartY, touchEndY ) {
if ( Math.abs( touchStartY - touchEndY ) < 5 ) &&
( ( window.scrollY = window.innerHeight && touchStartY - touchEndY > 0 ) ||
( window.scrollY = 0 && touchStartY - touchEndY < 0 ) ) ) {
return false;
}
return true;
}
There is no way to detect scrollbar events, combine this with your current code and only trigger mouse wheel and touch events if the scrollY position is at either 0 or max.
On a side note if you are trying to get rid of the scroll bar completely that is a very bad idea as it is both a wonderful tool for users as well as something that is a standard part of the ui. If you trying to do a scrollable fullpage app and don't want a scroll bar try using slides. Either way don't continue setting the scroll value thats slow instead just move the whole body using css:
transition: transform3d( 0, YOURSCROLLVALUE , 0);
One possible solution is using a plugin for scrolling like
ISCROLL
in this example here :
Example
they use the feature pull to refresh , which will fire upon reaching the maximum scroll available , here by you can use any custom function (even if your item is not scrollable ).

jQuery's $(selector).position().left Sometimes Fails in Firefox, Chrome Browsers

I have a content slideshow:
slide container
|--> wrapper
|------> slide1, slide2, etc.
that works as simple as calculating wrapper's position X and slide's position X to determine where to slide the wrapper for the next/previous slide to show up within container's viewport. It's pretty straight forward.
For Firefox and Chrome I am using CSS3 transform and transition-duration to animate the slides. Everything works perfect. Almost.
The problem is only when I click next button very fast. I am using jQuery's
$(selector).position().left
to read the slide's position X (left) and position becomes 0 (instead of expected, say, 300px). I do have a flag isAnimating to prevent users from going too fast but that does not help either. It does prevent from scrolling content too fast but position left may still be 0 as if something else is causing it to fail to determine.
I did a brief search and discovered that if it was image being loaded, some browsers would fail to determine its position until loading is over. However, each slide has an image but inside of it. The slide's CSS seems to have all widths and margins set fine.
The question is why may this be happening based on the scenario I described and possibly what can be improved to determine position X at all times for Firefox, Chrome browsers?
I've decided that if offsetLeft is not reliable for me at all times, I could use width of an element and its index position within container to figure out position X
var newWrapperPos = undefined;
$(lotWrapper).children('div').each(function(index){
if($(this).attr("id") === "slot"+id){
var width = $(this).width();
newWrapperPos = index * width;
return false;
}
});
//now I can shift wrapper to position = newWrapperPos
Sorry I couldn't share the code - it is a bit time consuming to rip off all pieces of functionality involved. But if somebody has a better answer, let me know. For now this works fine for me.

Categories