Intersection Observer

UPDATE (9-28-17): Un-implemented. Performance lousy; polyfill seemed to bork Safari?!

Original Post

Implemented 'j' and 'k' shortcuts on the main blog index today, to move forward and back through posts.

Gatsby uses a template file to create each page of posts (/src/templates/blog-index.js); those created pages in turn call a React component to "unroll" and display each set of posts sent to them in props. I cleverly called it Posts.

To implement the shortcuts I changed Posts to be a stateful component. The state holds an array called visible, intended to indicate which posts are currently in the viewport.

I added the react-scroll module (here), and set it up inside componentDidMount, along with a keydown listener on window. So... when the component is mounted in the browser, there's a function watching keypresses, and a pair of functions watching scrollEvents.

Then I implemented react-intersection-observer. Each post's code is wrapped in an Observer. When the observer enters the viewport, it fires a function adding it's index # to this.state.visible. When it leaves the viewport, the same function filters it back out of the array. It makes more sense, maybe, if you console.log that array while scrolling on the page -- the array goes from [0] to [0,1] to [1] to [1,2]... then there are 2 short posts in a row, all at least partially in the viewport, so it goes to [1,2,3], before dropping back to [2,3].

The keypress function calls scroll, when 'j' or 'k' is pressed. If the user presses 'j', react-scroll goes to the second number in the array (if the array has multiple numbers) or to the next number after the sole number in the array. So... if a half a post is on the top, and the next one in the middle of the viewport, the "middle post" scrolls to the top. If only one post is in the viewport, the next post scrolls up.

If the user presses 'k', the whole thing happens more or less the same, in reverse.

a gif of the page scrolling and console log changing

There are some edge cases where this process isn't perfect, and I hope to improve upon it, but it's not bad, considering. N.B., only after I put this together did I learn that there's a fairly well-supported DOM method called scrollIntoView that might be part of a more perfect solution. TBD...