javascript - How to start update scroll percentage when element is on the top of viewport

Viewed 469

When entering the website, this is the first UI you'll see. enter image description here

Then, if you scroll down to the very beginning of section B
I expect the section B will become sticky and the currentPercentage state will start increasing from 0% gradually if you scroll.
enter image description here

When the currentPercentage state reach 100%, the sticky effect will no longer apply to sectionB, then you can scroll down to section C or the bottom of the page.

enter image description here

It's kind of like circle-ci homepage

You can try to scroll in this section.
enter image description here

I think there are a few things we need to figure out for the solution

  1. How to make section B become sticky when scroll position arrives section B
  2. How to scroll and increase the percentage when arriving section B

I've tried thinking in both css and javascript way, still cannot find the solution

I've also done lots of research for this problem for a few days but still not figuring it out.
I believe a lots of developers would like to know how to do this kind of effect in their frontend, and it will be beneficial for other developers to have quicker solution to deal with this kind of UX in the future, since from what I've noticed no one has mentioned this kind of UX handling.

I would be appreciated if someone can answer this question with an example, for the 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
I updated the code and can be able to make container B sticky, but dont know how to continue for the UX

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

Added library react-scroll-percentage to scroll to update currentProgress

However, when the whole containerB exposed in the viewport, the currentProgress will start updating, which is not what I want
enter image description here

What I want is when SectionB reach the top of the viewport, the scrolling will make user feel sticky and then currentProgress will start updating 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

1 Answers

You need to listen to scroll events, check scrollTop of the scrolling element, and decide what the css and currentProgress need to be. This is easiest when the height of each section is known and constant. The outer element also needs a large predetermined height, to leave room for scrolling.

The code might just be

if (scrollTop > heightofSectionA) {
    //set sectionB css to {position: fixed, top: 0, ...}
    currentProgress = (scrollTop-heightofSectionA)/lengthofSectionB*100
} else {
    //set sectionB css to {position: static}
    currentProgress = 0
}

There is a number of ways the html and css could look. With position: sticky you might not need to change css at all. But as long as you know what the css at any stage of the scroll should look like, you should have no problems switching between them.

Related