(re)setting something on scroll-into-view

At the end of https://beta.observablehq.com/@forresto/labyrinth there are some animations that are done by the time the reader gets to bottom of page. Is there a way to trigger (re)setting the seed to start the animation when the person scrolls there (for the first time)?

Certainly – at least in nice browsers (not Safari, yet)! See my pauser notebook, which uses IntersectionObserver to pause cells until a user can see them in their viewport.

1 Like

:heart_eyes_cat: pauser works great in that notebook (scroll to last two sections). I have an initial board state to show until the el is in the viewport.

Plus, I learned some more about this generator stuff, which is all new to me. Thanks!!!

One caveat I noticed with IntersectionObserver : if the element is wider than the cell view it won’t start.

@forresto That is the case if you’re using threshold = 1. If you use threshold = 0 it should work, albeit starting as soon as any part of the element is visible. Alternatively, you can set the intersection observer to observe the cell’s parent node:

{
  const element = md`Hello, world!`;
  yield element;
  fadeIn(element.parentNode);
}

You must yield the element first so that it’s added to the DOM, but then you can register the intersection observer to fade-in when it’s visible.

https://beta.observablehq.com/@mbostock/intersection-observer

1 Like

Thanks to Mike and Tom for making these small tools. Both pauser and fadeIn work great!

I wonder if that’s something you could consider making the default for all the cells in a notebook (if the user chooses so), especially when they are writing a long computational essay and don’t want to drain the reader’s browser.

Yes, I’ve had this thought—it should be possible to defer evaluation of a cell that isn’t visible (and isn’t referenced by another cell that is visible). In addition to making the page lighter by automatically pausing animations, it would allow animations to start automatically when they first become visible, improving readability.

But cells can also have side-effects that aren’t tracked by the runtime, such as reaching into another cell’s value and mutating it. So probably we’ll want to make pausing opt-in.

1 Like

Intersection Observer should now (or soon) work in Safari,

https://webkit.org/blog/8718/new-webkit-features-in-safari-12-1/

There is also this polyfill, which seems to work for the bare basics at least (I have not tested it at all thoroughly)

The code I have been using is like:

Down at the bottom of the notebook:

IntersectionObserver = { //polyfill
  await require('https://bundle.run/intersection-observer');
  return window.IntersectionObserver; }

Then an animate function

animate = function(element, invalidation, drawframe) {
  let frame, tick, newframe, cleanup, intersect;
  newframe = function newframe() { frame = requestAnimationFrame(tick); };
  cleanup = function cleanup() { cancelAnimationFrame(frame); };
  tick = function tick() { drawframe(); newframe(); }
  intersect = function intersect(entries) {
    for (let e of entries) e.isIntersecting ? newframe() : cleanup(); }
  new IntersectionObserver(intersect).observe(element);
  newframe();
  invalidation.then(cleanup);
  return element;
}

And then finally in my cell,

{
  element = whatever
  ... set up drawing ...

  return animate(element, invalidation, () => {
    ... draw the frame ...
  });
}

If anyone runs into any issues with this in any browsers on e.g.

I’ll try to fix.


Edit: I put both loading the polyfill (only if IntersectionObserver is unavailable) and the definition of the animate function into @jrus/misc#animate.

2 Likes