Guide for initializing the scroll percentage update when the element reaches the top of the viewport in JavaScript

Upon entering the website, the initial UI that greets you can be seen here: https://i.sstatic.net/Z3eIl.png

As you scroll down to the very beginning of section B
I anticipate that section B will stick in place and the state of currentPercentage will gradually increase from 0% as you continue scrolling.
https://i.sstatic.net/LVfba.png

Once the currentPercentage reaches 100%, the sticky effect on section B will cease, allowing you to scroll down to section C or to the bottom of the page.

https://i.sstatic.net/2BlE1.png

This functionality is reminiscent of the homepage of circle-ci.

You are invited to experiment with scrolling within this particular section.
https://i.sstatic.net/t6uV2.png

I believe there are a few key aspects that need to be addressed in order to find a solution:

  1. How to make section B stick when the scroll position reaches it
  2. How to trigger the percentage increase while reaching section B through scrolling

After attempting different approaches using CSS and JavaScript, a viable solution has yet to be found.

Extensive research has been conducted for several days to tackle this issue without success thus far.
There seems to be a lack of information available regarding handling such UX elements, which is why providing a quick solution would benefit fellow developers dealing with similar challenges in the frontend.

Any help in answering this question with an example would be greatly appreciated and serve as a valuable reference for other developers.

App.js

import "./styles.css";
import "bootstrap/dist/css/bootstrap.min.css";

import ProgressBar from "react-bootstrap/ProgressBar";

import { useState } from "react";

export default function App() {
  const [currentProgress, setCurrentProgress] = useState(100);
  return (
    <div className="App">
      <div className="sectionA">Section A</div>
      <div className="sectionB">
        Section B
        <ProgressBar
          now={currentProgress}
          label={`${currentProgress}%`}
          style={{ marginBottom: "16px" }}
        />
      </div>
      <div className="sectionC">Section C</div>
    </div>
  );
}

Codesandbox
https://codesandbox.io/s/mutable-fire-sz19b?file=/src/App.js:0-619

Update 1
The code has been updated to achieve the sticky behavior for container B, but further steps remain elusive.

App.js

import "./styles.css";
import "bootstrap/dist/css/bootstrap.min.css";

import ProgressBar from "react-bootstrap/ProgressBar";

import { useState } from "react";

export default function App() {
  const [currentProgress, setCurrentProgress] = useState(3);
  return (
    <div className="App">
      <div className="sectionA">Section A</div>
      <div style={{ position: "sticky", top: 0 }}>
        <div className="sectionB">
          Section B
          <ProgressBar
            now={currentProgress}
            label={`${currentProgress}%`}
            style={{ marginBottom: "16px" }}
          />
        </div>
        <div className="sectionC">Section C</div>
      </div>
      <div style={{ minHeight: "400vh", maxHeight: "calc(200vh - 466px)" }} />
    </div>
  );
}

Codesandbox
https://codesandbox.io/s/elated-hawking-n3rvw?file=/src/App.js:0-778

Update 2

Integration of the library react-scroll-percentage will allow scrolling to update the currentProgress

Nonetheless, the current implementation updates the currentProgress once the entire container B is visible in the viewport, contrary to the intended behavior.
https://i.sstatic.net/9L6g1.png

The desired outcome is for the scrolling to initiate the sticky feeling when Section B reaches the top of the viewport and subsequently start updating the currentProgress from 0%.

App.js

import "./styles.css";
import "bootstrap/dist/css/bootstrap.min.css";
import React, { useState, useEffect } from "react";
import { useScrollPercentage } from "react-scroll-percentage";

import ProgressBar from "react-bootstrap/ProgressBar";

export default function App() {
  const [currentProgress, setCurrentProgress] = useState(0);

  const [ref, percentage] = useScrollPercentage({
    threshold: 1.0
  });
  useEffect(() => {
    setCurrentProgress(percentage * 100);
  }, [percentage]);

  return (
    <div className="App">
      <div className="sectionA">Section A</div>
      <div style={{ position: "sticky", top: 0 }}>
        <div className="sectionB" ref={ref}>
          Section B
          <ProgressBar
            now={currentProgress}
            label={`(${currentProgress})%`}
            style={{ marginBottom: "16px" }}
          />
        </div>
        <div className="sectionC">Section C</div>
      </div>
      <div style={{ minHeight: "400vh", maxHeight: "calc(200vh - 466px)" }} />
    </div>
  );
}

Codesandbox
https://codesandbox.io/s/elated-hawking-n3rvw?file=/src/App.js:0-1029

Answer №1

To handle the scrolling functionality, it is important to listen for scroll events, monitor the scrollTop of the scrolling element, and determine the appropriate CSS styles for the progress indicator currentProgress. This process is simplified when each section has a known and consistent height. Additionally, the container element should have a sufficient predetermined height to accommodate smooth scrolling.

A basic implementation could involve:

if (scrollTop > heightOfSectionA) {
    // Set sectionB css properties to {position: fixed, top: 0, ...}
    currentProgress = (scrollTop - heightOfSectionA) / lengthOfSectionB * 100
} else {
    // Restore sectionB css properties to {position: static}
    currentProgress = 0
}

There are various ways in which the HTML and CSS structures can be designed. Utilizing position: sticky may eliminate the need for frequent CSS adjustments. However, having a clear understanding of how the CSS should evolve during scrolling ensures seamless transitions between different states.

Answer №2

give this a shot

calculateScrollRatio(element){
  const scrollRatio = (window.scrollY - element.offsetTop + element.scrollHeight) / element.scrollHeight;
  return scrollRatio < 0 ? 0 : Math.min(scrollRatio, 1)
}

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

What is the correct way to write this unusual '<a>' tag markup using Pug language?

For a list of additional features and functionalities, click here. I'm attempting to write the above in Pug format. I've tried translating it directly from HTML, but it's not displaying correctly. Below is my attempt: li For more features ...

The CSS overflow scroller trims the excess background color

Attempting to build a website, I encountered an issue with displaying a scroll bar. Despite using the CSS property overflow: auto, I faced another problem. Let me illustrate the issue through a simple example. I have an outer div with the style overflow: ...

A function that extracts a number from an array and passes it as an argument to another function

Having trouble with a piece of jQuery code :) Here’s the issue: I have an array called var arr. We have a function called get_number() which retrieves each number from the array. We also have a function with a parameter called inner_li_height(numb ...

The implementation of next-pwa's service worker file (sw.js) seems to be redundant and does

I am currently developing a NextJS application that utilizes next-pwa and functions smoothly in the local environment. However, upon deployment to Vercel, it appears that the sw.js file is unnecessary. Despite investing numerous hours into this issue, I st ...

Elements that are added dynamically do not contain any space between each other

I have a markup template that looks like this. <template id="unsequenced-template"> <button type="button" class="btn btn-primary railcar"></button> </template> Then, I use the following JavaScript ...

Exploring the ways to access inner contents of a Div element using ajax

Looking to update the SQL database with a list of variables sent from an HTML page. Some data is being sent correctly, while others are not. The list is placed in a div divided into two parts: "h1" and another "div". The header data is being sent correctly ...

JavaScript manipulates CakePHP forms

Is it possible to dynamically generate form fields using PHP? <?php echo $this->Form->input('Model.0.name', array( 'label' => __('Name') ) ); ?> I am looking for a way to create fie ...

Trouble with video embedding not adjusting properly within a Bootstrap 5 carousel

I'm currently facing an issue with two video embeds in a carousel. One has a standard bootstrap size of 16x9, while the other has a custom size of 4x5. Even though I'm using the embed-responsive code for both videos, none of them scale responsiv ...

Client Blocking Issue: ExpressJS encountering problem loading JavaScript file

I recently deployed my express app on a server (specifically, I used Heroku). Everything functions perfectly when I test the app locally. However, when I access the app online, I encounter an issue with a net::ERR_BLOCKED_BY_CLIENT error. This error states ...

"Encountering a situation where React.useContext is being recognized

I am currently facing an issue with the React context hooks in my application where the default value of my context keeps displaying as "undefined." Here are the steps I have taken to troubleshoot so far: Ensured that React.createContext is in a separate ...

Enhance your webpage with a dynamic form feature for adding multiple products using jQuery

I'm currently working on a project to develop a dynamic form that includes both input fields and dropdown menus populated with data from a JSON file (for now, I'm using an array). The form should also have the functionality to "add another produc ...

Should I consider using Derby.js or Meteor for a production app with authentication capabilities?

Recently, I delved into researching Derby.js and Meteor for a project I'm currently working on. Both frameworks offer real-time functionalities that could be quite useful. However, I have some reservations and am contemplating whether it's the ri ...

Refreshing a redux form field via a callback function

I've encountered an issue while trying to implement the following code: "redux-form": "6.0.0-rc.5", "react": "^15.3.1", "react-dom": "^15.3.1", addressUpdated(newAddress) { //TODO, inform Redux form that a value is now available! this.pr ...

Substitute a variable within a script with another variable located on the same page (JS/PHP)

I am trying to modify a script that was generated by a Wordpress plugin: <script type='text/javascript'> if (typeof(jQuery)=="function") { (function($) { $.fn.fitVids=function(){}})(jQuery) }; jwplayer('jwplayer-0').s ...

Discovering the visible boundaries of the canvas displayed on the screen

https://i.sstatic.net/iq6kb.png Currently, I am in the process of creating a game on canvas using pixi and JavaScript. The canvas itself is infinite, with only a small portion visible at any given time. The rest of the canvas extends beyond the visible ar ...

What can be used in place of Subject.prototype.hasObservers?

I have recently come across the version 4 of RxJS, and noticed that the method hasObservers on Subjects seems to have been removed. I am now faced with the challenge of migrating, as this removal is not documented on the migration guide. hasObservers: fun ...

CSS tooltip fails to display

I'm having trouble with my code as the tooltip is not showing up! Interestingly, when I tested the CSS on a static table, it worked perfectly. Essentially, the table in the code is dynamically created using information from an array for headers and d ...

The Angular/Bootstrap Modal is not functioning properly: TypeError occurs due to v2.deleteModalProject not being recognized as a function and

My main objective is to allow users to call the modal multiple times for content deletion. However, I encountered issues when the modal was triggered a second time without reloading the page. I am currently working with Angular version 1.5.6 After spendi ...

Troubleshooting tip for Angular 2: Issue with accessing object properties using dot notation

When it comes to Angular, there are two methods for accessing object values: 1. Access the property of the object using dot notation (obj.property). 2. Access the property of the object by passing in a key value pair, for example obj["property"]. If I d ...

Struggling with white space problems? JavaScript can come to the rescue with its regular expression feature for replacing

I am looking to utilize regular expressions in wrapping HTML tags around specific words within a text, Below is an example using JavaScript: In the given scenario, the first "We" is not being replaced. Why is this happening and how can it be modified? va ...