diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index f4a8cb30b24a..86879fda27ba 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -69,6 +69,9 @@ class ScrollSpy extends BaseComponent { this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element this._activeTarget = null this._observer = null + // Sometimes scrolling to the section isn't enough to trigger a observation callback. + // Scroll to up to 2px to make sure it triggers. + this._scrollOffset = 2 this.refresh() // initialize } @@ -132,7 +135,8 @@ class ScrollSpy extends BaseComponent { if (observableSection) { event.preventDefault() const root = this._rootElement || window - const height = observableSection.offsetTop - this._element.offsetTop + + const height = observableSection.offsetTop - this._element.offsetTop - this._scrollOffset if (root.scrollTo) { root.scrollTo({ top: height, behavior: scrollBehavior }) return diff --git a/js/tests/unit/scrollspy.spec.js b/js/tests/unit/scrollspy.spec.js index 7dc9f44cc803..7947c0ef6f1b 100644 --- a/js/tests/unit/scrollspy.spec.js +++ b/js/tests/unit/scrollspy.spec.js @@ -858,9 +858,9 @@ describe('ScrollSpy', () => { setTimeout(() => { if (div.scrollTo) { - expect(clickSpy).toHaveBeenCalledWith({ top: observable.offsetTop - div.offsetTop, behavior: 'auto' }) + expect(clickSpy).toHaveBeenCalledWith({ top: observable.offsetTop - div.offsetTop - scrollSpy._scrollOffset, behavior: 'auto' }) } else { - expect(clickSpy).toHaveBeenCalledWith(observable.offsetTop - div.offsetTop) + expect(clickSpy).toHaveBeenCalledWith(observable.offsetTop - div.offsetTop - scrollSpy._scrollOffset) } done() @@ -883,9 +883,9 @@ describe('ScrollSpy', () => { setTimeout(() => { if (div.scrollTo) { - expect(clickSpy).toHaveBeenCalledWith({ top: observable.offsetTop - div.offsetTop, behavior: 'smooth' }) + expect(clickSpy).toHaveBeenCalledWith({ top: observable.offsetTop - div.offsetTop - scrollSpy._scrollOffset, behavior: 'smooth' }) } else { - expect(clickSpy).toHaveBeenCalledWith(observable.offsetTop - div.offsetTop) + expect(clickSpy).toHaveBeenCalledWith(observable.offsetTop - div.offsetTop - scrollSpy._scrollOffset) } done() @@ -943,17 +943,17 @@ describe('ScrollSpy', () => { const link = fixtureEl.querySelector('[href="#div-jsm-1"]') const observable = fixtureEl.querySelector('#div-jsm-1') const clickSpy = getElementScrollSpy(div) - // eslint-disable-next-line no-new - new ScrollSpy(div, { + + const scrollSpy = new ScrollSpy(div, { offset: 1, smoothScroll: true }) setTimeout(() => { if (div.scrollTo) { - expect(clickSpy).toHaveBeenCalledWith({ top: observable.offsetTop - div.offsetTop, behavior: 'smooth' }) + expect(clickSpy).toHaveBeenCalledWith({ top: observable.offsetTop - div.offsetTop - scrollSpy._scrollOffset, behavior: 'smooth' }) } else { - expect(clickSpy).toHaveBeenCalledWith(observable.offsetTop - div.offsetTop) + expect(clickSpy).toHaveBeenCalledWith(observable.offsetTop - div.offsetTop - scrollSpy._scrollOffset) } done() @@ -977,17 +977,17 @@ describe('ScrollSpy', () => { const link = fixtureEl.querySelector('[href="#présentation"]') const observable = fixtureEl.querySelector('#présentation') const clickSpy = getElementScrollSpy(div) - // eslint-disable-next-line no-new - new ScrollSpy(div, { + + const scrollSpy = new ScrollSpy(div, { offset: 1, smoothScroll: true }) setTimeout(() => { if (div.scrollTo) { - expect(clickSpy).toHaveBeenCalledWith({ top: observable.offsetTop - div.offsetTop, behavior: 'smooth' }) + expect(clickSpy).toHaveBeenCalledWith({ top: observable.offsetTop - div.offsetTop - scrollSpy._scrollOffset, behavior: 'smooth' }) } else { - expect(clickSpy).toHaveBeenCalledWith(observable.offsetTop - div.offsetTop) + expect(clickSpy).toHaveBeenCalledWith(observable.offsetTop - div.offsetTop - scrollSpy._scrollOffset) } done()