iamdustan / smoothscroll

Scroll Behavior polyfill
http://iamdustan.com/smoothscroll/
MIT License
3.85k stars 340 forks source link

Safari Exception when smooth scrolling to `documentElement` #138

Open danielnixon opened 5 years ago

danielnixon commented 5 years ago

document.documentElement.scrollIntoView({behavior:"smooth"})

=> TypeError: undefined is not an object (evaluating 'el.clientHeight')

danielnixon commented 5 years ago

document.documentElement.scrollIntoView({behavior:"auto"}) works as expected

dockleryxk commented 5 years ago

I have a similar issue. While I don't have an exception, nothing happens instead.

Also, if I use body instead of documentElement, scrolling works on Safari and iOS, but then breaks on Chrome, so I detected the device to see which to use first.

Clarification: my issue ended up having nothing to do with this package, just sharing what I did to fix it.

Here is some code I used:

let scrollElement;
if (isDumbBrowser) {
  scrollElement = document.body;
}
else {
  scrollElement = document.documentElement;
}
scrollElement.scrollBy({
  top: 250,
  left: 0,
  behavior: 'smooth'
});
mattpilott commented 5 years ago

I'm seeing the same thing @dockleryxk whereby: steps.children[i].scrollIntoView({ behavior: 'auto' }); Works fine on Chrome but is completely dead in Safari with no errors or anything. Any idea how I can fix that?

dockleryxk commented 5 years ago

@matt3224 are you using a particular framework or anything? My "isDumbBrowser" variable is true for Edge, iOS (Chrome or Safari), Safari (not on iOS), or Trident (Microsoft rendering engine)

mattpilott commented 5 years ago

I'm using svelte if that helps, not sure what i could do even if i had my own isDumbBrowser as this element based rather than on the body/documentelement?

dockleryxk commented 5 years ago

@matt3224 Ahhh right... so this is my full logic. Because scrollIntoView doesn't have great support, I only ever use it in certain cases. I'm using Angular btw so ignore the "this." and ngZone stuff. Also, el is the element being scrolled to.

/**
 * @param anchor id of element to scroll to
 * @param block see mozilla link below, can be 'start', 'center', 'end', or 'nearest'
 * @param forceScrollInfoView use element.scrollIntoView no matter what
 */
scrollToAnchor(anchor, block, forceScrollInfoView = false) {
  this.ngZone.runOutsideAngular(() => {
    // see https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
    try {
      const el = this.document.getElementById(anchor);
      if (!forceScrollInfoView && block === 'start') {
        let scrollElement;
        if (this.isDumbBrowser) {
          scrollElement = this.document.body;
        }
        else {
          scrollElement = this.document.documentElement;
        }
        scrollElement.scrollBy({
          top: this.getScrollByTop(el.getBoundingClientRect().top),
          left: 0,
          behavior: 'smooth'
        });
      }
      else {
        el.scrollIntoView({
          behavior: 'smooth',
          block: block,
          inline: 'nearest'
        });
      }
    }
    catch (error) {
      console.warn('Scroll Error Caught:', error);
    }
  });
}

Here is the getScrollByTop function, but basically it just subtracts heights if there are sticky navigation bars at the top of the page. You may or may not need to do that.

private getScrollByTop(elTop) {
  let subtractForStickies = parseInt(stickyNavbarheight, 10);
  if (otherBarIsSticky) {
    subtractForStickies += parseInt(otherBarHeight, 10);
  }
  return elTop - subtractForStickies;
}
mattpilott commented 5 years ago

Cool thanks i'll see how i get on!

dockleryxk commented 5 years ago

@matt3224 let me know what you come up with!