hjf.io

React Progress Bars

13th Sep - 2019

Progress bars make a blog look cooler. More importantly, it lets the reader know that they’re almost finished with the article, which if good, if you’re overstimulated.

They’re easy to add, I’ve added one to lumen, the template that this blog is based on.

First, add the code to make it work. You’re probably looking for this -

class Page extends React.Component {
  constructor(props) {
    super(props)
    this.state = {progress: '0%'}
    this.setScroll = this.setScroll.bind(this)
  }

  componentDidMount() {
    this.setScroll()
    window.addEventListener('scroll', this.setScroll)
  }

  componentWillUnmount() {
    window.removeEventListener('scroll', this.setScroll)
  }

  setScroll() {
    const winScroll = document.body.scrollTop || document.documentElement.scrollTop
    const height = document.documentElement.scrollHeight - document.documentElement.clientHeight
    const scrolled = (winScroll / height) * 100
    const scrollPerc = `${scrolled}%`

    this.setState({ scrollPerc })
  }
}

Here, we set an initial state - scrolled 0%. We also add an event listener to update the state on scroll. The magic happens within setScroll() — we calculate the scroll position and the height of the window. Height it calculated as scrollHeight - clientHeight so that the bar starts at 0, and when we hit the bottom of the page, the bar hits 100%.

Then, we sprinkle in a bit of JSX -

<div className="post-progress" style={{width: this.state.scrollPerc}}

And a bit of styling to boot -

.post-progress {
    position: fixed;
    height: 3px;
    top: 0;
    left: 0;
    background: $color-primary;
}

By updating state on each scroll, the component re-renders and div.post-progress gets wider (or thinner) based on the scroll, looking like a scrollbar.

The code examples are used ad verbatim in this blog - give them a go!


Update (20-Sep-2019): Migrated to Hooks

Since migrating the site to hooks, The code for this is significantly cleaner.

The component is done with a functional component, with a custom useScroll hook. It looks like this:

function useScroll() {
	const [scrollPerc, setScrollPerc] = useState(getScrollPerc())
	const setScroll = () => setScrollPerc(getScrollPerc())
	useEffect(() => {
		window.addEventListener('scroll', setScroll)
		return () => window.removeEventListener('scroll', setScroll)
	})
	return scrollPerc
}

The original getScrollPerc() from before is used. It’s used by setScroll, a higher-order function. This means that an event listener can be added and removed.

useEffect is used to set an event listener that updates the state and by returning window.removeEventListener(......), the component can cleanup on unmount.