Is there a way to track the amount a user scrolls once they have reached the bottom of a page using Javascript?

It's a popular UI pattern on mobile devices to have a draggable element containing a scrollable element. Once the user reaches the end of the scrollable content, further scrolling should transition into dragging the outer element. For example, in this demonstration (), after reaching the top and trying to scroll further, the subreddits menu starts to drag.

I am interested in creating a similar functionality using JS/CSS. Is there a way to detect when users continue scrolling beyond the end? Additionally, is it feasible to measure how much they scroll past the boundary?

Answer №1

 window.onscroll = function(element) {
    if ((window.innerHeight + window.pageYOffset) >= document.body.offsetHeight) {
       alert("you're at the bottom of the page");
    }
 };

Utilizing the element parameter to accurately determine the current x and y coordinates where the mouse is located, enabling calculation of scroll distance.

Javascript: How to detect if browser window is scrolled to bottom?

Answer №2

If you want to monitor user activity after reaching the bottom or top of the page, in addition to tracking the scroll event, it's important to also track the wheel event. Furthermore, on mobile devices, tracking touchstart and touchmove events is necessary.

Since not all browsers normalize these events consistently, I created my own normalization function that looks something like this:

var compulsivity = Math.log2(Math.max(scrollAmount, 0.01) * wheelAmount);

Below is a complete interactive playground where you can test these tracking functionalities. It works best in Chrome using Mobile View in Developer Tools, or with TouchEmulator for other browsers.

// JavaScript code for tracking user activity
// ...
.ui-page {
  touch-action: none;
}
h1, h2, h3, h4, h5, h6, p {
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}
/* Custom CSS styles */
<!-- HTML markup and dependencies -->

Another important aspect to consider is the pull-to-refresh feature and the inertia or momentum of smooth scrolling behaviors. Be sure to observe how the events are tracked by scrolling or swiping, as indicated by color changes in the top and bottom bars of the page.

Answer №3

JavaScript:

// retrieve the button element
var btn = document.getElementById('btn');
// fetch the box element
var box = document.getElementById('box');

// add click event to the button to toggle show/hide for the box
btn.addEventListener('click', () => {
  box.classList.toggle('active');
});

// when scrolling inside the box
box.onscroll = function(){
  // get the top position of the div
  var boxTop = box.scrollTop;
  if(boxTop <= 0){
    // hide the box when it reaches or goes below 0 by toggling the class from "show" to "hide"
    box.classList.toggle('active');
  }
};
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
  font-size: 10px;
  font-family: 'Arial', sans-serif;
  height: 1500px;
}

html {
  scroll-behavior: smooth;
}

ul {
  list-style-type: none;
}

#theBox ul li {
  border: 1px solid;
  height: 100px;
}

#navbar-bottom {
  height: 100px;
  width: 100%;
  background: rgb(90, 111, 143);
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  box-shadow: 0 0 2px 2px rgba(90, 111, 143, 0.562);
  display: flex;
  justify-content: space-around;
  align-items: center;
}

#theBox {
  background-color: red;
  height: 350px;
  width: 100%;
  position: fixed;
  bottom: 0;
  transform: translateY(100%);
  transition: all 0.3s;
  overflow-y: scroll;
}

#theBox.active{
  transform: translateY(0);
}

.myBtns {
  width: 50px;
  height: 50px;
  border-radius: 50%;
  border: none;
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  cursor: pointer;
}

.myBtns span {
  height: 3px;
  width: 30px;
  background-color: black;
  margin: 3px 0;
}
<main role="main">

    <div id="theBox">
      <ul>
        <li><p>Text</p></li>
        <li><p>Text</p></li>
        <li><p>Text</p></li>
        <li><p>Text</p></li>
        <li><p>Text</p></li>
        <li><p>Text</p></li>
        <li><p>Text</p></li>
        <li><p>Text</p></li>
        <li><p>Text</p></li>
      </ul>
    </div>

    <div id="navbar-bottom">
      <button class="myBtns"></button>
      <button class="myBtns" id="btn">
        <span></span>
        <span></span>
        <span></span>
      </button>
      <button class="myBtns"></button>
    </div>
  </main>

jQuery:

// add click event to the button to toggle show/hide for the box
$('#btn').click(function(){
  $('#box').toggleClass('active');
});

// when scrolling on the box
$('#box').scroll(function () {
  // get the top position of the div
  var boxTop = $('#box').scrollTop();
  if(boxTop <= 0){
    $('#box').toggleClass('active');
  }
});
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
  font-size: 10px;
  font-family: 'Arial', sans-serif;
  height: 1500px;
}

html {
  scroll-behavior: smooth;
}

ul {
  list-style-type: none;
}

#theBox ul li {
  border: 1px solid;
  height: 100px;
}

#navbar-bottom {
  height: 100px;
  width: 100%;
  background: rgb(90, 111, 143);
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  box-shadow: 0 0 2px 2px rgba(90, 111, 143, 0.562);
  display: flex;
  justify-content: space-around;
  align-items: center;
}

#theBox {
  background-color: red;
  height: 350px;
  width: 100%;
  position: fixed;
  bottom: 0;
  transform: translateY(100%);
  transition: all 0.3s;
  overflow-y: scroll;
}

#theBox.active{
  transform: translateY(0);
}

.myBtns {
  width: 50px;
  height: 50px;
  border-radius: 50%;
  border: none;
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  cursor: pointer;
}

.myBtns span {
  height: 3px;
  width: 30px;
  background-color: black;
  margin: 3px 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<main role="main">

    <div id="theBox">
      <ul>
        <li><p>Text</p></li>
        <li><p>Text</p></li>
        <li><p>Text</p></li>
        <li><p>Text</p></li>
        <li><p>Text</p></li>
        <li><p>Text</p></li>
        <li><p>Text</p></li>
        <li><p>Text</p></li>
        <li><p>Text</p></li>
      </ul>
    </div>

    <div id="navbar-bottom">
      <button class="myBtns"></button>
      <button class="myBtns" id="btn">
        <span></span>
        <span></span>
        <span></span>
      </button>
      <button class="myBtns"></button>
    </div>
  </main>

Answer №4

window.addEventListener('scroll', function() {
    if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
       alert("You have reached the bottom of the page");
    }
});

Check out the live demo here: http://jsfiddle.net/5xpoe4yg/

Answer №5

Here are two different solutions for your issue, catering to touch devices and mouse devices respectively.

For Mouse Devices:

If the target is a mouse device, we can utilize the following method:

document.onwheel = event => ScrollAction(event);

For more information on the wheel event, you can visit this link.

For Touch Devices:

If the target is a touch device, the following methods will be helpful:

document.ontouchcancel = event => TouchInterrupt(event);
document.ontouchend = event => FingerRemoved(event);
document.ontouchmove = event => FingerDragged(event);
document.ontouchstart = event => FingerPlaced(event);

For further details on touch events, please refer to this link.

This complete solution should resolve your issue effectively.

Answer №6

Your specific question can be addressed by monitoring the wheel event, but keep in mind that the accuracy of the result may not be perfect. The wheel event tends to trigger before the scroll event, leading to occasional instances where a negative scroll value is logged when scrolling up from the bottom of the page for the first time:

const content = document.querySelector('.content');

for (let i = 0; i < 50; i++) {
  const p = document.createElement('p');
  p.textContent = 'Content';
  content.append(p);
};

content.addEventListener('wheel', e => {
  const atBottom = content.scrollHeight - content.scrollTop === content.clientHeight;
  if (atBottom) console.log(e.deltaY);
});
* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}

body {
  height: 100vh;
  width: 100%;
}

.content {
  overflow-y: scroll;
  height: 100%;
}
<div class="content"></div>

Alternatively, another suggestion is to implement an overlay that can be activated with a click or touch, allowing for smoother scrolling into view. It's worth noting that dealing with deeply nested scrolling elements on web browsers can become complicated quickly, especially without utilizing pure JS solutions which come with their own set of performance challenges.

Answer №7

This popup will open when clicked, allowing you to scroll. Once it reaches the top of the page, its header will stick in place.

var navbar = document.querySelector('.navbar'),
    navheader = document.querySelector('.navheader');

// Toggle navbar
navheader.addEventListener('click', e => {
  navbar.classList.toggle('open');
  if (!navbar.classList.contains('open')) {
    navbar.style.overflow = 'hidden';
    document.body.style.overflow = '';
    navbar.scrollTop = 0;
    stickTop = false;
    navbar.classList.remove('sticky');
    navbar.style.top = '';
    navbar.style.transition = '.2s';
    setTimeout(() => {
      navbar.style.transition = '';
    }, 200);
  }
  else {
    navbar.style.overflow = 'overlay';
    navbar.style.transition = '.2s';
    setTimeout(() => {
      navbar.style.transition = '';
    }, 200);
  }
})

var prevtop = 0;
var stickTop = false;

// Add scroll listener
navbar.addEventListener('scroll', e => {
  // If navbar is open
  if (navbar.classList.contains('open')) {
    if (!stickTop) {
      navbar.style.top = navbar.getBoundingClientRect().top - navbar.scrollTop + 'px';
    }
    
    if ((window.innerHeight - navbar.getBoundingClientRect().bottom) >= 0) {
      document.body.style.overflow = 'hidden';
      navbar.style.overflow = 'auto';
      navbar.style.top = 0;
      navbar.classList.add('sticky');
      stickTop = true;
    }
    
    if (navbar.scrollTop == 0) {
      navbar.classList.remove('open');
      navbar.style.overflow = 'hidden';
      document.body.style.overflow = '';
      stickTop = false;
      navbar.classList.remove('sticky');
      navbar.style.top = '';
      navbar.style.transition = '.2s';
      setTimeout(() => {
        navbar.style.transition = '';
      }, 200);
    }
  }
})
body {
  font-family: sans-serif;
}

.navbar {
  position: fixed;
  top: calc(100vh - 50px);
  height: 100vh;
  left: 0;
  width: 100%;
  overflow: hidden;
}

.navbar.open {
  top: 50vh;
}

.navcontent {
  background: black;
  width: 100%;
  color: white;
}
.navcontent p {
  margin: 0;
}

.navheader {
  height: 50px;
  width: 100%;
  background: lightblue;
  cursor: pointer;
  top: 0;
  position: sticky;
  display: flex;
  justify-content: center;
  z-index: 1;
}

.navheader::before {
  width: 50px;
  height: 3px;
  margin-top: 10px;
  background: white;
  border-radius: 3px;
  content: '';
}
<div class="navbar">
  <div class="navheader"></div>
  <div class="navcontent"><p>S</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>E</p></div>
</div>
<div class="content">
<p>S</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>A</p><p>E</p>
</div>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Upcoming examination on SEO using React testing library

Currently, I am in the process of testing out my SEO component which has the following structure: export const Seo: React.FC<Props> = ({ seo, title, excerpt, heroImage }) => { const description = seo?.description || excerpt const pageTitle = s ...

Access PHP variables in JavaScript

In my project, I have a file named english.php which holds various variable values stored in the $LANG array. For example: $LANG['value_1']="abc"; $LANG['value_2']="xyz"; In addition to numerous .php files that include require_once( ...

What is the best way to display a message on the 403 client side when an email sending fails?

I am attempting to display an alert message when the email is sent successfully or if it fails. If it fails, I receive a 403 status code from the backend. However, I am unsure how to handle this error on the client-side. In the case of success, I receive a ...

Creating a vibrant and mesmerizing inward spiraling rainbow of colors on canvas using JavaScript

After coming across an image that caught my eye, I was inspired to recreate it using canvas: https://i.stack.imgur.com/fqk3m.png I've attempted drawing arcs starting from the center of the screen, but I'm struggling with getting their paths acc ...

What is the process for importing a JavaScript file into a Vue component?

Having trouble importing JSON results into a Vue component? The results are as follows: [{"id":"d023c5e3-ca3c-4d97-933a-1112a8516eee", "score":9001, "updated":"2018-12-07T13:48:33.6366278", "player":Johanna, "category":Funny}, {"id":"398b65fb-e741-4801-b ...

Toggle class on element based on presence of class on another element

If I have 4 boxes and the user is required to choose one. I aim to assign an active class to the box that the user clicks on. However, if the user selects another box, I want to remove the class from the first clicked box and apply it to the newly clicked ...

The special function fails to execute within an "if" condition

As a newcomer to JavaScript/jQuery and Stack Overflow, I kindly ask for your patience in case there are any major errors in my approach. I am currently developing an HTML page with Bootstrap 3.3.7, featuring a pagination button group that toggles the visib ...

Execute the identical script in NPM, but with various parameters each time

Recently, I created a nodeJS script with a parameter. Currently, using npm start allows me to pass arguments and run my script successfully. However, I'm now faced with the challenge of passing multiple arguments to npm start in order to run multipl ...

Client.db is undefined error encountered in MongoDB backend API

I'm having trouble retrieving data from a collection in my MongoDB backend. Every time I try, I encounter an error stating that the client is not defined. Has anyone else experienced this issue and knows how to resolve it? Error: Client is not define ...

The jQuery extension for XDomainRequest in the $.ajax function called onprogress is

Summary: I am trying to make this compatible with this: Detailed Explanation: My goal is to develop a jQuery extension that introduces a progress method to the $.ajax object and works seamlessly with IE8 & IE9's XDomainRequest object. Currently, I ...

The Vue-router is constantly adding a # symbol to the current routes, and it's important to note that this is not the typical problem of hash and

After setting up my router file using Vue 3, it looks like this: import { createRouter, createWebHistory } from "vue-router"; import Home from "../views/Home.vue"; const routes = [ { path: "/", name: &quo ...

Vanishing border around the sticky table header

While working on creating a table in an excel style using html and css with a sticky header, I noticed that the borders on the table head appeared strange. Take a look at the code snippet below: table { border-collapse: collapse; position: relative ...

How can the value of a button be displayed in an input box using an onclick function?

When the button '1' is clicked, it displays the value "1" inside a <!p> tag. However, the second button '2' does not display the value "2" in the input box. Even though both functions are identical. //Function with working func ...

What is the method for implementing a distinct background in dark mode while utilizing Material UI Themes?

I am facing an issue with changing the background color when in dark mode. Here is my initial code snippet... export const myThemeOptions: ThemeOptions = { palette: { mode: "light" as PaletteMode, primary: { main: ...

The spread operator in Vuex is causing compilation errors with babel, as it continuously results in a module build failure

Currently, I am utilizing a spread operator within my mapGetters object. It is crucial to use a specific babel-preset-stage for proper ES6 compilation. Even after installing babel-preset-stage-2 via npm, I encountered the following error: ERROR in ./~/bab ...

What is the best way to effectively incorporate Ruby into the CSS attribute of a HAML %li element?

Greetings everyone, I am new to the world of development and seeking guidance from experienced individuals. I have been trying to solve a coding issue for quite some time now. I am currently enrolled in a programming course at Code Academy based in Chicago ...

The JavaScript script to retrieve the background color is malfunctioning

I am currently working on developing a highlighting feature for an HTML table that will dynamically change the row colors on mouseover. Below is the code snippet I have been using, but it seems to be experiencing some issues. Any assistance would be greatl ...

What is the correct way to pass the res object into the callback function of a jest mock function?

Currently, I am working on developing a web server using Node.js and am in the process of ensuring comprehensive test coverage with Jest. One specific function, logout, requires testing within the if statement where it checks for errors. // app.js functio ...

Retrieve the exact value of a key within a string

This is my unique text: let x = "Learning new things every day!"; I am utilizing the substring() method to extract a specific part of it: console.log(x.substring(9, 12)); Instead of the entire string, I only want the word 'new'. let x = "l ...

Exploring the Intersection of Windows 8 Store Applications and jQuery: Leveraging MSApp.execUnsafeLocalFunction

Developing a Windows 8 JavaScript Store App (using Cordova) has led to some complications when using jQuery. It seems that in order to utilize certain functions, I have had to modify the jQuery library by adding: MSApp.execUnsafeLocalFunction While this ...