Showing & Hiding Navbar Bug on Momentum-Based Touch Devices - javascript

I used the code below to add a simple navbar hiding and showing when a user scrolls up and down on a webpage.
const Navbar = () => {
const [show, setShow] = useState(true);
const [lastScrollY, setLastScrollY] = useState(0);
const controlNavbar = () => {
if (typeof window !== 'undefined') {
if (window.scrollY > lastScrollY) { // if scroll down hide the navbar
setShow(false);
} else { // if scroll up show the navbar
setShow(true);
}
// remember current page location to use in the next move
setLastScrollY(window.scrollY);
}
};
useEffect(() => {
if (typeof window !== 'undefined') {
window.addEventListener('scroll', controlNavbar);
// cleanup function
return () => {
window.removeEventListener('scroll', controlNavbar);
};
}
}, [lastScrollY]);
return (
<nav className={`active ${show && 'hidden'}`}>
....
</nav>
);
};
export default Navbar;
It works perfectly well on a desktop browser, but had a bug when testing it on my iphone (on both chrome and safari). On touch devices, when scrolling back to the top, there's a little bounce animation where the scroll goes past the top of the page and then rubberbands back to the actual top of the page. This behaviour causes my event listener to register that the user scrolled down, and hides the navbar. Similarly, when the user scrolls to the bottom of the page, the rubberband effect causes my navbar event listener to register that the user scrolled up (when it bounces back) and shows the navbar. I hope I'm explaining this clearly (lol).
I'm trying to think of a solution, and the best one I came up with is to set the hiding and showing behaviour to work after a scroll of a certain number of pixels, so something like this.
if (window.scrollY > lastScrollY + 20){
follow abovementioned logic...
}
But since the bounce amplitude is based on the user's scroll speed, if the user scrolls really aggressively to the top of the window it definitely is going to bounce more than 20 pixels and therefore the same bug occurs.
I can't seem to even find others facing this problem online, so any help would be appreciated!

Related

One more automatically Re-render in React

In my React project I use scrolling function to go to the same scroll position after coming back from user page (using scroll ref in div), but when page reloads scroll doesn't go to position, but if I'll mechanically re-render page, it goes to the defined position. So I need one more re-render page to scroll it to position, but should be automatically and not after manually on click. anyone can help how can I fix this issue?
// go back to saved scroll position
const Scrolling = () => {
const pos = localStorage.getItem("webLocal:scrollPosition");
const w = document.getElementById("feed");
return (w.scrollTop = pos);
};
useEffect(() => {
Scrolling();
}, [userList]);
I tried lot of ways, setInterval, useEffect, useMemo etc. with all possible dependencies..

Detect scrolled to top of div with overflow: scroll in ReactJs

I'm creating a messaging feature. The conversation occupies only 100vh height minus the header - so, I have to make its overflow set to scroll. How do I detect if the user scrolls to the topmost part of the conversation? I will fetch older messages once the user scrolls to the top.
I did some research but none of them are working. I think they're only applicable for a scrollable page.
The code snippet below is similar to what I am seeing over the web. However, the scroll never gets triggered.
const top_ref = React.useRef < HTMLDivElement > null;
const trackScrolling = () => {
console.log("scrolling");
};
React.useEffect(() => {
document.addEventListener("scroll", trackScrolling);
return () => {
document.removeEventListener("scroll", trackScrolling);
};
});
As pointed out by cloned, Intersection Observer does the job.

How to keep div always visible in mobile with user scalable page?

Basically I want to build a page that consists of a weekly calendar. Since the calendar is big I want users to be able to zoom out on it to have an overview and to scroll to the specific hour they want. The problem is that when scrolling the users lose the context of the day they are viewing. Its also important to note that the day is on the left of the page and the scroll is done horizontally.
I built a solution based on window.scrollX that uses a transform to translate the div to the left side of the viewport. This works well in a browser, but unfortunately it doesnt work on mobile. The only way for the translation to be correct is by unzooming the page to maximum on Chrome mobile at least. After trying to debug on Google Chrome responsive design, I see that window.scrollX event holds the value 0 even if the scroll bar is not at the beggining
I would like to know if someone had this problem and have any idea how i should keep a div visible always independently of the zoom and scroll. Note that adding the meta viewport minimum-scale=1 solves the problem but that way I cant unzoom no more.
EDIT:
Here's more info of what I'm doing currently. I'm using react and I have a hook (https://github.com/neo/react-use-scroll-position) that does the following:
useEffect(() => {
let requestRunning = null
function handleScroll () {
if (isBrowser && requestRunning === null) {
requestRunning = window.requestAnimationFrame(() => {
setScrollPosition(getScrollPosition())
requestRunning = null
})
}
}
if (isBrowser) {
window.addEventListener('scroll', handleScroll)
window.addEventListener('touchmove', handleScroll)
return () => {
window.removeEventListener('scroll', handleScroll)
window.removeEventListener('touchmove', handleScroll)
}
}
}, [])
I have changed the getScrollPosition() function to use document.body.getBoundingClientRect() without success, and the original function uses window.scrollX.
Here's a picture of what I want to achieve in mobile:
I get that to work, but by limiting the viewport to disable unzoom. But I want the user to be able to unzoom and scroll horizontally while that div is always on the left side of the visible window. Any ideas/alternatives would be really appreciated!

sticky navbar does not work on mobile while scrolling

I've created a sticky navbar for a subnav, which should stick at the top of the screen when the user scrolls down. Therefore I've tried some javascript, which changes the position to 'fixed' when the top is reached. Avoiding a gap in the content when the navbar is taken out of the flow, I've also added a placeholder, which has the same height as the navbar.
On Desktop it really works and looks how it should be. But I got a "touch" issue on mobile view. When I scroll down on mobile view the navbar will not appear during the process of scrolling over the viewpoint, where the css class is changing. It only appears when I stop scrolling after that viewpoint. When it shows up I can normally scroll up and down and I am only getting this issue again if I repeat this procedure, where the navbar has to change the css class. So it might be a problem with the css class change and I guess the problem could be in the javascript snippet. Does anybody know a solution for this? I'd like to have the same behavior like on desktop view, so the navbar is always visible and just fixed to the very top of the screen, even if it is in the flow of scrolling.
JS:
var menu = document.querySelector('#irp-localnav');
var menuPosition = menu.getBoundingClientRect();
var placeholder = document.createElement('div');
placeholder.style.width = menuPosition.width + 'px';
placeholder.style.height = menuPosition.height + 'px';
var isAdded = false;
window.addEventListener('scroll', function() {
if (window.pageYOffset >= menuPosition.top && !isAdded) {
menu.classList.add('sticky');
menu.parentNode.insertBefore(placeholder, menu);
isAdded = true;
} else if (window.pageYOffset < menuPosition.top && isAdded) {
menu.classList.remove('sticky');
menu.parentNode.removeChild(placeholder);
isAdded = false;
}
});
If you guess an error in the html/css markup, just let me know, so I get in touch with you again by posting this markup
Kind Regards
I was able to hack around. For anyone, who is facing a similiar issue:
Mobile browsers simply do not fire on a scroll event, while the event is in process. They fire when the event has stopped, so, when you've stopped scrolling. Using translate3d(0px,0px,0px) can solve this. Refer to this thread to read more about it:
iOS 9 Safari: changing an element to fixed position while scrolling won't paint until scroll stops
Kind Regards!

Disable rubber band in iOS full screen web app

I have a full screen web app running on iOS. When I swipe down, the screen scrolls with the rubber band effect (bumping). I want to lock the whole document but still allow scrolling divs with overflow-y: scroll where needed.
I have experimented with
document.ontouchmove = function(e){
e.preventDefault();
}
but this disables scrolling in any container. Any idea? Thank you very much.
Calling preventDefault on the event is actually correct, but you don't want to do it for every component since this will also prevent scrolling in divs (as you mention) and sliding on range inputs for instance. So you'll need to add a check in the ontouchmove handler to see if you are touching on a component that is allowed to scroll.
I have an implementation that uses detection of a CSS class. The components that I want to allow touch moves on simply have the class assigned.
document.ontouchmove = function (event) {
var isTouchMoveAllowed = false;
var p = event.target;
while (p != null) {
if (p.classList && p.classList.contains("touchMoveAllowed")) {
isTouchMoveAllowed = true;
break;
}
p = p.parentNode;
}
if (!isTouchMoveAllowed) {
event.preventDefault();
}
});

Categories