@@ -42,19 +47,19 @@ To support headers and intro images/text, we use a container element containing
If these elements are passed to graphScroll as selections with `container` and `graph`, every element in the graph selection will be classed `graph-scroll-graph` if the top of the container is out of view.
-```
+```js
graphScroll()
.graph(d3.selectAll('#graph'))
.container(d3.select('#container'))
.sections(d3.selectAll('#sections > div'))
- .on('active', function(i){ console.log(i + 'th section active') })
+ .on('active', function(i, direction){ console.log(i + 'th section active', 'Moving ' + direction) })
```
With a little bit of css, the graph element snaps to the top of the page while the text scrolls by.
-```
+```css
#container{
position: relative;
overflow: auto;
@@ -81,7 +86,7 @@ With a little bit of css, the graph element snaps to the top of the page while t
As the bottom of the container approaches the top of the page, the graph is classed with `graph-scroll-below`. A little more css allows the graph slide out of view gracefully:
-```
+```css
#graph.graph-scroll-below{
position: absolute;
bottom: 0px;
diff --git a/graph-scroll.js b/graph-scroll.js
index 4e07b8f..f390197 100644
--- a/graph-scroll.js
+++ b/graph-scroll.js
@@ -1,158 +1,218 @@
-function graphScroll() {
- var windowHeight,
- dispatch = d3.dispatch("scroll", "active"),
- sections = d3.select('null'),
- i = -1,
- sectionPos = [],
- n,
- graph = d3.select('null'),
- isFixed = null,
- isBelow = null,
- container = d3.select('body'),
- containerStart = 0,
- belowStart,
- eventId = Math.random(),
- stickyTop
-
- function reposition(){
- var i1 = 0
- sectionPos.forEach(function(d, i){
- if (d < pageYOffset - containerStart + 180) i1 = i
- })
- i1 = Math.min(n - 1, i1)
- if (i != i1){
- sections.classed('graph-scroll-active', function(d, i){ return i === i1 })
-
- dispatch.active(i1, i)
-
- i = i1
+(function(){
+ var d3 = this.d3 || undefined
+ if (typeof exports !== 'undefined') {
+ if (typeof module !== 'undefined' && module.exports) {
+ d3 = require('d3')
+ exports = module.exports = graphScroll;
}
+ exports.graphScroll = graphScroll;
+ } else {
+ this.graphScroll = graphScroll;
+ }
- var isBelow1 = pageYOffset > belowStart - 120
- if (isBelow != isBelow1){
- isBelow = isBelow1
- graph.classed('graph-scroll-below', isBelow)
+ function graphScroll() {
+ var windowHeight,
+ dispatch = d3.dispatch("scroll", "active"),
+ sections = d3.select('null'),
+ i = -1,
+ sectionPos = [],
+ n,
+ graph = d3.select('null'),
+ isFixed = null,
+ isBelow = null,
+ container = d3.select('body'),
+ containerStart = 0,
+ belowStart,
+ eventId = Math.random(),
+ stickyTop,
+ lastPageY = -Infinity,
+ triggerAt = 'top',
+ offset = 0;
+
+ function reposition(){
+ var i1 = 0
+ var viewportHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0)
+
+ // If our lastPageY variable is our default but we're farther than the scroll top, then we are entering the page from somewhere not the top
+ if (lastPageY == -Infinity && pageYOffset > 0) {
+ direction = 'jump'
+ } else if (pageYOffset > lastPageY) {
+ direction = 'down'
+ } else {
+ direction = 'up'
+ }
+
+ sectionPos.forEach(function(d, i){
+ // Trigger active section when it gets to the middle of the viewport
+ if (triggerAt == 'middle' && d < (pageYOffset - containerStart + viewportHeight / 2 + offset) ) {
+ i1 = i
+ // Or at the top of the viewport
+ } else if (triggerAt == 'top' && d < pageYOffset - containerStart + offset) {
+ i1 = i
+ }
+ })
+
+ i1 = Math.min(n - 1, i1)
+ if (i != i1){
+ sections.classed('graph-scroll-active', function(d, i){ return i === i1 })
+
+ dispatch.active.call(sections[0][i1], i1, direction)
+
+ i = i1
+ }
+ var yStickyOffset = stickyTop || 0
+
+ var isBelow1 = pageYOffset + yStickyOffset > belowStart
+ if (isBelow != isBelow1){
+ isBelow = isBelow1
+ graph.classed('graph-scroll-below', isBelow)
+ }
+
+ var isFixed1 = !isBelow && pageYOffset > containerStart - yStickyOffset
+ if (isFixed != isFixed1){
+ isFixed = isFixed1
+ graph
+ .classed('graph-scroll-fixed', isFixed)
+ }
+
+ var top
+ if (stickyTop){
+ if (isBelow) {
+ top = 'auto'
+ } else if (isFixed) {
+ top = stickyTop + 'px'
+ } else {
+ top = '0px'
+ }
+ graph.style('top', top)
+ }
+
+ lastPageY = pageYOffset
}
- var isFixed1 = !isBelow && pageYOffset > containerStart - 120
- if (isFixed != isFixed1){
- isFixed = isFixed1
- graph
- .classed('graph-scroll-fixed', isFixed)
+ function resize(){
+ sectionPos = []
+ var startPos
+ sections.each(function(d, i){
+ if (!i) startPos = this.getBoundingClientRect().top
+ sectionPos.push(this.getBoundingClientRect().top - startPos) })
+
+ var containerBB = container.node().getBoundingClientRect()
+ var graphBB = graph.node().getBoundingClientRect()
+
+ containerStart = containerBB.top + pageYOffset
+ belowStart = containerBB.bottom - graphBB.height + pageYOffset
}
- if (stickyTop){
- graph.style('padding-top', (isBelow || isFixed ? stickyTop : 0)+ 'px')
+ function keydown() {
+ if (!isFixed) return
+ var delta
+ switch (d3.event.keyCode) {
+ case 39: // right arrow
+ if (d3.event.metaKey) return
+ case 40: // down arrow
+ case 34: // page down
+ delta = d3.event.metaKey ? Infinity : 1 ;break
+ case 37: // left arrow
+ if (d3.event.metaKey) return
+ case 38: // up arrow
+ case 33: // page up
+ delta = d3.event.metaKey ? -Infinity : -1 ;break
+ case 32: // space
+ delta = d3.event.shiftKey ? -1 : 1
+ ;break
+ default: return
+ }
+
+ var i1 = Math.max(0, Math.min(i + delta, n - 1))
+ rv.scrollTo(i1)
+
+ d3.event.preventDefault()
}
- }
- function resize(){
- sectionPos = []
- var startPos
- sections.each(function(d, i){
- if (!i) startPos = this.getBoundingClientRect().top
- sectionPos.push(this.getBoundingClientRect().top - startPos) })
- var containerBB = container.node().getBoundingClientRect()
- var graphBB = graph.node().getBoundingClientRect()
+ var rv ={}
- containerStart = containerBB.top + pageYOffset
- belowStart = containerBB.bottom - graphBB.height + pageYOffset
- }
+ rv.scrollTo = function(_x){
+ if (isNaN(_x)) return rv
- function keydown() {
- if (!isFixed) return
- var delta
- switch (d3.event.keyCode) {
- case 39: // right arrow
- if (d3.event.metaKey) return
- case 40: // down arrow
- case 34: // page down
- delta = d3.event.metaKey ? Infinity : 1 ;break
- case 37: // left arrow
- if (d3.event.metaKey) return
- case 38: // up arrow
- case 33: // page up
- delta = d3.event.metaKey ? -Infinity : -1 ;break
- case 32: // space
- delta = d3.event.shiftKey ? -1 : 1
- ;break
- default: return
+ d3.select(document.documentElement)
+ .interrupt()
+ .transition()
+ .duration(500)
+ .tween("scroll", function() {
+ var i = d3.interpolateNumber(pageYOffset, sectionPos[_x] + containerStart)
+ return function(t) { scrollTo(0, i(t)) }
+ })
+ return rv
}
- var i1 = Math.max(0, Math.min(i + delta, n - 1))
- rv.scrollTo(i1)
- d3.event.preventDefault()
- }
+ rv.container = function(_x){
+ if (!_x) return container
+ container = _x
+ return rv
+ }
- var rv ={}
+ rv.graph = function(_x){
+ if (!_x) return graph
- rv.scrollTo = function(_x){
- if (isNaN(_x)) return rv
+ graph = _x
+ return rv
+ }
- d3.select(document.documentElement)
- .interrupt()
- .transition()
- .duration(500)
- .tween("scroll", function() {
- var i = d3.interpolateNumber(pageYOffset, sectionPos[_x] + containerStart)
- return function(t) { scrollTo(0, i(t)) }
- })
- return rv
- }
+ rv.triggerAt = function(_x){
+ if (!_x) return triggerAt
+ triggerAt = _x
+ return rv
+ }
- rv.container = function(_x){
- if (!_x) return container
+ rv.eventId = function(_x){
+ if (!_x) return eventId
- container = _x
- return rv
- }
+ eventId = _x
+ return rv
+ }
- rv.graph = function(_x){
- if (!_x) return graph
+ rv.stickyTop = function(_x){
+ if (!_x) return stickyTop
- graph = _x
- return rv
- }
+ stickyTop = _x
+ return rv
+ }
- rv.eventId = function(_x){
- if (!_x) return eventId
+ rv.offset = function(_x){
+ if (!_x) return offset
- eventId = _x
- return rv
- }
+ offset = _x
+ return rv
+ }
- rv.stickyTop = function(_x){
- if (!_x) return stickyTop
+ rv.sections = function (_x){
+ if (!_x) return sections
- stickyTop = _x
- return rv
- }
+ sections = _x
+ n = sections.size()
- rv.sections = function (_x){
- if (!_x) return sections
+ d3.select(window)
+ .on('scroll.gscroll' + eventId, reposition)
+ .on('resize.gscroll' + eventId, resize)
+ .on('keydown.gscroll' + eventId, keydown)
- sections = _x
- n = sections.size()
+ resize()
+ d3.timer(function() {
+ reposition()
+ return true
+ })
- d3.select(window)
- .on('scroll.gscroll' + eventId, reposition)
- .on('resize.gscroll' + eventId, resize)
- .on('keydown.gscroll' + eventId, keydown)
+ return rv
+ }
- resize()
- d3.timer(function() {
- reposition()
- return true
- })
+ d3.rebind(rv, dispatch, "on")
return rv
}
-
- d3.rebind(rv, dispatch, "on")
-
- return rv
-}
\ No newline at end of file
+
+}).call(this)
\ No newline at end of file
diff --git a/index.html b/index.html
index 1ddfb4d..e33354e 100644
--- a/index.html
+++ b/index.html
@@ -82,19 +82,21 @@
Simple scrolling events for d3 graphics.
Connect text and graphics
graph-scroll
- takes a selection of explanatory text sections and dispatches
active events as different sections are scrolled into to view. These active events are used to update a graph's state.
+ takes a selection of explanatory text sections and dispatches
active events as different sections are scrolled into to view. These active events are used to update a graph's state.
A
direction is also passed on active. It is either
up,
down or
jump (fired when you load a page from not at the time, like when you refresh).
Set an
offset to determine how far from the top the next section will be before it becomes active.
graphScroll()
.sections(d3.selectAll('#sections > div'))
- .on('active', function(i){
- console.log(i + 'th section active') })
+ .offset(180)
+ .on('active', function(i, direction){
+ console.log(i + 'th section active')
+ console.log('Moving ' + direction)