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.
Related
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!
I have a header with a background color of transparent which will change to black when the user scrolls. Since I am using the requestAnimationFrame to accomplish this, the transition should pause when the tab is not active to save resources. I tested this with a function counting to 300 which indeed did pause when the tab was not active and resumed to 300 when active. However, it seems that my header's background color transition does not pause when on a new tab.
I am using mozilla's example which says "This example optimizes the scroll event for requestAnimationFrame." So I think it should work well with my use case (which it does) I would just like some insight as to why my transition doesn't pause when on a different tab to save resources and be as optimal as possible. Thanks!
https://developer.mozilla.org/en-US/docs/Web/API/Document/scroll_event
"use strict";
// Reference: http://www.html5rocks.com/en/tutorials/speed/animations/
const $ = selector => document.querySelector(selector);
let lastKnownScrollPosition = 0;
let ticking = false;
function doSomething(scrollPos) {
// Do something with the scroll position
if ( scrollPos > 0 ) {
$("header").style.backgroundColor = "black";
}
else {
$("header").style.backgroundColor = "transparent";
}
}
document.addEventListener('scroll', function(e) {
lastKnownScrollPosition = window.scrollY;
if (!ticking) {
window.requestAnimationFrame(function() {
doSomething(lastKnownScrollPosition);
ticking = false;
});
ticking = true;
}
});
Here is my JSFiddle https://jsfiddle.net/Boros/zjwyamvq/3/
A different approach would be to use an IntersectionObserver.
If you place a tiny (1px x 1px) element at the top of the page you can set an IntersectionObserver on it which will tell you as soon as it goes out of the viewport or comes back in.
That way you only have to run JS when there is a change from the user having scrolled away from the top - which will happen only once until they scroll back up to the top.
I don't know how much more efficient this is than using the traditional scroll and trying to throttle it, but MDN says:
sites no longer need to do anything on the main thread to watch for this kind of element intersection, and the browser is free to optimize the management of intersections as it sees fit.
The very fact that you aren't coming back to execute some JS every time the user does a scroll must help regardless of whether the browser does additional optimisation.
Here's a trivial example. It adds the 1px div, sets an IntersectionObserver on it and if the div goes out of the viewport that means the user has scrolled and if it is in the viewport the user has scrolled back to the top or the system is at the start position.
Note, if you want to make it slightly less sensitive you can set the check div to have a height of say 20px so if the user scrolls back to pretty near the top the color changes.
const $ = selector => document.querySelector(selector);
const check = (entries) => {
$("header").style.backgroundColor = (entries[0].isIntersecting) ? 'transparent' : 'black';
};
const observer = new IntersectionObserver(check);
observer.observe($("#checker"));
<div id="checker" style="width: 1px; height: 1pc; position: absolute; top: 0; left; 50%;"></div>
<header style="width: 100vw; height: 200vh; transition: 5s;">SCROLL DOWN TO SEE BACKGROUND COLOR CHANGE TO BLACK</header>
After a page loads, I’d like to observe an element (addonCard) using the Intersection Observer API to know if that element is fully visible or not. If the element is invisible or partially visible, I want the element to be scrolled into full visibility. If it’s already fully visible, I want the element to stop being observed. From my understanding, you can check for full visibility by setting the threshold property to 1. However, my implementation below doesn’t work (the element is scrolled regardless of whether it's fully visible or not):
let addonCard = this.querySelector(`[addon-id="${param}"]`);
let observer = new IntersectionObserver(
(entries, observer) => {
entries.forEach(entry => {
if (entry.intersectionRatio != 1) {
let stickyHeaderHeight = document.querySelector(
"#page-header > .sticky-container"
).offsetHeight;
let topOfTarget = entry.target.offsetTop - stickyHeaderHeight;
window.scrollTo({
top: topOfTarget,
behavior: "smooth"
});
} else {
observer.unobserve(entry.target);
}
});
}, {
threshold: 1,
}
);
observer.observe(addonCard);
Can someone explain why this implementation doesn't work and how I can make it work? Why does entry.intersectionRatio never change from 0?
Expected behavior: addonCard should scroll into full visibility if not fully visible. If it's already fully visible, no scrolling should be done.
Actual behavior: Scrolling occurs regardless of whether addonCard is fully visible or not.
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!
Hello Coders! I am coding a chat system, I would like the scroll bar to stay at the bottom. In this way when someone sends a message the user would not have to scroll down. I would also like it to start at the bottom of the page load. Additionally, the user can scroll up when needed, but when they scroll back down it locks in place. I have tried many times but for some reason it does not work, I was wondering if anyone has a method of doing this?
I've also have struggled with implementing this for a while. The approach I've ended up with relies on using MutationObservable
MutationObservable allows to watch for DOM changes inside element and perform some actions when deeply nested element is changed (in your case, for example, new comment was rendered):
// chatContainer is reference to your chatContainer element
var isLocked = true;
var mutationObserver = new MutationObserver(() => {
if (isLocked) {
scrollToBottom();
}
});
mutationObserver.observe(chatContainer, {
childList: true,
subtree: true,
attributes: false,
});
This gave me a callback, where I likely had to make chatContainer scroll to the bottom.
Scroll to the bottom implementation could be:
function scrollToBottom() {
chatContainer.scrollTop = 99999999999;
}
To change isLocked flag, I had to listen for user scrolls on chatContainer and update it accordingly:
var LOCK_OFFSET = 25; // how many pixels close to bottom consider scroll to be locked
chatContainer.addEventListener('scroll', handleUserScroll);
function handleUserScroll() {
var scrollFromBottom =
chatContainer.scrollHeight -
chatContainer.scrollTop -
chatContainer.clientHeight; // how many pixels user scrolled up from button of the chat container.
isLocked = scrollFromBottom > LOCK_OFFSET; // set new isLocked. lock, if user is close to the bottom, and unlock, if user is far from the bottom.
});
Hope I've explained the general idea. This approach works fine for me. User scroll listening should be improved with events debouncing. And don't forget to dispose scroll event and mutation observer subscriptions.
This is what I've used in my projects. It works on IE, Edge, Firefox, Chrome and Opera. I'm not sure about Safari.
var a = document.querySelector('#divchat');
a.scrollIntoView(false);
I hope it helps.
There are a lot of answers for this question around.
Here is one of them from Automatically scroll down chat div
The main point of that answer is the variables scrollHeight, scrollTop, and clientHeight can be manipulated.
But basically, to scroll down it is to use container.scrollTop = container.scrollHeight;