Get scrollAt, scroll position saved in socket

I would like to get client’s scroll position and track it in socket so I could trigger certain events depending on the value. I’m still lacking sufficient javascript experience, is this possible? Is it problematic? I tried something with hooks but it didn’t amount to much.

What’s your use case?

I have done this in a support-chat-like interface where we had to mount a WebView in a mobile app due to time constraints. The main issue had to do with lazy loading assets that affect the scroll height. If you structure your CSS such that you have are already holding the correct height before the asset has finished fetching, then you can write some JS hooks to do it. We didn’t need to actually store the height in the socket, just in the hook’s instance in the JS, but if we needed to it would have been trivial for us to do.

Please note, this is NOT good code in fact we’re dumping it right now so we don’t have to maintain the WebView and so our mobile folks can give it the time/love it deserves. I can’t say it’s covering scroll height in all cases, but it covers it pretty well, and it should be straightforward to extract the useful bits to your use case.

let MessagesLive = { 
   scrollRatioRelativeToTop() { 
     return window.pageYOffset / document.body.scrollHeight; 
   }, 
   scrollFromBottom() { return document.body.scrollHeight - window.pageYOffset }, 
   page() { return this.el.dataset.page }, 
   hasMore() { return this.el.dataset.hasMore == "true" }, 
   loadedInitial() { return this.el.dataset.loadedInitial == "true" }, 
   prependAvatars() { 
     const avatarContainers = document.querySelectorAll(".message-metadata[data-avatar-uid]"); 
     const templateAvatars = document.querySelector("#template-avatars").childNodes; 
     avatarContainers.forEach((avatarContainer) => { 
       let avatar = Array.from(templateAvatars).find((templateAvatar) => { 
         return templateAvatar.id == avatarContainer.dataset.avatarUid 
       }); 
       if (avatar) { 
         const clonedAvatar = avatar.cloneNode(true); 
         clonedAvatar.id = ""; 
         avatarContainer.prepend(clonedAvatar); 
       } 
  
       // remove data-avatar-uid from avatarContainer 
       delete avatarContainer.dataset.avatarUid; 
     }); 
   }, 
   beforeUpdate() { 
     // store the scroll position, so we can restore it after the update 
     this.prevYFromBottom = this.scrollFromBottom(); 
   }, 
   updated() { 
     // restore scroll position based on the previous scroll position 
     window.scrollTo(0, document.body.scrollHeight - this.prevYFromBottom); 
     document.getElementById("messages-spinner").toggleAttribute("hidden", true); 
     this.prependAvatars(); 
   }, 
   mounted() { 
     if (!this.loadedInitial()) { 
       this.pushEvent('load-initial', {}); 
       document.getElementById("messages-spinner").toggleAttribute("hidden", false); 
     }; 
  
     // scroll window to bottom when page loads 
     window.scrollTo(0, document.body.scrollHeight); 
     // store an initial scroll position (probably only needed for the case where the "bottom" is less than 10% from the "top" of the conversation) 
     this.prevYFromBottom = this.scrollFromBottom(); 
  
     this.pendingNextPageRender = this.page() 
     this.pendingResponse = false; 
  
     window.addEventListener("scroll", e => { 
       if (this.loadedInitial && !this.pendingResponse && this.hasMore() && this.pendingNextPageRender == this.page() && this.scrollRatioRelativeToTop() < 0.1) { 
         this.pendingResponse = true; 
  
         document.getElementById("messages-spinner").toggleAttribute("hidden", false); 
  
         this.pushEvent("load-more", {}, ({ page }) => { 
           this.pendingNextPageRender = page; 
           this.pendingResponse = false; 
         }); 
       } 
     }) 
   } 
 } 
  
 export { MessagesLive }