diff --git a/example/src/components/App.js b/example/src/components/App.js
index 0737e59..f618cdc 100644
--- a/example/src/components/App.js
+++ b/example/src/components/App.js
@@ -3,6 +3,7 @@ import Example1 from './Example1'
import Example2 from './Example2'
import Example3 from './Example3'
import Example4 from './Example4'
+import Example5 from './Example5'
import ScrollableAnchor, { goToTop, goToAnchor, removeHash } from '../../../src'
const examples = [
@@ -10,6 +11,7 @@ const examples = [
{id: 'example2', label: 'Example 2', component: Example2},
{id: 'example3', label: 'Example 3', component: Example3},
{id: 'example4', label: 'Example 4', component: Example4},
+ {id: 'example5', label: 'Example 5', component: Example5},
]
const styles = {
diff --git a/example/src/components/Example5.js b/example/src/components/Example5.js
new file mode 100644
index 0000000..d131fcf
--- /dev/null
+++ b/example/src/components/Example5.js
@@ -0,0 +1,58 @@
+import React, { Component } from 'react'
+import ScrollableAnchor, { configureAnchors } from '../../../src'
+import Section from './Section'
+
+const sections = [
+ {id: 'section1', label: 'Section 1', backgroundColor: 'red'},
+ {id: 'section2', label: 'Section 2', backgroundColor: 'darkgray'},
+ {id: 'section3', label: 'Section 3', backgroundColor: 'green'},
+ {id: 'section4', label: 'Section 4', backgroundColor: 'brown'},
+ {id: 'section5', label: 'Section 5', backgroundColor: 'lightpink'}
+]
+
+const styles = {
+ offsetUp: {
+ marginTop: '-549px'
+ },
+ extraTall: {
+ height: '700px'
+ },
+ scrollingDiv: {
+ height: '50vh',
+ overflowY: 'scroll',
+ marginTop: '25vh',
+ width: '50%',
+ marginLeft: '25%',
+ position: 'relative'
+ }
+
+}
+
+export default class Example5 extends Component {
+
+ componentWillMount() {
+ configureAnchors({containerId: 'scrolling-div'})
+ }
+
+ renderSection = (section) => {
+ const props = {...section, sections, style: styles.extraTall}
+ return (
+
+
+
+
+
+ )
+ }
+
+ render() {
+ return (
+
+ { this.props.renderHeader(true, sections, true) }
+
+ { sections.map(this.renderSection) }
+
+
+ )
+ }
+}
diff --git a/package.json b/package.json
index 244e691..4ba2fc4 100644
--- a/package.json
+++ b/package.json
@@ -43,8 +43,8 @@
},
"homepage": "https://github.com/gabergg/react-scrollable-anchor",
"dependencies": {
- "jump.js": "1.0.2",
- "prop-types": "^15.5.10"
+ "prop-types": "^15.5.10",
+ "zenscroll": "^4.0.2"
},
"peerDependencies": {
"react": "^15.3.0 || ^16.0.0",
diff --git a/src/Manager.js b/src/Manager.js
index 6d74f72..17475e2 100644
--- a/src/Manager.js
+++ b/src/Manager.js
@@ -1,4 +1,4 @@
-import jump from 'jump.js'
+import zenscroll from 'zenscroll'
import { debounce } from './utils/func'
import { getBestAnchorGivenScrollLocation, getScrollTop } from './utils/scroll'
import { getHash, updateHash, removeHash } from './utils/hash'
@@ -6,9 +6,10 @@ import { getHash, updateHash, removeHash } from './utils/hash'
const defaultConfig = {
offset: 0,
scrollDuration: 400,
- keepLastAnchorHash: false,
+ keepLastAnchorHash: false
}
+
class Manager {
constructor() {
this.anchors = {}
@@ -19,30 +20,44 @@ class Manager {
this.forceHashUpdate = debounce(this.handleHashChange, 1)
}
+ setContainer = () => {
+ // if we have a containerId, find the scrolling container, else set it to window
+ if (this.config.containerId) {
+ this.config.container = document.getElementById(this.config.containerId)
+ this.config.scroller = zenscroll.createScroller(this.config.container, this.config.scrollDuration, this.config.offset)
+ } else {
+ this.config.container = window
+ this.config.scroller = zenscroll
+ }
+ }
+
addListeners = () => {
- window.addEventListener('scroll', this.scrollHandler, false)
+ this.config.container.addEventListener('scroll', this.scrollHandler, false)
window.addEventListener('hashchange', this.handleHashChange)
}
removeListeners = () => {
- window.removeEventListener('scroll', this.scrollHandler, false)
+ this.config.container.removeEventListener('scroll', this.scrollHandler, false)
window.removeEventListener('hashchange', this.handleHashChange)
}
configure = (config) => {
this.config = {
...defaultConfig,
- ...config,
+ ...config
}
}
goToTop = () => {
- if (getScrollTop() === 0) return
- this.forcedHash = true
- window.scroll(0,0)
+ if (getScrollTop(this.config.container) === 0) return
+ this.config.scroller.toY(0, this.config.scrollDuration)
}
addAnchor = (id, component) => {
+ // if container is not set, set container
+ if (!this.config.container) {
+ this.setContainer()
+ }
// if this is the first anchor, set up listeners
if (Object.keys(this.anchors).length === 0) {
this.addListeners()
@@ -61,7 +76,7 @@ class Manager {
handleScroll = () => {
const {offset, keepLastAnchorHash} = this.config
- const bestAnchorId = getBestAnchorGivenScrollLocation(this.anchors, offset)
+ const bestAnchorId = getBestAnchorGivenScrollLocation(this.anchors, offset, this.config.container)
if (bestAnchorId && getHash() !== bestAnchorId) {
this.forcedHash = true
@@ -81,20 +96,16 @@ class Manager {
goToSection = (id) => {
let element = this.anchors[id]
+ let viewHeight = this.config.container.innerHeight || this.config.container.clientHeight
+ let offset = this.config.offset + viewHeight/2
if (element) {
- jump(element, {
- duration: this.config.scrollDuration,
- offset: this.config.offset,
- })
+ this.config.scroller.center(element, this.config.scrollDuration, offset)
} else {
// make sure that standard hash anchors don't break.
// simply jump to them.
element = document.getElementById(id)
if (element) {
- jump(element, {
- duration: 0,
- offset: this.config.offset,
- })
+ this.config.scroller.center(element, 0, this.config.offset)
}
}
}
diff --git a/src/utils/scroll.js b/src/utils/scroll.js
index e97b4e3..b5dcfa2 100644
--- a/src/utils/scroll.js
+++ b/src/utils/scroll.js
@@ -1,10 +1,10 @@
-export const getScrollTop = () => {
- return document.body.scrollTop || document.documentElement.scrollTop
+export const getScrollTop = (container) => {
+ return container.scrollTop || document.body.scrollTop || document.documentElement.scrollTop
}
// get vertical offsets of element, taking scrollTop into consideration
-export const getElementOffset = (element) => {
- const scrollTop = getScrollTop()
+export const getElementOffset = (element, container) => {
+ const scrollTop = getScrollTop(container)
const {top, bottom} = element.getBoundingClientRect()
return {
top: Math.floor(top + scrollTop),
@@ -13,17 +13,19 @@ export const getElementOffset = (element) => {
}
// does scrollTop live within element bounds?
-export const doesElementContainScrollTop = (element, extraOffset = 0) => {
- const scrollTop = getScrollTop()
- const offsetTop = getElementOffset(element).top + extraOffset
+export const doesElementContainScrollTop = (element, container, extraOffset = 0) => {
+ let scrollTop = getScrollTop(container)
+ const offsetTop = getElementOffset(element, container).top + extraOffset
+ // if scrolling within a container we need to add the position of the container to scrollTop
+ scrollTop += container.getBoundingClientRect ? container.getBoundingClientRect().top : 0
return scrollTop >= offsetTop && scrollTop < offsetTop + element.offsetHeight
}
// is el2's location more relevant than el2,
// parent-child relationship aside?
-export const checkLocationRelevance = (el1, el2) => {
- const {top: top1, bottom: bottom1} = getElementOffset(el1)
- const {top: top2, bottom: bottom2} = getElementOffset(el2)
+export const checkLocationRelevance = (el1, el2, container) => {
+ const {top: top1, bottom: bottom1} = getElementOffset(el1, container)
+ const {top: top2, bottom: bottom2} = getElementOffset(el2, container)
if (top1 === top2) {
if (bottom1 === bottom2) {
// top and bottom of compared elements are the same,
@@ -41,11 +43,11 @@ export const checkLocationRelevance = (el1, el2) => {
// check if el2 is more relevant than el1, considering child-parent
// relationships as well as node location.
-export const checkElementRelevance = (el1, el2) => {
+export const checkElementRelevance = (el1, el2, container) => {
if (el1.contains(el2)) {
// el2 is child, so it gains relevance priority
return true
- } else if (!el2.contains(el1) && checkLocationRelevance(el1, el2)) {
+ } else if (!el2.contains(el1) && checkLocationRelevance(el1, el2, container)) {
// el1 and el2 are unrelated, but el2 has a better location,
// so it gains relevance priority
return true
@@ -62,13 +64,13 @@ export const checkElementRelevance = (el1, el2) => {
// 4. if neither node contains the other, and their top and bottom locations
// are the same, a node is chosen at random, in a deterministic way,
// to be more relevant.
-export const getBestAnchorGivenScrollLocation = (anchors, offset) => {
+export const getBestAnchorGivenScrollLocation = (anchors, offset, container) => {
let bestId, bestElement
Object.keys(anchors).forEach((id) => {
const element = anchors[id]
- if (doesElementContainScrollTop(element, offset)) {
- if (!bestElement || checkElementRelevance(bestElement, element)) {
+ if (doesElementContainScrollTop(element, container, offset)) {
+ if (!bestElement || checkElementRelevance(bestElement, element, container)) {
bestElement = element
bestId = id
}