iOS browser scrolling bug - javascript

I'm having a vague bug while scrolling in a browser on an iOS device (iPad), it works 90% of the time but 10% it the scrolling is blocked. I then have to wait for a couple of seconds before it works again. This often happens after I've contracted/expanded a filter panel, which is also a scrollable component.
On all other devices, the scrolling is no issue whatsoever. I've looked everywhere on the internet but I couldn't find anyone with a similar problem?
My two scrollable components do have overflow-y: scroll and
-webkit-overflow-scrolling: touch, but in general I highly doubt it's a CSS issue as it works most of the time, just not at random times.
I have tried adding onTouchStart={ () => {} } to my component, but also that doesn't solve it.

This problem is still here in iOs15, and I developed a small fix and put it on GitHub.
Complete code here:
https://github.com/AlessandroCipolletti/fix-ios15-safari-scroll/blob/main/preventScrollBugsIfNeeded.ts
I noticed that the bug happens only when the scrollable content is "all on top" or "all at the end" of its scroll space.
I mean, when there is no scroll available in one of the two directions (top-bottom / left-right makes no difference for the occurrence of the bug).
So the idea is to check if this is the case during a touchstart event, and if so, make a 1px scroll by code.
I don't know why iOS "needs" this to make a proper scroll, but it works fine for me.
// do this inside a onTouchStart event handler:
const target = myScrollableContainer
// if it has vertical scroll
if (target.scrollHeight > target.clientHeight) {
// if scroll is on top
if (target.scrollTop === 0) {
// move content 1px up
target.scrollTop = 1
} else
// if scroll in at the bottom
if (target.scrollTop === (target.scrollHeight - target.clientHeight)) {
// move content 1px down
target.scrollTop = (target.scrollHeight - target.clientHeight) - 1
}
}
// if it has horizontal scroll
if (target.scrollWidth > target.clientWidth) {
// if scroll is at the beginning (left)
if (target.scrollLeft === 0) {
// move content 1px to the left
target.scrollLeft = 1
} else
// if scroll is at the end (right)
if (target.scrollLeft === (target.scrollWidth - target.clientWidth)) {
// move content 1px to the right
target.scrollLeft = (target.scrollWidth - target.clientWidth) - 1
}
}

I've come across this bug before. It seems to arise when setting elements to be the height of the browser viewport (e.g., height: 100vh). Try setting this on your <body> element:
body {
position: relative;
}

Related

Issue to fire scroll events with Smooth Scrollbar plugin

I'm using the plugin Smooth Scrollbar (here).
I have an animation on my fixed header (outside the smooth scrollbar container) and triggered when user starts scrolling the smooth scrollbar container. As mentioned in the documentation (here), it's not possible that scrollbars fire scroll events. So we have to use:
scrollbar.addListener((status) => {
...
});
This is the code to animate my fixed header:
$(window).scroll(function() {
if ($(this).scrollTop() > 50){
$('.site_header').removeClass('is--large').addClass('is--small');
}
else{
$('.site_header').removeClass('is--small').addClass('is--large');
}
});
I can't figure it out how to integrate it with Smooth Scrollbar plugin. I tried various solutions to use addListener but I can't make it work.
Any help would be much appreciated!
I know it's been a while but:
const scroller = document.querySelector('.scrollable');
const bodyScrollBar = Scrollbar.init(scroller, { damping: 0.1, delegateTo: window, alwaysShowTracks: false });
bodyScrollBar.addListener( () => {
if (bodyScrollBar.scrollTop > 50){
$('.site_header').removeClass('is--large').addClass('is--small');
}
else{
$('.site_header').removeClass('is--small').addClass('is--large');
}
}
)
where .scroller is my scrolling container. Whenever you call the variable bodyScroller in the console you can see all options set and what value you can retrieve at any time. In this instance, by calling .scrollTop (NOT a function) you can retrieve its offset top value at any time.

Unexpected jump while changing position from absolute to fixed, why?

first of all I want to say that it's not about content jumping!
I have a navbar and a sidebar which both have absolute position. after user scrolls 100 pixels I change both of them to fixed. but an odd action happens (not always!). wrappers of navbar and sidebar flush for a second. I tested it with different browsers and it does not depend on browser. I tried to reproduce the situation in this fiddle:
https://jsfiddle.net/addxmkgj/
(resize the screen as large as possible it happens in large screens)
-- Edit --
https://codepen.io/anon/pen/dJKBPe
codepen link added too.
Causes
Scrolling can generate scroll events quickly and handlers may need to either throttle scroll events to some extent (e.g. perform code action after scrolling has stopped) or be fairly lightweight functions that can execute quickly.
In addition scroll event handling is not synchronized with page update: if the mouse wheel initiates downward scrolling, scrolling can continue after the wheel is released (and similarly with touch event scrolling). The browser can scroll below a top position of 100px before scroll event handling has had a chance to catch up and change the positioning.
The result is the header jumps down from being partially off-screen to occupy a fixed position at top of screen. The faster the scroll action (or the busier the browser is) the more likely it is that jumping will be noticeable.
A secondary effect in desktop browsing is that when the side bar panel scrolls upwards past top of screen and moves down again, a visible patch of white screen "flashes" momentarily below the side bar before fixed positioning takes effect.
Experimental Remedies
Flashing of the side bar can be reduced but not necessarily fully eliminated, by increasing the height of the container. Changing the height to 150% with visible overflow met with some success:
.side-bar {
position: absolute;
height: 150%;
... /* more declarations */
This may or may not conflict with application requirements.
Some mitigation of navbar jumping can be achieved by using requestAnimationFrame call backs to monitor scrollTop values and change positioning as necessary. This does not use scroll event handling as such:
$(document).ready(function() {
$(window).resize(function() {
if( $(window).width() > 850) {
$('.navbar').css('display', 'block');
} else {
$('.navbar').css('display', 'none');
}
});
scrollTo(0, 0);
var num = 100;
var bAbsolute = true;
function checkScroll() {
var newTop = $(window).scrollTop();
if( bAbsolute && newTop >= num) {
$('.navbar').css('position', 'fixed');
$('.navbar').css('top', '0');
$('.side-bar').css('position', 'fixed');
$('.side-bar').css('top', '0');
bAbsolute = false;
}
if( !bAbsolute && newTop < num) {
$('.navbar').css('position', 'absolute');
$('.side-bar').css('position', 'absolute');
$('.navbar').css('top', '100px');
$('.side-bar').css('top', '100px');
bAbsolute = true;
}
requestAnimationFrame( checkScroll);
}
requestAnimationFrame( checkScroll)
});
This code showed an improvement in jump reduction but was not perfect. It is not particularly a JQuery solution and calls requestAnimationFrame directly.
One option, of course, is to do nothing given browser timing constraints.
Update
This MDN guide for Scroll linked effects explains the root cause problem better than I was able to:
most browsers now support some sort of asynchronous scrolling .... the visual scroll position is updated in the compositor thread and is visible to the user before the scroll event is updated in the DOM and fired on the main thread ... This can cause the effect to be laggy, janky, or jittery — in short, something we want to avoid.
So the absolutely positioned elements can scroll off screen (to some extent) before scroll handlers are notified of a new scroll position.
The solution going forward is to use sticky positioning (see the scroll effects guide above or the CSS position guide. However position:sticky swaps between relative and fixed position so the HTML would need redesigning to accommodate this.
Sticky positioning is also leading edge technology at January 2018, and not yet recommended for production use on MDN. A web search for "JQuery support sticky position" revealed a choice of JQuery plugin support.
Recommendation
Potentially the best-case compromise may be to redesign the HTML to use sticky positioning and include a JQuery plugin that uses native support when available or a polyfill when not - site visitors with supporting browsers will get the best experience, those with older browsers will get functional support.

JQuery/CSS/Chrome scrolling bug (viewport drops to end of element when height changes)

On some of our pages, we've got some legacy JQuery that detects when a page is scrolling and then sets the height of the main element, to position the footer correctly on both mobile and desktop:
function scrollPage(event){
base.keepMainMarginOverFooter();
// other functions
}
$(window).on('scroll',scrollPage);
This is the offending function:
keepMainMarginOverFooter:function() {
// Adjusts container height so footer is clickable but can also be revealed on mobile
var h = 0;
$('.fp-section.visible').children().each(function() {
h+= $(this).outerHeight()
});
$('#main').css({ height: h });
}
And it seems that there's a bug in Chrome (Version 61.0.3163.100) that seems to trigger if you're actively scrolling during any event which triggers the above code (we also have some JQuery functions that , which automatically takes you to the end of the #main section, ie the bottom of the page. Or at least I guess it's a bug - this doesn't seem to happen in Safari (Version 11.0 12604.1.38.1.7), and it doesn't happen if you wait a couple of seconds after page load before scrolling.
It's tempting to remove this function for something simpler, but it's in the middle of enough dependencies that that wouldn't be trivial.
Is this a known issue with a decent workaround?

Flickering with jQuery animate scrollTop

My problem
I am making a vertical website for a client who wishes to have the window "snap" to the nearest page when most of the element is visible in the viewport. So, if the page is 85% visible, it should scroll to be 100% visible.
My problem is that occasionally when scrolling all the way to the top or bottom of the viewport, the viewport will "stick" to the first or last element, preventing a few scroll events and causing a highly noticeable flicker.
A working fiddle is here: http://jsfiddle.net/RTzu8/1/
To reproduce the error, use the scrollbar to scroll to the bottom of the page. Then, scroll up with your mousewheel. You should see the flicker. Sometimes it takes a few refreshes or attempts, but the issue is highly reproducible.
I'm at a loss as to what could be causing this issue. See below for a run-down of my code and what I have tried to prevent it so far.
My code
To accomplish my snapping, I needed to detect whether an element was a certain percentage visible. So, I added a jQuery function, isNearScreen, below. I have thoroughly tested this function, and as far as I can tell it returns accurate results.
//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;
};
I then created a snap function, which makes use of Underscore.js's _.debounce function, to only fire on the trailing end of continuous scroll events. It fires after a 500ms timeout, and I am fairly (though not 100%) convinced that it is firing correctly. I have not been able to reproduce console logs that would indicate multiple concurrent firings.
//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);
Finally, I bind to the window's scroll event
$(window).on('scroll', snap});
The (extremely simplified) 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>
and CSS:
.page{
height: 750px;
width: 100%;
margin: 10px 0;
background: gray;
}
.page-contents{
height: 100%;
width: 100%;
}
What I've tried
I have tried the following, with no success:
Setting a boolean, 'preventSnap', on the window, checking its state, and only firing the animate portion of snap if it is set to false. After animation, set it to true, then set it to false after 500ms (which should in theory prevent double firings).
Calling .stop() on the element before running the snap animation.
Calling event.preventDefault() on the scroll event before running the animation.
Reducing and increasing my _.debounce delay. Interestingly, a lower _.debounce delay (200-300ms) seems to aggravate the problem and a higher _.debounce delay (1000ms) seems to fix it. This is not an acceptable solution, however, as it feels "long" waiting 1sec for the page to "snap".
Changing the heights of the elements
If there is any other information I can provide, please let me know. I'm at a loss!
I think this is a combination of events and how _.debounce works. I noticed in the fiddle (in Chrome) that the elements were 'jitterring' long after the snap finished. If you put a console log in the snap event handler you can see it's constantly being called after a snap even with no scroll inputs.
This must be the scroll animation itself setting off the snap, I tried to set a flag to prevent dual snapping and clearing the flag after the animation was finished -- however that didn't work I think because _.debounce is queuing the event to happen later (after the animation finishes and clears the flag).
So what does work is to add this as the start of the snap handler:
var nosnap = false;
var snap = _.debounce(function(event){
// Don't snap if already animating ...
if (nosnap) { nosnap = false; return; }
nosnap = true;
Fiddle
That prevents the animation directly firing the next snap event -- however that's going to cause issues if you scroll again during the animation.
So, that's a bit of a hack. Ideally you want to be able to tell what's causing the scroll event and react accordingly but there's no easy way to do that.
I absolutely think you need to stop the animation when handling a second scroll event as well.

How to create a dynamic navigation bar which follows you when you reach certain location

I want to create a navigation bar similar to this site's:
http://www.mysupermarket.co.uk/#/shelves/top_offers_in_asda.html
Can anyone tell me how to create that navigation bar, which follows you as you scroll the page down, but not following you at the initial loading of page?
When you access to the given website, try to scrolling down and you will understand what I am talking about. The navigation bar that consists of MY SHOP, OFFERS, IDEAS & LIFESTYLE, BAKERY and so-on...
I have really no idea what it's called. At least tell me what it's called, so I'll be able to search.
Here is the solution I've done
window.onscroll = function(){
if(getScrollTop()>140) {
document.getElementById("menu").style.position="fixed";
} else {
document.getElementById("menu").style.position="";
}
}
function getScrollTop() {
if (window.onscroll) {
// Most browsers
return window.pageYOffset;
}
var d = document.documentElement;
if (d.clientHeight) {
// IE in standards mode
return d.scrollTop;
}
// IE in quirks mode
return document.body.scrollTop;
}
Holding an element on same position can be achieved by fixed position styling.
If you want your navigation bar to stay on exact same location, position:fixed; is enough. (At least non IE6)
You can find a working example and some details here
However, if you want your navigation bar to move from it's initial location to the top border of page as you scroll the page down, you must implement some JavaScript to catch page scroll event and move the <div> accordingly.
See this question for an example on how to do that.
Note: this won't work with the Android 2.3 browser; position:fixed will not behave as expected - it kinda of temporarily attaches its position to the scrolling element before jumping back to the top.
if you want you could just set the z-index to be a specific No. and that should work.
example
z-index:100;

Categories

Resources