forked from vidal-anguiano/chicago-traffic-violations-d3-viz
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathscroller.js
134 lines (120 loc) · 3.35 KB
/
scroller.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/**
* scroller - handles the details
* of figuring out which section
* the user is currently scrolled
* to.
*
*/
function scroller() {
var container = d3.select('body');
// event dispatcher
var dispatch = d3.dispatch('active', 'progress');
// d3 selection of all the
// text sections that will
// be scrolled through
var sections = null;
// array that will hold the
// y coordinate of each section
// that is scrolled through
var sectionPositions = [];
var currentIndex = -1;
// y coordinate of
var containerStart = 0;
/**
* scroll - constructor function.
* Sets up scroller to monitor
* scrolling of els selection.
*
* @param els - d3 selection of
* elements that will be scrolled
* through by user.
*/
function scroll(els) {
sections = els;
// when window is scrolled call
// position. When it is resized
// call resize.
d3.select(window)
.on('scroll.scroller', position)
.on('resize.scroller', resize);
// manually call resize
// initially to setup
// scroller.
resize();
// hack to get position
// to be called once for
// the scroll position on
// load.
// @v4 timer no longer stops if you
// return true at the end of the callback
// function - so here we stop it explicitly.
var timer = d3.timer(function () {
position();
timer.stop();
});
}
/**
* resize - called initially and
* also when page is resized.
* Resets the sectionPositions
*
*/
function resize() {
// sectionPositions will be each sections
// starting position relative to the top
// of the first section.
sectionPositions = [];
var startPos;
sections.each(function (d, i) {
var top = this.getBoundingClientRect().top;
if (i === 0) {
startPos = top;
}
sectionPositions.push(top - startPos);
});
containerStart = container.node().getBoundingClientRect().top + window.pageYOffset;
}
/**
* position - get current users position.
* if user has scrolled to new section,
* dispatch active event with new section
* index.
*
*/
function position() {
var pos = window.pageYOffset - 10 - containerStart;
var sectionIndex = d3.bisect(sectionPositions, pos);
sectionIndex = Math.min(sections.size() - 1, sectionIndex);
if (currentIndex !== sectionIndex) {
// @v4 you now `.call` the dispatch callback
dispatch.call('active', this, sectionIndex);
currentIndex = sectionIndex;
}
var prevIndex = Math.max(sectionIndex - 1, 0);
var prevTop = sectionPositions[prevIndex];
var progress = (pos - prevTop) / (sectionPositions[sectionIndex] - prevTop);
// @v4 you now `.call` the dispatch callback
dispatch.call('progress', this, currentIndex, progress);
}
/**
* container - get/set the parent element
* of the sections. Useful for if the
* scrolling doesn't start at the very top
* of the page.
*
* @param value - the new container value
*/
scroll.container = function (value) {
if (arguments.length === 0) {
return container;
}
container = value;
return scroll;
};
// @v4 There is now no d3.rebind, so this implements
// a .on method to pass in a callback to the dispatcher.
scroll.on = function (action, callback) {
dispatch.on(action, callback);
};
return scroll;
}