├── .gitignore
├── README.md
├── assets
├── demo.mp4
├── guardian_480.mp4
├── nyt1_480.mp4
├── nyt2_480.mp4
├── polygraph_480.mp4
├── scrollytelling.gif
└── scrollytelling.jpg
├── css
├── prism.css
└── style.css
├── demo
├── d3.v4.min.js
├── graphic.css
├── graphic.js
├── graphscroll
│ ├── d3.v4.min.js
│ ├── graph-scroll.js
│ └── index.html
├── inview
│ ├── in-view.min.js
│ └── index.html
├── rollyourown
│ └── index.html
├── scrollmagic
│ ├── ScrollMagic.min.js
│ └── index.html
├── scrollstory
│ ├── index.html
│ ├── jquery-3.1.1.min.js
│ └── jquery.scrollstory.min.js
└── waypoints
│ ├── index.html
│ └── noframework.waypoints.min.js
├── index.html
└── js
└── prism.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | Makefile
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # How to implement scrollytelling with six different libraries
2 |
3 | Check out the full article [here](https://pudding.cool/process/how-to-implement-scrollytelling).
4 |
5 | All the code is available in the [demo](demo) folder.
6 |
--------------------------------------------------------------------------------
/assets/demo.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-pudding/how-to-implement-scrollytelling/9e059c3c4f843cc9f2f1b3a0443d81f9273dda9d/assets/demo.mp4
--------------------------------------------------------------------------------
/assets/guardian_480.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-pudding/how-to-implement-scrollytelling/9e059c3c4f843cc9f2f1b3a0443d81f9273dda9d/assets/guardian_480.mp4
--------------------------------------------------------------------------------
/assets/nyt1_480.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-pudding/how-to-implement-scrollytelling/9e059c3c4f843cc9f2f1b3a0443d81f9273dda9d/assets/nyt1_480.mp4
--------------------------------------------------------------------------------
/assets/nyt2_480.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-pudding/how-to-implement-scrollytelling/9e059c3c4f843cc9f2f1b3a0443d81f9273dda9d/assets/nyt2_480.mp4
--------------------------------------------------------------------------------
/assets/polygraph_480.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-pudding/how-to-implement-scrollytelling/9e059c3c4f843cc9f2f1b3a0443d81f9273dda9d/assets/polygraph_480.mp4
--------------------------------------------------------------------------------
/assets/scrollytelling.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-pudding/how-to-implement-scrollytelling/9e059c3c4f843cc9f2f1b3a0443d81f9273dda9d/assets/scrollytelling.gif
--------------------------------------------------------------------------------
/assets/scrollytelling.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/the-pudding/how-to-implement-scrollytelling/9e059c3c4f843cc9f2f1b3a0443d81f9273dda9d/assets/scrollytelling.jpg
--------------------------------------------------------------------------------
/css/prism.css:
--------------------------------------------------------------------------------
1 | /* http://prismjs.com/download.html?themes=prism-okaidia&languages=clike+javascript */
2 | /**
3 | * okaidia theme for JavaScript, CSS and HTML
4 | * Loosely based on Monokai textmate theme by http://www.monokai.nl/
5 | * @author ocodia
6 | */
7 |
8 | code[class*="language-"],
9 | pre[class*="language-"] {
10 | color: #f8f8f2;
11 | background: none;
12 | text-shadow: 0 1px rgba(0, 0, 0, 0.3);
13 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
14 | text-align: left;
15 | white-space: pre;
16 | word-spacing: normal;
17 | word-break: normal;
18 | word-wrap: normal;
19 | line-height: 1.5;
20 |
21 | -moz-tab-size: 4;
22 | -o-tab-size: 4;
23 | tab-size: 4;
24 |
25 | -webkit-hyphens: none;
26 | -moz-hyphens: none;
27 | -ms-hyphens: none;
28 | hyphens: none;
29 | }
30 |
31 | /* Code blocks */
32 | pre[class*="language-"] {
33 | padding: 1em;
34 | margin: .5em 0;
35 | overflow: auto;
36 | border-radius: 0.3em;
37 | }
38 |
39 | :not(pre) > code[class*="language-"],
40 | pre[class*="language-"] {
41 | background: #272822;
42 | }
43 |
44 | /* Inline code */
45 | :not(pre) > code[class*="language-"] {
46 | padding: .1em;
47 | border-radius: .3em;
48 | white-space: normal;
49 | }
50 |
51 | .token.comment,
52 | .token.prolog,
53 | .token.doctype,
54 | .token.cdata {
55 | color: slategray;
56 | }
57 |
58 | .token.punctuation {
59 | color: #f8f8f2;
60 | }
61 |
62 | .namespace {
63 | opacity: .7;
64 | }
65 |
66 | .token.property,
67 | .token.tag,
68 | .token.constant,
69 | .token.symbol,
70 | .token.deleted {
71 | color: #f92672;
72 | }
73 |
74 | .token.boolean,
75 | .token.number {
76 | color: #ae81ff;
77 | }
78 |
79 | .token.selector,
80 | .token.attr-name,
81 | .token.string,
82 | .token.char,
83 | .token.builtin,
84 | .token.inserted {
85 | color: #a6e22e;
86 | }
87 |
88 | .token.operator,
89 | .token.entity,
90 | .token.url,
91 | .language-css .token.string,
92 | .style .token.string,
93 | .token.variable {
94 | color: #f8f8f2;
95 | }
96 |
97 | .token.atrule,
98 | .token.attr-value,
99 | .token.function {
100 | color: #e6db74;
101 | }
102 |
103 | .token.keyword {
104 | color: #66d9ef;
105 | }
106 |
107 | .token.regex,
108 | .token.important {
109 | color: #fd971f;
110 | }
111 |
112 | .token.important,
113 | .token.bold {
114 | font-weight: bold;
115 | }
116 | .token.italic {
117 | font-style: italic;
118 | }
119 |
120 | .token.entity {
121 | cursor: help;
122 | }
123 |
124 |
--------------------------------------------------------------------------------
/css/style.css:
--------------------------------------------------------------------------------
1 | /* reset stuff */
2 | * {
3 | box-sizing: border-box;
4 | }
5 | a,
6 | abbr,
7 | acronym,
8 | address,
9 | applet,
10 | article,
11 | aside,
12 | audio,
13 | b,
14 | big,
15 | blockquote,
16 | body,
17 | canvas,
18 | caption,
19 | center,
20 | cite,
21 | code,
22 | dd,
23 | del,
24 | details,
25 | dfn,
26 | div,
27 | dl,
28 | dt,
29 | em,
30 | embed,
31 | fieldset,
32 | figcaption,
33 | figure,
34 | footer,
35 | form,
36 | h1,
37 | h2,
38 | h3,
39 | h4,
40 | h5,
41 | h6,
42 | header,
43 | hgroup,
44 | html,
45 | i,
46 | iframe,
47 | img,
48 | ins,
49 | kbd,
50 | label,
51 | legend,
52 | li,
53 | mark,
54 | menu,
55 | nav,
56 | object,
57 | ol,
58 | output,
59 | p,
60 | pre,
61 | q,
62 | ruby,
63 | s,
64 | samp,
65 | section,
66 | small,
67 | span,
68 | strike,
69 | strong,
70 | sub,
71 | summary,
72 | sup,
73 | table,
74 | tbody,
75 | td,
76 | tfoot,
77 | th,
78 | thead,
79 | time,
80 | tr,
81 | tt,
82 | u,
83 | ul,
84 | var,
85 | video {
86 | margin: 0;
87 | padding: 0;
88 | border: 0;
89 | font-size: 100%;
90 | font: inherit;
91 | vertical-align: baseline;
92 | }
93 | article,
94 | aside,
95 | details,
96 | figcaption,
97 | figure,
98 | footer,
99 | header,
100 | hgroup,
101 | main,
102 | menu,
103 | nav,
104 | section {
105 | display: block;
106 | }
107 | body,
108 | html {
109 | height: 100%;
110 | }
111 | a img {
112 | border: none;
113 | }
114 | blockquote {
115 | quotes: none;
116 | }
117 | blockquote:after,
118 | blockquote:before {
119 | content: "";
120 | content: none;
121 | }
122 | table {
123 | border-collapse: collapse;
124 | border-spacing: 0;
125 | }
126 | caption,
127 | td,
128 | th {
129 | text-align: left;
130 | font-weight: 400;
131 | vertical-align: middle;
132 | }
133 | html {
134 | -webkit-text-size-adjust: 100%;
135 | -ms-text-size-adjust: 100%;
136 | text-size-adjust: 100%;
137 | }
138 | body {
139 | font-feature-settings: "kern" 1, "onum" 1, "liga" 0;
140 | }
141 | b,
142 | strong {
143 | font-weight: 700;
144 | }
145 | em,
146 | i {
147 | font-style: italic;
148 | }
149 | ul {
150 | list-style-type: none;
151 | }
152 | img,
153 | video {
154 | display: block;
155 | width: 100%;
156 | }
157 | button {
158 | cursor: pointer;
159 | border: none;
160 | outline: 0;
161 | margin: 0;
162 | padding: 0;
163 | font-size: 1rem;
164 | }
165 | sub,
166 | sup {
167 | font-size: 75%;
168 | line-height: 0;
169 | position: relative;
170 | vertical-align: baseline;
171 | }
172 | sup {
173 | top: -0.5em;
174 | }
175 | sub {
176 | bottom: -0.25em;
177 | }
178 | .hide-accessible {
179 | width: 1px;
180 | height: 1px;
181 | overflow: hidden;
182 | position: absolute;
183 | }
184 | .hide-invisible {
185 | visibility: hidden;
186 | }
187 | .hide-display {
188 | display: none;
189 | }
190 | .tk-whitney {
191 | font-family: Helvetica, Arial, sans-serif;
192 | letter-spacing: 0.04em;
193 | font-weight: 300;
194 | }
195 | .loaded-whitney .tk-whitney {
196 | font-family: "Whitney SSm A", "Whitney SSm B", Helvetica, Arial, sans-serif;
197 | letter-spacing: normal;
198 | }
199 | .tk-mercury {
200 | font-family: Georgia, Times, serif;
201 | letter-spacing: 0.04em;
202 | font-weight: 400;
203 | }
204 | .loaded-mercury .tk-mercury {
205 | font-family: "Mercury SSm A", "Mercury SSm B", Georgia, Times, serif;
206 | letter-spacing: normal;
207 | }
208 | body {
209 | background: #fff;
210 | font-size: 17px;
211 | }
212 | @media only screen and (min-width: 25em) {
213 | .hack-for-mq-cache {
214 | height: 0;
215 | }
216 | }
217 | @media only screen and (min-width: 40em) {
218 | .hack-for-mq-cache {
219 | height: 0;
220 | }
221 | }
222 | @media only screen and (min-width: 50em) {
223 | .hack-for-mq-cache {
224 | height: 0;
225 | }
226 | }
227 | @media only screen and (min-width: 60em) {
228 | .hack-for-mq-cache {
229 | height: 0;
230 | }
231 | }
232 |
233 | body {
234 | font-size: 17px;
235 | font-weight: 400;
236 | background-color: #fffffc;
237 | color: #1a1a1a;
238 | line-height: 1.8;
239 | font-family: "Whitney SSm A", "Whitney SSm B", Helvetica, sans-serif;
240 | -webkit-font-smoothing: antialiased;
241 | -moz-osx-font-smoothing: grayscale;
242 | }
243 |
244 | main {
245 | padding: 0.75rem;
246 | padding-bottom: 5rem;
247 | }
248 |
249 | p {
250 | margin: 1.5rem 0;
251 | }
252 |
253 | a {
254 | color: #1a1a1a;
255 | text-decoration: none;
256 | border-bottom: 1px dotted currentColor;
257 | }
258 |
259 | a:hover {
260 | color: #f33;
261 | }
262 |
263 | ol,
264 | ul {
265 | list-style-type: none;
266 | }
267 |
268 | header {
269 | z-index: 1000;
270 | margin: 1rem auto;
271 | max-width: 12rem;
272 | padding: 1rem;
273 | }
274 | header a.logo {
275 | display: block;
276 | border: none;
277 | }
278 | header a.logo svg {
279 | fill: #2a2a2a;
280 | display: block;
281 | width: 100%;
282 | }
283 |
284 | .intro {
285 | max-width: 60rem;
286 | margin: 4rem auto 2rem auto;
287 | }
288 |
289 | .intro p {
290 | max-width: 40rem;
291 | margin-left: auto;
292 | margin-right: auto;
293 | }
294 |
295 | .hed {
296 | font-family: "Mercury SSm A", "Mercury SSm B", Georgia, serif;
297 | font-size: 2rem;
298 | font-weight: bold;
299 | line-height: 1.4;
300 | margin-bottom: 3rem;
301 | margin-left: auto;
302 | margin-right: auto;
303 | max-width: 40rem;
304 | }
305 |
306 | .hed a {
307 | border: none;
308 | }
309 |
310 | .byline {
311 | color: #777;
312 | height: 100%;
313 | overflow: hidden;
314 | }
315 |
316 | .byline__author {
317 | float: left;
318 | /*display: block;*/
319 | }
320 |
321 | .byline__date {
322 | float: right;
323 | }
324 |
325 | .disclaimer {
326 | clear: both;
327 | font-size: 0.9rem;
328 | }
329 |
330 | .update {
331 | background-color: #eee;
332 | font-size: 0.9rem;
333 | padding: 1rem;
334 | }
335 |
336 | .video-example {
337 | display: -webkit-flex;
338 | display: -ms-flexbox;
339 | display: flex;
340 | -webkit-flex-wrap: wrap;
341 | -ms-flex-wrap: wrap;
342 | flex-wrap: wrap;
343 | -webkit-align-items: flex-start;
344 | -ms-flex-align: flex-start;
345 | align-items: flex-start;
346 | }
347 |
348 | .video-example {
349 | background-color: #1a1a1a;
350 | margin: 3rem auto;
351 | }
352 |
353 | .example-link {
354 | display: block;
355 | width: 49.25%;
356 | margin: 0.25%;
357 | border: none;
358 | }
359 |
360 | .example-link:nth-child(1) {
361 | margin-left: 0.5%;
362 | margin-top: 0.5%;
363 | }
364 |
365 | .example-link:nth-child(2) {
366 | margin-right: 0.5%;
367 | margin-top: 0.5%;
368 | }
369 |
370 | .example-link:nth-child(3) {
371 | margin-left: 0.5%;
372 | margin-bottom: 0.5%;
373 | }
374 |
375 | .example-link:nth-child(4) {
376 | margin-right: 0.5%;
377 | margin-bottom: 0.5%;
378 | }
379 |
380 | .video-demo {
381 | display: block;
382 | max-width: 40rem;
383 | margin: 0 auto;
384 | border: 1px solid #eee;
385 | }
386 |
387 | .outro {
388 | max-width: 40rem;
389 | margin: 0 auto;
390 | margin-bottom: 5rem;
391 | }
392 |
393 | .library {
394 | padding: 1rem 0;
395 | margin: 2rem auto;
396 | max-width: 50rem;
397 | }
398 |
399 | .library__text {
400 | margin: 0 auto;
401 | max-width: 40rem;
402 | }
403 |
404 | .library__hed {
405 | font-size: 1.25rem;
406 | text-transform: uppercase;
407 | font-weight: 700;
408 | margin-bottom: 1rem;
409 | }
410 |
411 | .library__review {
412 | }
413 |
414 | .library__demo {
415 | text-transform: uppercase;
416 | }
417 |
418 | .library__code {
419 | font-size: 0.85rem;
420 | height: 20vh;
421 | overflow: hidden;
422 | position: relative;
423 | }
424 |
425 | .library__code.is-expand {
426 | height: auto;
427 | }
428 |
429 | .library__code:after {
430 | content: "";
431 | display: block;
432 | width: 100%;
433 | position: absolute;
434 | left: 0;
435 | bottom: 0;
436 | height: 3rem;
437 | background-image: linear-gradient(
438 | to bottom,
439 | rgba(0, 0, 0, 0),
440 | rgba(39, 40, 34, 1) 80%
441 | );
442 | }
443 |
444 | .code__button {
445 | display: block;
446 | width: 100%;
447 | background: #1a1a1a;
448 | color: #efefef;
449 | border-top: 1px solid #666;
450 | padding: 0.5rem;
451 | text-align: center;
452 | text-transform: uppercase;
453 | font-size: 0.9rem;
454 | font-weight: 700;
455 | cursor: pointer;
456 | }
457 |
458 | .code__button:hover {
459 | background-color: #f33;
460 | }
461 |
462 | .back-to-blog {
463 | text-align: center;
464 | }
465 |
466 | .demo-links {
467 | height: 100%;
468 | overflow: hidden;
469 | max-width: 40rem;
470 | margin: 0 auto;
471 | text-align: center;
472 | }
473 |
474 | .demo-links li {
475 | float: left;
476 | margin-right: 1rem;
477 | }
478 |
--------------------------------------------------------------------------------
/demo/graphic.css:
--------------------------------------------------------------------------------
1 | /* GRAPHIC CODE */
2 | .graphic {
3 | width: 100%;
4 | position: relative;
5 | }
6 |
7 | .graphic__prose {
8 | width: 24rem;
9 | }
10 |
11 | .graphic__prose .trigger {
12 | padding: 0;
13 | margin: 0;
14 | min-height: 240px;
15 |
16 | }
17 |
18 | .graphic__vis {
19 | position: absolute;
20 | top: 0;
21 | margin-left: 30rem;
22 | -webkit-transform: translate3d(0, 0, 0);
23 | -moz-transform: translate3d(0, 0, 0);
24 | transform: translate3d(0, 0, 0);
25 | height: 100vh;
26 | }
27 |
28 | .graphic__vis.is-fixed {
29 | position: fixed;
30 | }
31 |
32 | .graphic__vis.is-bottom {
33 | top: auto;
34 | bottom: 0;
35 | }
36 |
37 | .graphic__vis svg {
38 | border: 1px dashed black;
39 | top: 50%;
40 | position: relative;
41 | -webkit-transform: translateY(-50%);
42 | -moz-transform: translateY(-50%);
43 | transform: translateY(-50%);
44 |
45 | }
46 |
47 | .item circle {
48 | stroke: #666;
49 | stroke-width: 1px;
50 | fill: #fff;
51 | }
52 |
53 | .item text {
54 | fill: #666;
55 | font-size: 12px;
56 | text-anchor: middle;
57 | alignment-baseline: middle;
58 | }
59 |
60 |
61 |
62 | /* graph-scroll.js version */
63 | .graphic__vis.graph-scroll-fixed {
64 | position: fixed;
65 | right: auto;
66 | }
67 |
68 | .graphic__vis.graph-scroll-below {
69 | top: auto;
70 | bottom: 0;
71 | }
--------------------------------------------------------------------------------
/demo/graphic.js:
--------------------------------------------------------------------------------
1 | /*
2 | I've created a function here that is a simple d3 chart.
3 | This could be anthing that has discrete steps, as simple as changing
4 | the background color, or playing/pausing a video.
5 | The important part is that it exposes and update function that
6 | calls a new thing on a scroll trigger.
7 | */
8 | window.createGraphic = function(graphicSelector) {
9 | var graphicEl = d3.select('.graphic')
10 | var graphicVisEl = graphicEl.select('.graphic__vis')
11 | var graphicProseEl = graphicEl.select('.graphic__prose')
12 |
13 | var margin = 20
14 | var size = 400
15 | var chartSize = size - margin * 2
16 | var scaleX = null
17 | var scaleR = null
18 | var data = [8, 6, 7, 5, 3, 0, 9]
19 | var extent = d3.extent(data)
20 | var minR = 10
21 | var maxR = 24
22 |
23 | // actions to take on each step of our scroll-driven story
24 | var steps = [
25 | function step0() {
26 | // circles are centered and small
27 | var t = d3.transition()
28 | .duration(800)
29 | .ease(d3.easeQuadInOut)
30 |
31 |
32 | var item = graphicVisEl.selectAll('.item')
33 |
34 | item.transition(t)
35 | .attr('transform', translate(chartSize / 2, chartSize / 2))
36 |
37 | item.select('circle')
38 | .transition(t)
39 | .attr('r', minR)
40 |
41 | item.select('text')
42 | .transition(t)
43 | .style('opacity', 0)
44 | },
45 |
46 | function step1() {
47 | var t = d3.transition()
48 | .duration(800)
49 | .ease(d3.easeQuadInOut)
50 |
51 | // circles are positioned
52 | var item = graphicVisEl.selectAll('.item')
53 |
54 | item.transition(t)
55 | .attr('transform', function(d, i) {
56 | return translate(scaleX(i), chartSize / 2)
57 | })
58 |
59 | item.select('circle')
60 | .transition(t)
61 | .attr('r', minR)
62 |
63 | item.select('text')
64 | .transition(t)
65 | .style('opacity', 0)
66 | },
67 |
68 | function step2() {
69 | var t = d3.transition()
70 | .duration(800)
71 | .ease(d3.easeQuadInOut)
72 |
73 | // circles are sized
74 | var item = graphicVisEl.selectAll('.item')
75 |
76 | item.select('circle')
77 | .transition(t)
78 | .delay(function(d, i) { return i * 200 })
79 | .attr('r', function(d, i) {
80 | return scaleR(d)
81 | })
82 |
83 | item.select('text')
84 | .transition(t)
85 | .delay(function(d, i) { return i * 200 })
86 | .style('opacity', 1)
87 | },
88 | ]
89 |
90 | // update our chart
91 | function update(step) {
92 | steps[step].call()
93 | }
94 |
95 | // little helper for string concat if using es5
96 | function translate(x, y) {
97 | return 'translate(' + x + ',' + y + ')'
98 | }
99 |
100 | function setupCharts() {
101 | var svg = graphicVisEl.append('svg')
102 | .attr('width', size + 'px')
103 | .attr('height', size + 'px')
104 |
105 | var chart = svg.append('g')
106 | .classed('chart', true)
107 | .attr('transform', 'translate(' + margin + ',' + margin + ')')
108 |
109 | scaleR = d3.scaleLinear()
110 | scaleX = d3.scaleBand()
111 |
112 | var domainX = d3.range(data.length)
113 |
114 | scaleX
115 | .domain(domainX)
116 | .range([0, chartSize])
117 | .padding(1)
118 |
119 | scaleR
120 | .domain(extent)
121 | .range([minR, maxR])
122 |
123 | var item = chart.selectAll('.item')
124 | .data(data)
125 | .enter().append('g')
126 | .classed('item', true)
127 | .attr('transform', translate(chartSize / 2, chartSize / 2))
128 |
129 | item.append('circle')
130 | .attr('cx', 0)
131 | .attr('cy', 0)
132 |
133 | item.append('text')
134 | .text(function(d) { return d })
135 | .attr('y', 1)
136 | .style('opacity', 0)
137 | }
138 |
139 | function setupProse() {
140 | var height = window.innerHeight * 0.5
141 | graphicProseEl.selectAll('.trigger')
142 | .style('height', height + 'px')
143 | }
144 |
145 | function init() {
146 | setupCharts()
147 | setupProse()
148 | update(0)
149 | }
150 |
151 | init()
152 |
153 | return {
154 | update: update,
155 | }
156 | }
--------------------------------------------------------------------------------
/demo/graphscroll/graph-scroll.js:
--------------------------------------------------------------------------------
1 | (function (global, factory) {
2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3')) :
3 | typeof define === 'function' && define.amd ? define(['exports', 'd3'], factory) :
4 | (factory((global.d3 = global.d3 || {}),global.d3));
5 | }(this, function (exports,d3) { 'use strict';
6 |
7 | function graphScroll() {
8 | var windowHeight,
9 | dispatch = d3.dispatch("scroll", "active"),
10 | sections = d3.select('null'),
11 | i = NaN,
12 | sectionPos = [],
13 | n,
14 | graph = d3.select('null'),
15 | isFixed = null,
16 | isBelow = null,
17 | container = d3.select('body'),
18 | containerStart = 0,
19 | belowStart,
20 | eventId = Math.random(),
21 | offset = 0
22 |
23 | function reposition(){
24 | var i1 = 0
25 | sectionPos.forEach(function(d, i){
26 | if (d < pageYOffset - containerStart + offset) i1 = i
27 | })
28 | i1 = Math.min(n - 1, i1)
29 | if (i != i1){
30 | sections.classed('graph-scroll-active', function(d, i){ return i === i1 })
31 |
32 | dispatch.call('active', null, i1)
33 |
34 | i = i1
35 | }
36 |
37 | var isBelow1 = pageYOffset > belowStart
38 | if (isBelow != isBelow1){
39 | isBelow = isBelow1
40 | graph.classed('graph-scroll-below', isBelow)
41 | }
42 | var isFixed1 = !isBelow && pageYOffset > containerStart
43 | if (isFixed != isFixed1){
44 | isFixed = isFixed1
45 | graph.classed('graph-scroll-fixed', isFixed)
46 | }
47 | }
48 |
49 | function resize(){
50 | sectionPos = []
51 | var startPos
52 | sections.each(function(d, i){
53 | if (!i) startPos = this.getBoundingClientRect().top
54 | sectionPos.push(this.getBoundingClientRect().top - startPos) })
55 |
56 | var containerBB = container.node().getBoundingClientRect()
57 | var graphBB = graph.node().getBoundingClientRect()
58 |
59 | containerStart = containerBB.top + pageYOffset
60 | belowStart = containerBB.bottom - graphBB.height + pageYOffset
61 | }
62 |
63 | function keydown() {
64 | if (!isFixed) return
65 | var delta
66 | switch (d3.event.keyCode) {
67 | case 39: // right arrow
68 | if (d3.event.metaKey) return
69 | case 40: // down arrow
70 | case 34: // page down
71 | delta = d3.event.metaKey ? Infinity : 1 ;break
72 | case 37: // left arrow
73 | if (d3.event.metaKey) return
74 | case 38: // up arrow
75 | case 33: // page up
76 | delta = d3.event.metaKey ? -Infinity : -1 ;break
77 | case 32: // space
78 | delta = d3.event.shiftKey ? -1 : 1
79 | ;break
80 | default: return
81 | }
82 |
83 | var i1 = Math.max(0, Math.min(i + delta, n - 1))
84 | d3.select(document.documentElement)
85 | .interrupt()
86 | .transition()
87 | .duration(500)
88 | .tween("scroll", function() {
89 | var i = d3.interpolateNumber(pageYOffset, sectionPos[i1] + containerStart)
90 | return function(t) { scrollTo(0, i(t)) }
91 | })
92 |
93 | d3.event.preventDefault()
94 | }
95 |
96 |
97 | var rv ={}
98 |
99 | rv.container = function(_x){
100 | if (!_x) return container
101 |
102 | container = _x
103 | return rv
104 | }
105 |
106 | rv.graph = function(_x){
107 | if (!_x) return graph
108 |
109 | graph = _x
110 | return rv
111 | }
112 |
113 | rv.eventId = function(_x){
114 | if (!_x) return eventId
115 |
116 | eventId = _x
117 | return rv
118 | }
119 |
120 | rv.sections = function (_x){
121 | if (!_x) return sections
122 |
123 | sections = _x
124 | n = sections.size()
125 |
126 | d3.select(window)
127 | .on('scroll.gscroll' + eventId, reposition)
128 | .on('resize.gscroll' + eventId, resize)
129 | .on('keydown.gscroll' + eventId, keydown)
130 |
131 | resize()
132 | d3.timer(function() {
133 | reposition()
134 | return true
135 | })
136 |
137 | return rv
138 | }
139 |
140 | rv.on = function() {
141 | var value = dispatch.on.apply(dispatch, arguments);
142 | return value === dispatch ? rv : value;
143 | }
144 |
145 | rv.offset = function(_x) {
146 | if (!_x) return rv
147 |
148 | offset = _x
149 | return rv
150 | }
151 |
152 | return rv
153 | }
154 |
155 | exports.graphScroll = graphScroll;
156 |
157 | Object.defineProperty(exports, '__esModule', { value: true });
158 |
159 | }));
--------------------------------------------------------------------------------
/demo/graphscroll/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Scrollytelling demo: graph-scroll.js
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 |
47 |
48 |
49 |
58 |
59 |
63 |
64 |
73 |
74 |
75 |
76 |
77 |
78 |
Step 1 in the graphic. It triggers in the middle of the viewport. For this graphic, it is the same as the initial state so the reader doesn’t miss anything.
79 |
Step 2 arrives. The graphic should be locking into a fixed position right about now. We could have a whole bunch of these “fixed” steps.
80 |
Step 3 concludes our brief tour. The graphic should now go back to its original in-flow position, elegantly snapping back into place.
81 |
82 |
83 |
84 |
← Back to the blog
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
133 |
134 |
135 |
136 |
137 |
--------------------------------------------------------------------------------
/demo/inview/in-view.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * in-view 0.6.1 - Get notified when a DOM element enters or exits the viewport.
3 | * Copyright (c) 2016 Cam Wiegert - https://camwiegert.github.io/in-view
4 | * License: MIT
5 | */
6 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.inView=e():t.inView=e()}(this,function(){return function(t){function e(r){if(n[r])return n[r].exports;var i=n[r]={exports:{},id:r,loaded:!1};return t[r].call(i.exports,i,i.exports,e),i.loaded=!0,i.exports}var n={};return e.m=t,e.c=n,e.p="",e(0)}([function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{default:t}}var i=n(2),o=r(i);t.exports=o.default},function(t,e){function n(t){var e=typeof t;return null!=t&&("object"==e||"function"==e)}t.exports=n},function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(e,"__esModule",{value:!0});var i=n(9),o=r(i),u=n(3),f=r(u),s=n(4),c=function(){if("undefined"!=typeof window){var t=100,e=["scroll","resize","load"],n={history:[]},r={offset:{},threshold:0,test:s.inViewport},i=(0,o.default)(function(){n.history.forEach(function(t){n[t].check()})},t);e.forEach(function(t){return addEventListener(t,i)}),window.MutationObserver&&addEventListener("DOMContentLoaded",function(){new MutationObserver(i).observe(document.body,{attributes:!0,childList:!0,subtree:!0})});var u=function(t){if("string"==typeof t){var e=[].slice.call(document.querySelectorAll(t));return n.history.indexOf(t)>-1?n[t].elements=e:(n[t]=(0,f.default)(e,r),n.history.push(t)),n[t]}};return u.offset=function(t){if(void 0===t)return r.offset;var e=function(t){return"number"==typeof t};return["top","right","bottom","left"].forEach(e(t)?function(e){return r.offset[e]=t}:function(n){return e(t[n])?r.offset[n]=t[n]:null}),r.offset},u.threshold=function(t){return"number"==typeof t&&t>=0&&t<=1?r.threshold=t:r.threshold},u.test=function(t){return"function"==typeof t?r.test=t:r.test},u.is=function(t){return r.test(t,r)},u.offset(0),u}};e.default=c},function(t,e){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(e,"__esModule",{value:!0});var r=function(){function t(t,e){for(var n=0;n-1,o=n&&!i,u=!n&&i;o&&(t.current.push(e),t.emit("enter",e)),u&&(t.current.splice(r,1),t.emit("exit",e))}),this}},{key:"on",value:function(t,e){return this.handlers[t].push(e),this}},{key:"once",value:function(t,e){return this.singles[t].unshift(e),this}},{key:"emit",value:function(t,e){for(;this.singles[t].length;)this.singles[t].pop()(e);for(var n=this.handlers[t].length;--n>-1;)this.handlers[t][n](e);return this}}]),t}();e.default=function(t,e){return new i(t,e)}},function(t,e){"use strict";function n(t,e){var n=t.getBoundingClientRect(),r=n.top,i=n.right,o=n.bottom,u=n.left,f=n.width,s=n.height,c={t:o,r:window.innerWidth-u,b:window.innerHeight-r,l:i},a={x:e.threshold*f,y:e.threshold*s};return c.t>e.offset.top+a.y&&c.r>e.offset.right+a.x&&c.b>e.offset.bottom+a.y&&c.l>e.offset.left+a.x}Object.defineProperty(e,"__esModule",{value:!0}),e.inViewport=n},function(t,e){(function(e){var n="object"==typeof e&&e&&e.Object===Object&&e;t.exports=n}).call(e,function(){return this}())},function(t,e,n){var r=n(5),i="object"==typeof self&&self&&self.Object===Object&&self,o=r||i||Function("return this")();t.exports=o},function(t,e,n){function r(t,e,n){function r(e){var n=x,r=m;return x=m=void 0,E=e,w=t.apply(r,n)}function a(t){return E=t,j=setTimeout(h,e),M?r(t):w}function l(t){var n=t-O,r=t-E,i=e-n;return _?c(i,g-r):i}function d(t){var n=t-O,r=t-E;return void 0===O||n>=e||n<0||_&&r>=g}function h(){var t=o();return d(t)?p(t):void(j=setTimeout(h,l(t)))}function p(t){return j=void 0,T&&x?r(t):(x=m=void 0,w)}function v(){void 0!==j&&clearTimeout(j),E=0,x=O=m=j=void 0}function y(){return void 0===j?w:p(o())}function b(){var t=o(),n=d(t);if(x=arguments,m=this,O=t,n){if(void 0===j)return a(O);if(_)return j=setTimeout(h,e),r(O)}return void 0===j&&(j=setTimeout(h,e)),w}var x,m,g,w,j,O,E=0,M=!1,_=!1,T=!0;if("function"!=typeof t)throw new TypeError(f);return e=u(e)||0,i(n)&&(M=!!n.leading,_="maxWait"in n,g=_?s(u(n.maxWait)||0,e):g,T="trailing"in n?!!n.trailing:T),b.cancel=v,b.flush=y,b}var i=n(1),o=n(8),u=n(10),f="Expected a function",s=Math.max,c=Math.min;t.exports=r},function(t,e,n){var r=n(6),i=function(){return r.Date.now()};t.exports=i},function(t,e,n){function r(t,e,n){var r=!0,f=!0;if("function"!=typeof t)throw new TypeError(u);return o(n)&&(r="leading"in n?!!n.leading:r,f="trailing"in n?!!n.trailing:f),i(t,e,{leading:r,maxWait:e,trailing:f})}var i=n(7),o=n(1),u="Expected a function";t.exports=r},function(t,e){function n(t){return t}t.exports=n}])});
--------------------------------------------------------------------------------
/demo/inview/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Scrollytelling demo: in-view.js
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 |
46 |
47 |
48 |
57 |
58 |
62 |
63 |
72 |
73 |
74 |
75 |
76 |
77 |
Step 1 in the graphic. It triggers in the middle of the viewport. For this graphic, it is the same as the initial state so the reader doesn’t miss anything.
78 |
Step 2 arrives. The graphic should be locking into a fixed position right about now. We could have a whole bunch of these “fixed” steps.
79 |
Step 3 concludes our brief tour. The graphic should now go back to its original in-flow position, elegantly snapping back into place.
80 |
81 |
82 |
83 |
← Back to the blog
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
205 |
206 |
207 |
208 |
209 |
--------------------------------------------------------------------------------
/demo/rollyourown/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Scrollytelling demo: Roll your own
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 |
46 |
47 |
48 |
57 |
58 |
62 |
63 |
72 |
73 |
74 |
75 |
76 |
77 |
Step 1 in the graphic. It triggers in the middle of the viewport. For this graphic, it is the same as the initial state so the reader doesn’t miss anything.
78 |
Step 2 arrives. The graphic should be locking into a fixed position right about now. We could have a whole bunch of these “fixed” steps.
79 |
Step 3 concludes our brief tour. The graphic should now go back to its original in-flow position, elegantly snapping back into place.
80 |
81 |
82 |
83 |
← Back to the blog
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
220 |
221 |
222 |
223 |
224 |
--------------------------------------------------------------------------------
/demo/scrollmagic/ScrollMagic.min.js:
--------------------------------------------------------------------------------
1 | /*! ScrollMagic v2.0.5 | (c) 2015 Jan Paepke (@janpaepke) | license & info: http://scrollmagic.io */
2 | !function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():e.ScrollMagic=t()}(this,function(){"use strict";var e=function(){};e.version="2.0.5",window.addEventListener("mousewheel",function(){});var t="data-scrollmagic-pin-spacer";e.Controller=function(r){var o,s,a="ScrollMagic.Controller",l="FORWARD",c="REVERSE",u="PAUSED",f=n.defaults,d=this,h=i.extend({},f,r),g=[],p=!1,v=0,m=u,w=!0,y=0,S=!0,b=function(){for(var e in h)f.hasOwnProperty(e)||delete h[e];if(h.container=i.get.elements(h.container)[0],!h.container)throw a+" init failed.";w=h.container===window||h.container===document.body||!document.body.contains(h.container),w&&(h.container=window),y=z(),h.container.addEventListener("resize",T),h.container.addEventListener("scroll",T),h.refreshInterval=parseInt(h.refreshInterval)||f.refreshInterval,E()},E=function(){h.refreshInterval>0&&(s=window.setTimeout(A,h.refreshInterval))},x=function(){return h.vertical?i.get.scrollTop(h.container):i.get.scrollLeft(h.container)},z=function(){return h.vertical?i.get.height(h.container):i.get.width(h.container)},C=this._setScrollPos=function(e){h.vertical?w?window.scrollTo(i.get.scrollLeft(),e):h.container.scrollTop=e:w?window.scrollTo(e,i.get.scrollTop()):h.container.scrollLeft=e},F=function(){if(S&&p){var e=i.type.Array(p)?p:g.slice(0);p=!1;var t=v;v=d.scrollPos();var n=v-t;0!==n&&(m=n>0?l:c),m===c&&e.reverse(),e.forEach(function(e){e.update(!0)})}},L=function(){o=i.rAF(F)},T=function(e){"resize"==e.type&&(y=z(),m=u),p!==!0&&(p=!0,L())},A=function(){if(!w&&y!=z()){var e;try{e=new Event("resize",{bubbles:!1,cancelable:!1})}catch(t){e=document.createEvent("Event"),e.initEvent("resize",!1,!1)}h.container.dispatchEvent(e)}g.forEach(function(e){e.refresh()}),E()};this._options=h;var O=function(e){if(e.length<=1)return e;var t=e.slice(0);return t.sort(function(e,t){return e.scrollOffset()>t.scrollOffset()?1:-1}),t};return this.addScene=function(t){if(i.type.Array(t))t.forEach(function(e){d.addScene(e)});else if(t instanceof e.Scene)if(t.controller()!==d)t.addTo(d);else if(g.indexOf(t)<0){g.push(t),g=O(g),t.on("shift.controller_sort",function(){g=O(g)});for(var n in h.globalSceneOptions)t[n]&&t[n].call(t,h.globalSceneOptions[n])}return d},this.removeScene=function(e){if(i.type.Array(e))e.forEach(function(e){d.removeScene(e)});else{var t=g.indexOf(e);t>-1&&(e.off("shift.controller_sort"),g.splice(t,1),e.remove())}return d},this.updateScene=function(t,n){return i.type.Array(t)?t.forEach(function(e){d.updateScene(e,n)}):n?t.update(!0):p!==!0&&t instanceof e.Scene&&(p=p||[],-1==p.indexOf(t)&&p.push(t),p=O(p),L()),d},this.update=function(e){return T({type:"resize"}),e&&F(),d},this.scrollTo=function(n,r){if(i.type.Number(n))C.call(h.container,n,r);else if(n instanceof e.Scene)n.controller()===d&&d.scrollTo(n.scrollOffset(),r);else if(i.type.Function(n))C=n;else{var o=i.get.elements(n)[0];if(o){for(;o.parentNode.hasAttribute(t);)o=o.parentNode;var s=h.vertical?"top":"left",a=i.get.offset(h.container),l=i.get.offset(o);w||(a[s]-=d.scrollPos()),d.scrollTo(l[s]-a[s],r)}}return d},this.scrollPos=function(e){return arguments.length?(i.type.Function(e)&&(x=e),d):x.call(d)},this.info=function(e){var t={size:y,vertical:h.vertical,scrollPos:v,scrollDirection:m,container:h.container,isDocument:w};return arguments.length?void 0!==t[e]?t[e]:void 0:t},this.loglevel=function(){return d},this.enabled=function(e){return arguments.length?(S!=e&&(S=!!e,d.updateScene(g,!0)),d):S},this.destroy=function(e){window.clearTimeout(s);for(var t=g.length;t--;)g[t].destroy(e);return h.container.removeEventListener("resize",T),h.container.removeEventListener("scroll",T),i.cAF(o),null},b(),d};var n={defaults:{container:window,vertical:!0,globalSceneOptions:{},loglevel:2,refreshInterval:100}};e.Controller.addOption=function(e,t){n.defaults[e]=t},e.Controller.extend=function(t){var n=this;e.Controller=function(){return n.apply(this,arguments),this.$super=i.extend({},this),t.apply(this,arguments)||this},i.extend(e.Controller,n),e.Controller.prototype=n.prototype,e.Controller.prototype.constructor=e.Controller},e.Scene=function(n){var o,s,a="BEFORE",l="DURING",c="AFTER",u=r.defaults,f=this,d=i.extend({},u,n),h=a,g=0,p={start:0,end:0},v=0,m=!0,w=function(){for(var e in d)u.hasOwnProperty(e)||delete d[e];for(var t in u)L(t);C()},y={};this.on=function(e,t){return i.type.Function(t)&&(e=e.trim().split(" "),e.forEach(function(e){var n=e.split("."),r=n[0],i=n[1];"*"!=r&&(y[r]||(y[r]=[]),y[r].push({namespace:i||"",callback:t}))})),f},this.off=function(e,t){return e?(e=e.trim().split(" "),e.forEach(function(e){var n=e.split("."),r=n[0],i=n[1]||"",o="*"===r?Object.keys(y):[r];o.forEach(function(e){for(var n=y[e]||[],r=n.length;r--;){var o=n[r];!o||i!==o.namespace&&"*"!==i||t&&t!=o.callback||n.splice(r,1)}n.length||delete y[e]})}),f):f},this.trigger=function(t,n){if(t){var r=t.trim().split("."),i=r[0],o=r[1],s=y[i];s&&s.forEach(function(t){o&&o!==t.namespace||t.callback.call(f,new e.Event(i,t.namespace,f,n))})}return f},f.on("change.internal",function(e){"loglevel"!==e.what&&"tweenChanges"!==e.what&&("triggerElement"===e.what?E():"reverse"===e.what&&f.update())}).on("shift.internal",function(){S(),f.update()}),this.addTo=function(t){return t instanceof e.Controller&&s!=t&&(s&&s.removeScene(f),s=t,C(),b(!0),E(!0),S(),s.info("container").addEventListener("resize",x),t.addScene(f),f.trigger("add",{controller:s}),f.update()),f},this.enabled=function(e){return arguments.length?(m!=e&&(m=!!e,f.update(!0)),f):m},this.remove=function(){if(s){s.info("container").removeEventListener("resize",x);var e=s;s=void 0,e.removeScene(f),f.trigger("remove")}return f},this.destroy=function(e){return f.trigger("destroy",{reset:e}),f.remove(),f.off("*.*"),null},this.update=function(e){if(s)if(e)if(s.enabled()&&m){var t,n=s.info("scrollPos");t=d.duration>0?(n-p.start)/(p.end-p.start):n>=p.start?1:0,f.trigger("update",{startPos:p.start,endPos:p.end,scrollPos:n}),f.progress(t)}else T&&h===l&&O(!0);else s.updateScene(f,!1);return f},this.refresh=function(){return b(),E(),f},this.progress=function(e){if(arguments.length){var t=!1,n=h,r=s?s.info("scrollDirection"):"PAUSED",i=d.reverse||e>=g;if(0===d.duration?(t=g!=e,g=1>e&&i?0:1,h=0===g?a:l):0>e&&h!==a&&i?(g=0,h=a,t=!0):e>=0&&1>e&&i?(g=e,h=l,t=!0):e>=1&&h!==c?(g=1,h=c,t=!0):h!==l||i||O(),t){var o={progress:g,state:h,scrollDirection:r},u=h!=n,p=function(e){f.trigger(e,o)};u&&n!==l&&(p("enter"),p(n===a?"start":"end")),p("progress"),u&&h!==l&&(p(h===a?"start":"end"),p("leave"))}return f}return g};var S=function(){p={start:v+d.offset},s&&d.triggerElement&&(p.start-=s.info("size")*d.triggerHook),p.end=p.start+d.duration},b=function(e){if(o){var t="duration";F(t,o.call(f))&&!e&&(f.trigger("change",{what:t,newval:d[t]}),f.trigger("shift",{reason:t}))}},E=function(e){var n=0,r=d.triggerElement;if(s&&r){for(var o=s.info(),a=i.get.offset(o.container),l=o.vertical?"top":"left";r.parentNode.hasAttribute(t);)r=r.parentNode;var c=i.get.offset(r);o.isDocument||(a[l]-=s.scrollPos()),n=c[l]-a[l]}var u=n!=v;v=n,u&&!e&&f.trigger("shift",{reason:"triggerElementPosition"})},x=function(){d.triggerHook>0&&f.trigger("shift",{reason:"containerResize"})},z=i.extend(r.validate,{duration:function(e){if(i.type.String(e)&&e.match(/^(\.|\d)*\d+%$/)){var t=parseFloat(e)/100;e=function(){return s?s.info("size")*t:0}}if(i.type.Function(e)){o=e;try{e=parseFloat(o())}catch(n){e=-1}}if(e=parseFloat(e),!i.type.Number(e)||0>e)throw o?(o=void 0,0):0;return e}}),C=function(e){e=arguments.length?[e]:Object.keys(z),e.forEach(function(e){var t;if(z[e])try{t=z[e](d[e])}catch(n){t=u[e]}finally{d[e]=t}})},F=function(e,t){var n=!1,r=d[e];return d[e]!=t&&(d[e]=t,C(e),n=r!=d[e]),n},L=function(e){f[e]||(f[e]=function(t){return arguments.length?("duration"===e&&(o=void 0),F(e,t)&&(f.trigger("change",{what:e,newval:d[e]}),r.shifts.indexOf(e)>-1&&f.trigger("shift",{reason:e})),f):d[e]})};this.controller=function(){return s},this.state=function(){return h},this.scrollOffset=function(){return p.start},this.triggerPosition=function(){var e=d.offset;return s&&(e+=d.triggerElement?v:s.info("size")*f.triggerHook()),e};var T,A;f.on("shift.internal",function(e){var t="duration"===e.reason;(h===c&&t||h===l&&0===d.duration)&&O(),t&&_()}).on("progress.internal",function(){O()}).on("add.internal",function(){_()}).on("destroy.internal",function(e){f.removePin(e.reset)});var O=function(e){if(T&&s){var t=s.info(),n=A.spacer.firstChild;if(e||h!==l){var r={position:A.inFlow?"relative":"absolute",top:0,left:0},o=i.css(n,"position")!=r.position;A.pushFollowers?d.duration>0&&(h===c&&0===parseFloat(i.css(A.spacer,"padding-top"))?o=!0:h===a&&0===parseFloat(i.css(A.spacer,"padding-bottom"))&&(o=!0)):r[t.vertical?"top":"left"]=d.duration*g,i.css(n,r),o&&_()}else{"fixed"!=i.css(n,"position")&&(i.css(n,{position:"fixed"}),_());var u=i.get.offset(A.spacer,!0),f=d.reverse||0===d.duration?t.scrollPos-p.start:Math.round(g*d.duration*10)/10;u[t.vertical?"top":"left"]+=f,i.css(A.spacer.firstChild,{top:u.top,left:u.left})}}},_=function(){if(T&&s&&A.inFlow){var e=h===l,t=s.info("vertical"),n=A.spacer.firstChild,r=i.isMarginCollapseType(i.css(A.spacer,"display")),o={};A.relSize.width||A.relSize.autoFullWidth?e?i.css(T,{width:i.get.width(A.spacer)}):i.css(T,{width:"100%"}):(o["min-width"]=i.get.width(t?T:n,!0,!0),o.width=e?o["min-width"]:"auto"),A.relSize.height?e?i.css(T,{height:i.get.height(A.spacer)-(A.pushFollowers?d.duration:0)}):i.css(T,{height:"100%"}):(o["min-height"]=i.get.height(t?n:T,!0,!r),o.height=e?o["min-height"]:"auto"),A.pushFollowers&&(o["padding"+(t?"Top":"Left")]=d.duration*g,o["padding"+(t?"Bottom":"Right")]=d.duration*(1-g)),i.css(A.spacer,o)}},N=function(){s&&T&&h===l&&!s.info("isDocument")&&O()},P=function(){s&&T&&h===l&&((A.relSize.width||A.relSize.autoFullWidth)&&i.get.width(window)!=i.get.width(A.spacer.parentNode)||A.relSize.height&&i.get.height(window)!=i.get.height(A.spacer.parentNode))&&_()},D=function(e){s&&T&&h===l&&!s.info("isDocument")&&(e.preventDefault(),s._setScrollPos(s.info("scrollPos")-((e.wheelDelta||e[s.info("vertical")?"wheelDeltaY":"wheelDeltaX"])/3||30*-e.detail)))};this.setPin=function(e,n){var r={pushFollowers:!0,spacerClass:"scrollmagic-pin-spacer"};if(n=i.extend({},r,n),e=i.get.elements(e)[0],!e)return f;if("fixed"===i.css(e,"position"))return f;if(T){if(T===e)return f;f.removePin()}T=e;var o=T.parentNode.style.display,s=["top","left","bottom","right","margin","marginLeft","marginRight","marginTop","marginBottom"];T.parentNode.style.display="none";var a="absolute"!=i.css(T,"position"),l=i.css(T,s.concat(["display"])),c=i.css(T,["width","height"]);T.parentNode.style.display=o,!a&&n.pushFollowers&&(n.pushFollowers=!1);var u=T.parentNode.insertBefore(document.createElement("div"),T),d=i.extend(l,{position:a?"relative":"absolute",boxSizing:"content-box",mozBoxSizing:"content-box",webkitBoxSizing:"content-box"});if(a||i.extend(d,i.css(T,["width","height"])),i.css(u,d),u.setAttribute(t,""),i.addClass(u,n.spacerClass),A={spacer:u,relSize:{width:"%"===c.width.slice(-1),height:"%"===c.height.slice(-1),autoFullWidth:"auto"===c.width&&a&&i.isMarginCollapseType(l.display)},pushFollowers:n.pushFollowers,inFlow:a},!T.___origStyle){T.___origStyle={};var h=T.style,g=s.concat(["width","height","position","boxSizing","mozBoxSizing","webkitBoxSizing"]);g.forEach(function(e){T.___origStyle[e]=h[e]||""})}return A.relSize.width&&i.css(u,{width:c.width}),A.relSize.height&&i.css(u,{height:c.height}),u.appendChild(T),i.css(T,{position:a?"relative":"absolute",margin:"auto",top:"auto",left:"auto",bottom:"auto",right:"auto"}),(A.relSize.width||A.relSize.autoFullWidth)&&i.css(T,{boxSizing:"border-box",mozBoxSizing:"border-box",webkitBoxSizing:"border-box"}),window.addEventListener("scroll",N),window.addEventListener("resize",N),window.addEventListener("resize",P),T.addEventListener("mousewheel",D),T.addEventListener("DOMMouseScroll",D),O(),f},this.removePin=function(e){if(T){if(h===l&&O(!0),e||!s){var n=A.spacer.firstChild;if(n.hasAttribute(t)){var r=A.spacer.style,o=["margin","marginLeft","marginRight","marginTop","marginBottom"];margins={},o.forEach(function(e){margins[e]=r[e]||""}),i.css(n,margins)}A.spacer.parentNode.insertBefore(n,A.spacer),A.spacer.parentNode.removeChild(A.spacer),T.parentNode.hasAttribute(t)||(i.css(T,T.___origStyle),delete T.___origStyle)}window.removeEventListener("scroll",N),window.removeEventListener("resize",N),window.removeEventListener("resize",P),T.removeEventListener("mousewheel",D),T.removeEventListener("DOMMouseScroll",D),T=void 0}return f};var R,k=[];return f.on("destroy.internal",function(e){f.removeClassToggle(e.reset)}),this.setClassToggle=function(e,t){var n=i.get.elements(e);return 0!==n.length&&i.type.String(t)?(k.length>0&&f.removeClassToggle(),R=t,k=n,f.on("enter.internal_class leave.internal_class",function(e){var t="enter"===e.type?i.addClass:i.removeClass;k.forEach(function(e){t(e,R)})}),f):f},this.removeClassToggle=function(e){return e&&k.forEach(function(e){i.removeClass(e,R)}),f.off("start.internal_class end.internal_class"),R=void 0,k=[],f},w(),f};var r={defaults:{duration:0,offset:0,triggerElement:void 0,triggerHook:.5,reverse:!0,loglevel:2},validate:{offset:function(e){if(e=parseFloat(e),!i.type.Number(e))throw 0;return e},triggerElement:function(e){if(e=e||void 0){var t=i.get.elements(e)[0];if(!t)throw 0;e=t}return e},triggerHook:function(e){var t={onCenter:.5,onEnter:1,onLeave:0};if(i.type.Number(e))e=Math.max(0,Math.min(parseFloat(e),1));else{if(!(e in t))throw 0;e=t[e]}return e},reverse:function(e){return!!e}},shifts:["duration","offset","triggerHook"]};e.Scene.addOption=function(e,t,n,i){e in r.defaults||(r.defaults[e]=t,r.validate[e]=n,i&&r.shifts.push(e))},e.Scene.extend=function(t){var n=this;e.Scene=function(){return n.apply(this,arguments),this.$super=i.extend({},this),t.apply(this,arguments)||this},i.extend(e.Scene,n),e.Scene.prototype=n.prototype,e.Scene.prototype.constructor=e.Scene},e.Event=function(e,t,n,r){r=r||{};for(var i in r)this[i]=r[i];return this.type=e,this.target=this.currentTarget=n,this.namespace=t||"",this.timeStamp=this.timestamp=Date.now(),this};var i=e._util=function(e){var t,n={},r=function(e){return parseFloat(e)||0},i=function(t){return t.currentStyle?t.currentStyle:e.getComputedStyle(t)},o=function(t,n,o,s){if(n=n===document?e:n,n===e)s=!1;else if(!f.DomElement(n))return 0;t=t.charAt(0).toUpperCase()+t.substr(1).toLowerCase();var a=(o?n["offset"+t]||n["outer"+t]:n["client"+t]||n["inner"+t])||0;if(o&&s){var l=i(n);a+="Height"===t?r(l.marginTop)+r(l.marginBottom):r(l.marginLeft)+r(l.marginRight)}return a},s=function(e){return e.replace(/^[^a-z]+([a-z])/g,"$1").replace(/-([a-z])/g,function(e){return e[1].toUpperCase()})};n.extend=function(e){for(e=e||{},t=1;t-1};var a=0,l=["ms","moz","webkit","o"],c=e.requestAnimationFrame,u=e.cancelAnimationFrame;for(t=0;!c&&t=0},f.DomElement=function(e){return"object"==typeof HTMLElement?e instanceof HTMLElement:e&&"object"==typeof e&&null!==e&&1===e.nodeType&&"string"==typeof e.nodeName};var d=n.get={};return d.elements=function(t){var n=[];if(f.String(t))try{t=document.querySelectorAll(t)}catch(r){return n}if("nodelist"===f(t)||f.Array(t))for(var i=0,o=n.length=t.length;o>i;i++){var s=t[i];n[i]=f.DomElement(s)?s:d.elements(s)}else(f.DomElement(t)||t===document||t===e)&&(n=[t]);return n},d.scrollTop=function(t){return t&&"number"==typeof t.scrollTop?t.scrollTop:e.pageYOffset||0},d.scrollLeft=function(t){return t&&"number"==typeof t.scrollLeft?t.scrollLeft:e.pageXOffset||0},d.width=function(e,t,n){return o("width",e,t,n)},d.height=function(e,t,n){return o("height",e,t,n)},d.offset=function(e,t){var n={top:0,left:0};if(e&&e.getBoundingClientRect){var r=e.getBoundingClientRect();n.top=r.top,n.left=r.left,t||(n.top+=d.scrollTop(),n.left+=d.scrollLeft())}return n},n.addClass=function(e,t){t&&(e.classList?e.classList.add(t):e.className+=" "+t)},n.removeClass=function(e,t){t&&(e.classList?e.classList.remove(t):e.className=e.className.replace(RegExp("(^|\\b)"+t.split(" ").join("|")+"(\\b|$)","gi")," "))},n.css=function(e,t){if(f.String(t))return i(e)[s(t)];if(f.Array(t)){var n={},r=i(e);return t.forEach(function(e){n[e]=r[s(e)]}),n}for(var o in t){var a=t[o];a==parseFloat(a)&&(a+="px"),e.style[s(o)]=a}},n}(window||{});return e});
--------------------------------------------------------------------------------
/demo/scrollmagic/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Scrollytelling demo: ScrollMagic.js
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 |
46 |
47 |
48 |
57 |
58 |
62 |
63 |
72 |
73 |
74 |
75 |
76 |
77 |
Step 1 in the graphic. It triggers in the middle of the viewport. For this graphic, it is the same as the initial state so the reader doesn’t miss anything.
78 |
Step 2 arrives. The graphic should be locking into a fixed position right about now. We could have a whole bunch of these “fixed” steps.
79 |
Step 3 concludes our brief tour. The graphic should now go back to its original in-flow position, elegantly snapping back into place.
80 |
81 |
82 |
83 |
← Back to the blog
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
187 |
188 |
189 |
190 |
191 |
--------------------------------------------------------------------------------
/demo/scrollstory/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Scrollytelling demo: ScrollStory.js
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 |
46 |
47 |
48 |
57 |
58 |
62 |
63 |
72 |
73 |
74 |
75 |
76 |
77 |
Step 1 in the graphic. It triggers in the middle of the viewport. For this graphic, it is the same as the initial state so the reader doesn’t miss anything.
78 |
Step 2 arrives. The graphic should be locking into a fixed position right about now. We could have a whole bunch of these “fixed” steps.
79 |
Step 3 concludes our brief tour. The graphic should now go back to its original in-flow position, elegantly snapping back into place.
80 |
81 |
82 |
83 |
← Back to the blog
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
161 |
162 |
163 |
164 |
165 |
--------------------------------------------------------------------------------
/demo/scrollstory/jquery.scrollstory.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @preserve ScrollStory - v0.3.7 - 2016-07-15
3 | * https://github.com/sjwilliams/scrollstory
4 | * Copyright (c) 2016 Josh Williams; Licensed MIT
5 | */
6 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery",undefined],factory)}else{factory(jQuery,undefined)}})(function($,undefined){var pluginName="scrollStory";var defaults={content:null,contentSelector:".story",keyboard:true,scrollOffset:0,triggerOffset:0,scrollEvent:"scroll",autoActivateFirstItem:false,disablePastLastItem:true,speed:800,easing:"swing",throttleType:"throttle",scrollSensitivity:100,throttleTypeOptions:null,autoUpdateOffsets:true,debug:false,enabled:true,setup:$.noop,itembuild:$.noop,itemfocus:$.noop,itemblur:$.noop,itemfilter:$.noop,itemunfilter:$.noop,itementerviewport:$.noop,itemexitviewport:$.noop,categoryfocus:$.noop,categeryblur:$.noop,containeractive:$.noop,containerinactive:$.noop,containerresize:$.noop,containerscroll:$.noop,updateoffsets:$.noop,triggeroffsetupdate:$.noop,scrolloffsetupdate:$.noop,complete:$.noop};var instanceCounter=0;var dateNow=Date.now||function(){return(new Date).getTime()};var debounce=function(func,wait,immediate){var result;var timeout=null;return function(){var context=this,args=arguments;var later=function(){timeout=null;if(!immediate){result=func.apply(context,args)}};var callNow=immediate&&!timeout;clearTimeout(timeout);timeout=setTimeout(later,wait);if(callNow){result=func.apply(context,args)}return result}};var throttle=function(func,wait,options){var context,args,result;var timeout=null;var previous=0;options||(options={});var later=function(){previous=options.leading===false?0:dateNow();timeout=null;result=func.apply(context,args)};return function(){var now=dateNow();if(!previous&&options.leading===false){previous=now}var remaining=wait-(now-previous);context=this;args=arguments;if(remaining<=0){clearTimeout(timeout);timeout=null;previous=now;result=func.apply(context,args)}else if(!timeout&&options.trailing!==false){timeout=setTimeout(later,remaining)}return result}};var $window=$(window);var winHeight=$window.height();var offsetToPx=function(offset){var pxOffset;if(offsetIsAPercentage(offset)){pxOffset=offset.slice(0,-1);pxOffset=Math.round(winHeight*(parseInt(pxOffset,10)/100))}else{pxOffset=parseInt(offset,10)}return pxOffset};var offsetIsAPercentage=function(offset){return typeof offset==="string"&&offset.slice(-1)==="%"};function ScrollStory(element,options){this.el=element;this.$el=$(element);this.options=$.extend({},defaults,options);this.useNativeScroll=typeof this.options.scrollEvent==="string"&&this.options.scrollEvent.indexOf("scroll")===0;this._defaults=defaults;this._name=pluginName;this._instanceId=function(){return pluginName+"_"+instanceCounter}();this.init()}ScrollStory.prototype={init:function(){this._items=[];this._itemsById={};this._categories=[];this._tags=[];this._isActive=false;this._activeItem;this._previousItems=[];this.$el.on("setup",this._onSetup.bind(this));this.$el.on("containeractive",this._onContainerActive.bind(this));this.$el.on("containerinactive",this._onContainerInactive.bind(this));this.$el.on("itemblur",this._onItemBlur.bind(this));this.$el.on("itemfocus",this._onItemFocus.bind(this));this.$el.on("itementerviewport",this._onItemEnterViewport.bind(this));this.$el.on("itemexitviewport",this._onItemExitViewport.bind(this));this.$el.on("itemfilter",this._onItemFilter.bind(this));this.$el.on("itemunfilter",this._onItemUnfilter.bind(this));this.$el.on("categoryfocus",this._onCategoryFocus.bind(this));this.$el.on("triggeroffsetupdate",this._onTriggerOffsetUpdate.bind(this));this._trigger("setup",null,this);this.addItems(this.options.content,{handleRepaint:false});this.updateOffsets();this._trigger("complete",null,this);this._handleRepaint();if(this.options.keyboard){$(document).keydown(function(e){var captured=true;switch(e.keyCode){case 37:if(e.metaKey){return}this.previous();break;case 39:this.next();break;default:captured=false}return!captured}.bind(this))}this.$trigger=$('
').css({position:"fixed",width:"100%",height:"1px",top:offsetToPx(this.options.triggerOffset)+"px",left:"0px",backgroundColor:"#ff0000","-webkit-transform":"translateZ(0)","-webkit-backface-visibility":"hidden",zIndex:1e3}).attr("id",pluginName+"Trigger-"+this._instanceId);if(this.options.debug){this.$trigger.appendTo("body")}var scrollThrottle,scrollHandler;if(this.useNativeScroll){scrollThrottle=this.options.throttleType==="throttle"?throttle:debounce;scrollHandler=scrollThrottle(this._handleScroll.bind(this),this.options.scrollSensitivity,this.options.throttleTypeOptions);$window.on("scroll",scrollHandler)}else{scrollHandler=this._handleScroll.bind(this);if(typeof this.options.scrollEvent==="function"){this.options.scrollEvent(scrollHandler)}else{$window.on(this.options.scrollEvent,function(){scrollHandler()})}}var resizeThrottle=debounce(this._handleResize,100);$window.on("DOMContentLoaded load resize",resizeThrottle.bind(this));instanceCounter=instanceCounter+1},index:function(index,callback){if(typeof index==="number"&&this.getItemByIndex(index)){this.setActiveItem(this.getItemByIndex(index),{},callback)}else{return this.getActiveItem().index}},next:function(){this.index(this.index()+1)},previous:function(){this.index(this.index()-1)},getActiveItem:function(){return this._activeItem},setActiveItem:function(item,options,callback){options=options||{};if(item.id&&this.getItemById(item.id)){this._scrollToItem(item,options,callback)}},each:function(callback){this.applyToAllItems(callback)},getLength:function(){return this.getItems().length},getItems:function(){return this._items},getItemById:function(id){return this._itemsById[id]},getItemByIndex:function(index){return this._items[index]},getItemsBy:function(truthTest){if(typeof truthTest!=="function"){throw new Error("You must provide a truthTest function")}return this.getItems().filter(function(item){return truthTest(item)})},getItemsWhere:function(properties){var keys,items=[];if($.isPlainObject(properties)){keys=Object.keys(properties);items=this.getItemsBy(function(item){var isMatch=keys.every(function(key){var match;if(typeof properties[key]==="function"){match=properties[key](item[key]);if(typeof match!=="boolean"){match=item[key]===match}}else{match=item[key]===properties[key]}return match});if(isMatch){return item}})}return items},getItemsInViewport:function(){return this.getItemsWhere({inViewport:true})},getPreviousItem:function(){return this._previousItems[0]},getPreviousItems:function(){return this._previousItems},getPercentScrollToLastItem:function(){return this._percentScrollToLastItem||0},getFilteredItems:function(){return this.getItemsWhere({filtered:true})},getUnFilteredItems:function(){return this.getItemsWhere({filtered:false})},getItemsByCategory:function(categorySlug){return this.getItemsWhere({category:categorySlug})},getCategorySlugs:function(){return this._categories},filter:function(item){if(!item.filtered){item.filtered=true;this._trigger("itemfilter",null,item)}},unfilter:function(item){if(item.filtered){item.filtered=false;this._trigger("itemunfilter",null,item)}},filterAll:function(callback){callback=$.isFunction(callback)?callback.bind(this):$.noop;var filterFnc=this.filter.bind(this);this.getItems().forEach(filterFnc)},unfilterAll:function(callback){callback=$.isFunction(callback)?callback.bind(this):$.noop;var unfilterFnc=this.unfilter.bind(this);this.getItems().forEach(unfilterFnc)},filterBy:function(truthTest,callback){callback=$.isFunction(callback)?callback.bind(this):$.noop;var filterFnc=this.filter.bind(this);this.getItemsBy(truthTest).forEach(filterFnc);callback()},filterWhere:function(properties,callback){callback=$.isFunction(callback)?callback.bind(this):$.noop;var filterFnc=this.filter.bind(this);this.getItemsWhere(properties).forEach(filterFnc);callback()},isContainerActive:function(){return this._isActive},disable:function(){this.options.enabled=false},enable:function(){this.options.enabled=true},updateTriggerOffset:function(offset){this.options.triggerOffset=offset;this.updateOffsets();this._trigger("triggeroffsetupdate",null,offsetToPx(offset))},updateScrollOffset:function(offset){this.options.scrollOffset=offset;this.updateOffsets();this._trigger("scrolloffsetupdate",null,offsetToPx(offset))},_setActiveItem:function(){var containerInActiveArea=this._distanceToFirstItemTopOffset<=0&&Math.abs(this._distanceToOffset)-this._height<0;var items=this.getItemsWhere({filtered:false});var activeItem;items.forEach(function(item){if(item.adjustedDistanceToOffset<=0){if(!activeItem){activeItem=item}else{if(activeItem.adjustedDistanceToOffset0){activeItem=items[0]}if(activeItem){this._focusItem(activeItem);if(!this._isActive){this._isActive=true;this._trigger("containeractive")}}else{this._blurAllItems();if(this._isActive){this._isActive=false;this._trigger("containerinactive")}}},_scrollToItem:function(item,opts,callback){callback=$.isFunction(callback)?callback.bind(this):$.noop;opts=$.extend(true,{scrollOffset:item.scrollOffset!==false?offsetToPx(item.scrollOffset):offsetToPx(this.options.scrollOffset),speed:this.options.speed,easing:this.options.easing},opts);var debouncedCallback=debounce(callback,100);var scrolllTop=item.el.offset().top-offsetToPx(opts.scrollOffset);$("html, body").stop(true).animate({scrollTop:scrolllTop},opts.speed,opts.easing,debouncedCallback)},applyToAllItems:function(callback,exceptions){exceptions=$.isArray(exceptions)?exceptions:[exceptions];callback=$.isFunction(callback)?callback.bind(this):$.noop;var items=this.getItems();var i=0;var length=items.length;var item;for(i=0;i=0){item.percentScrollComplete=0}else if(Math.abs(item.distanceToOffset)>=rect.height){item.percentScrollComplete=100}else{item.percentScrollComplete=Math.abs(item.distanceToOffset)/rect.height}previouslyInViewport=item.inViewport;item.inViewport=rect.bottom>0&&rect.right>0&&rect.left=0&&rect.left>=0&&rect.bottom<=wHeight&&rect.right<=wWidth;if(item.inViewport&&!previouslyInViewport){this._trigger("itementerviewport",null,item)}else if(!item.inViewport&&previouslyInViewport){this._trigger("itemexitviewport",null,item)}}this._distanceToFirstItemTopOffset=items[0].adjustedDistanceToOffset;this._distanceToOffset=this._topOffset-scrollTop-triggerOffset;var percentScrollToLastItem=0;if(this._distanceToOffset<0){percentScrollToLastItem=1-lastItem.distanceToOffset/(this._height-lastItem.height);percentScrollToLastItem=percentScrollToLastItem<1?percentScrollToLastItem:1}this._percentScrollToLastItem=percentScrollToLastItem},addItems:function(items,opts){opts=$.extend(true,{handleRepaint:true},opts);if(items instanceof $){this._prepItemsFromSelection(items)}else if(typeof items==="string"){this._prepItemsFromSelection(this.$el.find(items))}else if($.isArray(items)){this._prepItemsFromData(items)}else{this._prepItemsFromSelection(this.$el.find(this.options.contentSelector))}if(this.getItems().length<1){throw new Error("addItems found no valid items.")}if(opts.handleRepaint){this._handleRepaint()}},_handleRepaint:function(updateOffsets){updateOffsets=updateOffsets===false?false:true;if(updateOffsets){this.updateOffsets()}this._updateScrollPositions();this._setActiveItem()},_handleScroll:function(){if(this.options.enabled){this._handleRepaint(false);this._trigger("containerscroll")}},_handleResize:function(){winHeight=$window.height();if(this.options.enabled&&this.options.autoUpdateOffsets){if(offsetIsAPercentage(this.options.triggerOffset)){this.updateTriggerOffset(this.options.triggerOffset)}if(offsetIsAPercentage(this.options.scrollOffset)){this.updateScrollOffset(this.options.scrollOffset)}this._debouncedHandleRepaint();this._trigger("containerresize")}},_onSetup:function(){this.$el.addClass(pluginName)},_onContainerActive:function(){this.$el.addClass(pluginName+"Active")},_onContainerInactive:function(){this.$el.removeClass(pluginName+"Active")},_onItemFocus:function(ev,item){item.el.addClass("active");this._manageContainerClasses("scrollStoryActiveItem-",item.id);if(item.category){if(this.getPreviousItem()&&this.getPreviousItem().category!==item.category||!this.isContainerActive()){this._trigger("categoryfocus",null,item.category);if(this.getPreviousItem()){this._trigger("categoryblur",null,this.getPreviousItem().category)}}}},_onItemBlur:function(ev,item){this._previousItems.unshift(item);item.el.removeClass("active")},_onItemEnterViewport:function(ev,item){item.el.addClass("inviewport")},_onItemExitViewport:function(ev,item){item.el.removeClass("inviewport")},_onItemFilter:function(ev,item){item.el.addClass("filtered");if(this.options.autoUpdateOffsets){this._debouncedHandleRepaint()}},_onItemUnfilter:function(ev,item){item.el.removeClass("filtered");if(this.options.autoUpdateOffsets){this._debouncedHandleRepaint()}},_onCategoryFocus:function(ev,category){this._manageContainerClasses("scrollStoryActiveCategory-",category)},_onTriggerOffsetUpdate:function(ev,offset){this.$trigger.css({top:offset+"px"})},_manageContainerClasses:function(prefix,value){this.$el.removeClass(function(index,classes){return classes.split(" ").filter(function(c){return c.lastIndexOf(prefix,0)===0}).join(" ")});this.$el.addClass(prefix+value)},_prepItemsFromSelection:function($selection){var that=this;$selection.each(function(){that._addItem({},$(this))})},_prepItemsFromData:function(items){var that=this;var selector=this.options.contentSelector.replace(/\./g,"");var frag=document.createDocumentFragment();items.forEach(function(data){var $item=$('
');that._addItem(data,$item);frag.appendChild($item.get(0))});this.$el.append(frag)},_addItem:function(data,$el){var domData=$el.data();var item={index:this._items.length,el:$el,id:$el.attr("id")?$el.attr("id"):data.id?data.id:"story"+instanceCounter+"-"+this._items.length,data:$.extend({},data,domData),category:domData.category||data.category,tags:data.tags||[],scrollStory:this,active:false,filtered:false,scrollOffset:false,triggerOffset:false,inViewport:false};if(!$el.attr("id")){$el.attr("id",item.id)}$el.addClass("scrollStoryItem");this._items.push(item);this._itemsById[item.id]=item;this._trigger("itembuild",null,item);if(item.category&&this._categories.indexOf(item.category)===-1){this._categories.push(item.category)}},_trigger:function(eventType,event,data){var callback=this.options[eventType];var prop,orig;if($.isFunction(callback)){data=data||{};event=$.Event(event);event.target=this.el;event.type=eventType;orig=event.originalEvent;if(orig){for(prop in orig){if(!(prop in event)){event[prop]=orig[prop]}}}this.$el.trigger(event,data);var boundCb=this.options[eventType].bind(this);boundCb(event,data)}}};ScrollStory.prototype.debouncedUpdateOffsets=debounce(ScrollStory.prototype.updateOffsets,100);ScrollStory.prototype._debouncedHandleRepaint=debounce(ScrollStory.prototype._handleRepaint,100);$.fn[pluginName]=function(options){return this.each(function(){if(!$.data(this,"plugin_"+pluginName)){$.data(this,"plugin_"+pluginName,new ScrollStory(this,options))}})}});
--------------------------------------------------------------------------------
/demo/waypoints/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Scrollytelling demo: Waypoints
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 |
46 |
47 |
48 |
57 |
58 |
62 |
63 |
72 |
73 |
74 |
75 |
76 |
77 |
Step 1 in the graphic. It triggers in the middle of the viewport. For this graphic, it is the same as the initial state so the reader doesn’t miss anything.
78 |
Step 2 arrives. The graphic should be locking into a fixed position right about now. We could have a whole bunch of these “fixed” steps.
79 |
Step 3 concludes our brief tour. The graphic should now go back to its original in-flow position, elegantly snapping back into place.
80 |
81 |
82 |
83 |
← Back to the blog
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
178 |
179 |
180 |
181 |
182 |
--------------------------------------------------------------------------------
/demo/waypoints/noframework.waypoints.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | Waypoints - 4.0.1
3 | Copyright © 2011-2016 Caleb Troughton
4 | Licensed under the MIT license.
5 | https://github.com/imakewebthings/waypoints/blob/master/licenses.txt
6 | */
7 | !function(){"use strict";function t(n){if(!n)throw new Error("No options passed to Waypoint constructor");if(!n.element)throw new Error("No element option passed to Waypoint constructor");if(!n.handler)throw new Error("No handler option passed to Waypoint constructor");this.key="waypoint-"+e,this.options=t.Adapter.extend({},t.defaults,n),this.element=this.options.element,this.adapter=new t.Adapter(this.element),this.callback=n.handler,this.axis=this.options.horizontal?"horizontal":"vertical",this.enabled=this.options.enabled,this.triggerPoint=null,this.group=t.Group.findOrCreate({name:this.options.group,axis:this.axis}),this.context=t.Context.findOrCreateByElement(this.options.context),t.offsetAliases[this.options.offset]&&(this.options.offset=t.offsetAliases[this.options.offset]),this.group.add(this),this.context.add(this),i[this.key]=this,e+=1}var e=0,i={};t.prototype.queueTrigger=function(t){this.group.queueTrigger(this,t)},t.prototype.trigger=function(t){this.enabled&&this.callback&&this.callback.apply(this,t)},t.prototype.destroy=function(){this.context.remove(this),this.group.remove(this),delete i[this.key]},t.prototype.disable=function(){return this.enabled=!1,this},t.prototype.enable=function(){return this.context.refresh(),this.enabled=!0,this},t.prototype.next=function(){return this.group.next(this)},t.prototype.previous=function(){return this.group.previous(this)},t.invokeAll=function(t){var e=[];for(var n in i)e.push(i[n]);for(var o=0,r=e.length;r>o;o++)e[o][t]()},t.destroyAll=function(){t.invokeAll("destroy")},t.disableAll=function(){t.invokeAll("disable")},t.enableAll=function(){t.Context.refreshAll();for(var e in i)i[e].enabled=!0;return this},t.refreshAll=function(){t.Context.refreshAll()},t.viewportHeight=function(){return window.innerHeight||document.documentElement.clientHeight},t.viewportWidth=function(){return document.documentElement.clientWidth},t.adapters=[],t.defaults={context:window,continuous:!0,enabled:!0,group:"default",horizontal:!1,offset:0},t.offsetAliases={"bottom-in-view":function(){return this.context.innerHeight()-this.adapter.outerHeight()},"right-in-view":function(){return this.context.innerWidth()-this.adapter.outerWidth()}},window.Waypoint=t}(),function(){"use strict";function t(t){window.setTimeout(t,1e3/60)}function e(t){this.element=t,this.Adapter=o.Adapter,this.adapter=new this.Adapter(t),this.key="waypoint-context-"+i,this.didScroll=!1,this.didResize=!1,this.oldScroll={x:this.adapter.scrollLeft(),y:this.adapter.scrollTop()},this.waypoints={vertical:{},horizontal:{}},t.waypointContextKey=this.key,n[t.waypointContextKey]=this,i+=1,o.windowContext||(o.windowContext=!0,o.windowContext=new e(window)),this.createThrottledScrollHandler(),this.createThrottledResizeHandler()}var i=0,n={},o=window.Waypoint,r=window.onload;e.prototype.add=function(t){var e=t.options.horizontal?"horizontal":"vertical";this.waypoints[e][t.key]=t,this.refresh()},e.prototype.checkEmpty=function(){var t=this.Adapter.isEmptyObject(this.waypoints.horizontal),e=this.Adapter.isEmptyObject(this.waypoints.vertical),i=this.element==this.element.window;t&&e&&!i&&(this.adapter.off(".waypoints"),delete n[this.key])},e.prototype.createThrottledResizeHandler=function(){function t(){e.handleResize(),e.didResize=!1}var e=this;this.adapter.on("resize.waypoints",function(){e.didResize||(e.didResize=!0,o.requestAnimationFrame(t))})},e.prototype.createThrottledScrollHandler=function(){function t(){e.handleScroll(),e.didScroll=!1}var e=this;this.adapter.on("scroll.waypoints",function(){(!e.didScroll||o.isTouch)&&(e.didScroll=!0,o.requestAnimationFrame(t))})},e.prototype.handleResize=function(){o.Context.refreshAll()},e.prototype.handleScroll=function(){var t={},e={horizontal:{newScroll:this.adapter.scrollLeft(),oldScroll:this.oldScroll.x,forward:"right",backward:"left"},vertical:{newScroll:this.adapter.scrollTop(),oldScroll:this.oldScroll.y,forward:"down",backward:"up"}};for(var i in e){var n=e[i],o=n.newScroll>n.oldScroll,r=o?n.forward:n.backward;for(var s in this.waypoints[i]){var l=this.waypoints[i][s];if(null!==l.triggerPoint){var a=n.oldScroll=l.triggerPoint,p=a&&h,u=!a&&!h;(p||u)&&(l.queueTrigger(r),t[l.group.id]=l.group)}}}for(var d in t)t[d].flushTriggers();this.oldScroll={x:e.horizontal.newScroll,y:e.vertical.newScroll}},e.prototype.innerHeight=function(){return this.element==this.element.window?o.viewportHeight():this.adapter.innerHeight()},e.prototype.remove=function(t){delete this.waypoints[t.axis][t.key],this.checkEmpty()},e.prototype.innerWidth=function(){return this.element==this.element.window?o.viewportWidth():this.adapter.innerWidth()},e.prototype.destroy=function(){var t=[];for(var e in this.waypoints)for(var i in this.waypoints[e])t.push(this.waypoints[e][i]);for(var n=0,o=t.length;o>n;n++)t[n].destroy()},e.prototype.refresh=function(){var t,e=this.element==this.element.window,i=e?void 0:this.adapter.offset(),n={};this.handleScroll(),t={horizontal:{contextOffset:e?0:i.left,contextScroll:e?0:this.oldScroll.x,contextDimension:this.innerWidth(),oldScroll:this.oldScroll.x,forward:"right",backward:"left",offsetProp:"left"},vertical:{contextOffset:e?0:i.top,contextScroll:e?0:this.oldScroll.y,contextDimension:this.innerHeight(),oldScroll:this.oldScroll.y,forward:"down",backward:"up",offsetProp:"top"}};for(var r in t){var s=t[r];for(var l in this.waypoints[r]){var a,h,p,u,d,f=this.waypoints[r][l],c=f.options.offset,w=f.triggerPoint,y=0,g=null==w;f.element!==f.element.window&&(y=f.adapter.offset()[s.offsetProp]),"function"==typeof c?c=c.apply(f):"string"==typeof c&&(c=parseFloat(c),f.options.offset.indexOf("%")>-1&&(c=Math.ceil(s.contextDimension*c/100))),a=s.contextScroll-s.contextOffset,f.triggerPoint=Math.floor(y+a-c),h=w=s.oldScroll,u=h&&p,d=!h&&!p,!g&&u?(f.queueTrigger(s.backward),n[f.group.id]=f.group):!g&&d?(f.queueTrigger(s.forward),n[f.group.id]=f.group):g&&s.oldScroll>=f.triggerPoint&&(f.queueTrigger(s.forward),n[f.group.id]=f.group)}}return o.requestAnimationFrame(function(){for(var t in n)n[t].flushTriggers()}),this},e.findOrCreateByElement=function(t){return e.findByElement(t)||new e(t)},e.refreshAll=function(){for(var t in n)n[t].refresh()},e.findByElement=function(t){return n[t.waypointContextKey]},window.onload=function(){r&&r(),e.refreshAll()},o.requestAnimationFrame=function(e){var i=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||t;i.call(window,e)},o.Context=e}(),function(){"use strict";function t(t,e){return t.triggerPoint-e.triggerPoint}function e(t,e){return e.triggerPoint-t.triggerPoint}function i(t){this.name=t.name,this.axis=t.axis,this.id=this.name+"-"+this.axis,this.waypoints=[],this.clearTriggerQueues(),n[this.axis][this.name]=this}var n={vertical:{},horizontal:{}},o=window.Waypoint;i.prototype.add=function(t){this.waypoints.push(t)},i.prototype.clearTriggerQueues=function(){this.triggerQueues={up:[],down:[],left:[],right:[]}},i.prototype.flushTriggers=function(){for(var i in this.triggerQueues){var n=this.triggerQueues[i],o="up"===i||"left"===i;n.sort(o?e:t);for(var r=0,s=n.length;s>r;r+=1){var l=n[r];(l.options.continuous||r===n.length-1)&&l.trigger([i])}}this.clearTriggerQueues()},i.prototype.next=function(e){this.waypoints.sort(t);var i=o.Adapter.inArray(e,this.waypoints),n=i===this.waypoints.length-1;return n?null:this.waypoints[i+1]},i.prototype.previous=function(e){this.waypoints.sort(t);var i=o.Adapter.inArray(e,this.waypoints);return i?this.waypoints[i-1]:null},i.prototype.queueTrigger=function(t,e){this.triggerQueues[e].push(t)},i.prototype.remove=function(t){var e=o.Adapter.inArray(t,this.waypoints);e>-1&&this.waypoints.splice(e,1)},i.prototype.first=function(){return this.waypoints[0]},i.prototype.last=function(){return this.waypoints[this.waypoints.length-1]},i.findOrCreate=function(t){return n[t.axis][t.name]||new i(t)},o.Group=i}(),function(){"use strict";function t(t){return t===t.window}function e(e){return t(e)?e:e.defaultView}function i(t){this.element=t,this.handlers={}}var n=window.Waypoint;i.prototype.innerHeight=function(){var e=t(this.element);return e?this.element.innerHeight:this.element.clientHeight},i.prototype.innerWidth=function(){var e=t(this.element);return e?this.element.innerWidth:this.element.clientWidth},i.prototype.off=function(t,e){function i(t,e,i){for(var n=0,o=e.length-1;o>n;n++){var r=e[n];i&&i!==r||t.removeEventListener(r)}}var n=t.split("."),o=n[0],r=n[1],s=this.element;if(r&&this.handlers[r]&&o)i(s,this.handlers[r][o],e),this.handlers[r][o]=[];else if(o)for(var l in this.handlers)i(s,this.handlers[l][o]||[],e),this.handlers[l][o]=[];else if(r&&this.handlers[r]){for(var a in this.handlers[r])i(s,this.handlers[r][a],e);this.handlers[r]={}}},i.prototype.offset=function(){if(!this.element.ownerDocument)return null;var t=this.element.ownerDocument.documentElement,i=e(this.element.ownerDocument),n={top:0,left:0};return this.element.getBoundingClientRect&&(n=this.element.getBoundingClientRect()),{top:n.top+i.pageYOffset-t.clientTop,left:n.left+i.pageXOffset-t.clientLeft}},i.prototype.on=function(t,e){var i=t.split("."),n=i[0],o=i[1]||"__default",r=this.handlers[o]=this.handlers[o]||{},s=r[n]=r[n]||[];s.push(e),this.element.addEventListener(n,e)},i.prototype.outerHeight=function(e){var i,n=this.innerHeight();return e&&!t(this.element)&&(i=window.getComputedStyle(this.element),n+=parseInt(i.marginTop,10),n+=parseInt(i.marginBottom,10)),n},i.prototype.outerWidth=function(e){var i,n=this.innerWidth();return e&&!t(this.element)&&(i=window.getComputedStyle(this.element),n+=parseInt(i.marginLeft,10),n+=parseInt(i.marginRight,10)),n},i.prototype.scrollLeft=function(){var t=e(this.element);return t?t.pageXOffset:this.element.scrollLeft},i.prototype.scrollTop=function(){var t=e(this.element);return t?t.pageYOffset:this.element.scrollTop},i.extend=function(){function t(t,e){if("object"==typeof t&&"object"==typeof e)for(var i in e)e.hasOwnProperty(i)&&(t[i]=e[i]);return t}for(var e=Array.prototype.slice.call(arguments),i=1,n=e.length;n>i;i++)t(e[0],e[i]);return e[0]},i.inArray=function(t,e,i){return null==e?-1:e.indexOf(t,i)},i.isEmptyObject=function(t){for(var e in t)return!1;return!0},n.adapters.push({name:"noframework",Adapter:i}),n.Adapter=i}();
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | How to implement scrollytelling with six different libraries
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 |
49 |
50 |
51 |
52 |
63 |
64 |
65 |
How to implement scrollytelling with six different libraries
66 |
67 |
68 | Russell Goldenberg
69 |
70 | January 2017
71 |
72 | Scrollytelling is the best (or the worst depending on your
73 | point of view ). We love it here at
74 | The Pudding . But creating a scroll-driven story is hardly a standardized practice, and there are many libraries
75 | out there that can help make it happen.
76 | Check out my
77 | follow-up post , where we look at mobile solutions.
78 |
79 |
80 | “Scrollytelling” is when content (e.g., a graphic) is revealed or changed as the user scrolls. It does not alter scroll behavior,
81 | but simply monitors it. As Martha Stewart would say, “It’s a good thing.” Do not confuse this will scrolljacking, which
82 | manipulates the browser’s scroll mechanics with JavaScript, which is generally considered bad practice. Here are a few
83 | examples of the good stuff in action:
84 |
85 |
99 |
100 | In this post, I look at how to tackle a simple scroll-driven chart using six different libraries and share
101 | my thoughts on each implementation. It is inspired by Lisa Charlotte Rost’s
102 | great post comparing charting libraries.
103 |
104 |
105 | I'm not going to get into the different types of scrollytelling, or debate the serious moral and ethical implications of
106 | the practice. I defer to
107 | these
108 | posts for scrollytelling primers.
109 |
110 |
We want to make something like this:
111 |
112 |
113 | There are two goals here:
114 | 1. Use the "scroll-to-trigger" pattern where the trigger element (in our case a block of text) tells the chart to update
115 | to a new state.
116 | 2. We want the chart to stay fixed while the text moves and have it snap back into place as it enters and exits, because
117 | often the scrollytelling portion is not the entire story.
118 |
119 |
120 |
121 | Disclaimer:
122 | this post does not cover responsive solutions (though you should do it, and most libraries make it pretty painless).
123 | If you have the audacity to resize your browser, refresh the page. For mobile and responsive solutions, check out my
124 | follow-up post,
125 |
126 |
127 |
128 | Update (November 2017): I created my own scrollytelling library,
129 | scrollama.js . The goal of this library is to provide a simple interface for creating scroll-driven interactives.
130 | Scrollama is focused on perfomance by using IntersectionObserver in favor of scroll events.
131 | Check it out .
132 |
133 |
134 |
135 |
136 |
137 |
138 |
141 |
142 |
143 | Waypoints is a very solid choice. It has been around for a while, and supports both vanilla JS and jQuery integration. It
144 | has an easy to understand API, and good documentation. It has a lot of add-ons, like a debugger extension to reduce
145 | some head-scratching, inview detection, and sticky elements. Below is the core code used to implement this library.
146 | See the demo for the full code with comments.
147 |
148 |
149 |
150 | Demo →
151 |
152 |
153 |
154 |
155 |
156 | ...
157 |
158 | var waypoints = triggerEls.map(function(el) {
159 | var step = +el.getAttribute('data-step')
160 |
161 | return new Waypoint({
162 | element: el,
163 | handler: function(direction) {
164 | var nextStep = direction === 'down' ? step : Math.max(0, step - 1)
165 | graphic.update(nextStep)
166 | },
167 | offset: '50%',
168 | })
169 | })
170 |
171 | var enterWaypoint = new Waypoint({
172 | element: graphicEl,
173 | handler: function(direction) {
174 | var fixed = direction === 'down'
175 | var bottom = false
176 | toggle(fixed, bottom)
177 | },
178 | })
179 |
180 | var exitWaypoint = new Waypoint({
181 | element: graphicEl,
182 | handler: function(direction) {
183 | var fixed = direction === 'up'
184 | var bottom = !fixed
185 | toggle(fixed, bottom)
186 | },
187 | offset: 'bottom-in-view',
188 | })
189 |
190 | ...
191 |
192 |
193 |
Expand to see full code
194 |
195 |
196 |
197 |
198 |
201 |
202 | ScrollStory is a jQuery-based library used for some projects at The New York Times. It has a super clear API and supports
203 | tons of options. The two big drawbacks that I found were that it depends on jQuery (which is not a problem if you use
204 | it by default) and it requires some additional code to get the fixed graphic part working properly. If those two things
205 | are not important to you, I recommend exploring this one since it is setup to handle a variety of use cases.
206 |
207 |
208 | Demo →
209 |
210 |
211 |
212 |
213 | ...
214 |
215 | var handleItemFocus = function(event, item) {
216 | var step = item.data.step
217 | graphic.update(step)
218 | }
219 |
220 | var handleContainerScroll = function(event) {
221 | var bottom = false
222 | var fixed = false
223 |
224 | var bb = $graphicEl[0].getBoundingClientRect()
225 | var bottomFromTop = bb.bottom - viewportHeight
226 |
227 | if (bb.top < 0 && bottomFromTop > 0) {
228 | bottom = false
229 | fixed = true
230 | } else if (bb.top < 0 && bottomFromTop < 0) {
231 | bottom = true
232 | fixed = false
233 | }
234 |
235 | toggle(fixed, bottom)
236 | }
237 |
238 | $graphicEl.scrollStory({
239 | contentSelector: '.trigger',
240 | triggerOffset: halfViewportHeight,
241 | itemfocus: handleItemFocus,
242 | containerscroll: handleContainerScroll,
243 | })
244 |
245 | ...
246 |
247 |
248 |
Expand to see full code
249 |
250 |
251 |
317 |
318 |
352 |
353 |
354 |
355 |
358 |
359 | in-view.js is a great library in general, and I tried to adapt it to my scrollytelling needs. While one of its features is
360 | performance optimization, that comes with the downside of only having a single global offset for triggering an enter/exit.
361 | I had to use a
362 | modified version that allows for creating multiple instances to make it work for this scenario. Also, the offsets
363 | are a bit confusing to use in this context. In short, this library is amazingly simple to use for just triggering, but
364 | definitely not designed for customized scrollytelling.
365 |
366 |
367 | Demo →
368 |
369 |
370 |
371 |
372 | ...
373 |
374 | var inviewTrigger = inView()
375 |
376 | inviewTrigger.offset({
377 | top: 0,
378 | right: 0,
379 | bottom: halfViewportHeight,
380 | left: 0,
381 | })
382 |
383 | inviewTrigger('.trigger')
384 | .on('enter', function(el) {
385 | var step = +el.getAttribute('data-step')
386 | graphic.update(step)
387 | })
388 |
389 | var inviewTop = inView()
390 |
391 | inviewTop.offset({
392 | top: -999999,
393 | right: 0,
394 | bottom: window.innerHeight,
395 | left: 0,
396 | })
397 |
398 | inviewTop('.graphic')
399 | .on('enter', function(el) {
400 | var fixed = true
401 | var bottom = false
402 | toggle(fixed, bottom)
403 | })
404 | .on('exit', function(el) {
405 | var fixed = false
406 | var bottom = false
407 | toggle(fixed, bottom)
408 | })
409 |
410 | var inviewBottom = inView()
411 |
412 | inviewBottom.offset({
413 | top: -999999,
414 | right: 0,
415 | bottom: graphicEl.offsetHeight,
416 | left: 0,
417 | })
418 |
419 | inviewBottom('.graphic')
420 | .on('enter', function(el) {
421 | var fixed = false
422 | var bottom = true
423 | toggle(fixed, bottom)
424 | })
425 | .on('exit', function(el) {
426 | var fixed = true
427 | var bottom = false
428 | toggle(fixed, bottom)
429 | })
430 |
431 | ...
432 |
433 |
434 |
Expand to see full code
435 |
436 |
437 |
438 |
439 |
442 |
443 |
444 | When in doubt, roll your own. There will be a bit more coding involved (and some fun math!), but you get to familiarize yourself
445 | with the core concepts of how scroll-driven libraries are created. It is all custom, so you can have it do whatever
446 | you want. You will want to consider performance optimizations like throttling and such.
447 |
448 |
449 | Demo →
450 |
451 |
452 |
453 |
454 | ...
455 |
456 | var bbTop = 0
457 | var bbBottom = 0
458 | var height = graphicEl.getBoundingClientRect().height
459 | var prevStep = 0
460 | var currentStep = 0
461 | var numSteps = triggerEls.length
462 |
463 | var checkTrigger = function() {
464 | if (bbTop < viewportHeight && bbBottom > 0) {
465 | var progress = Math.abs(bbTop - halfViewportHeight) / height * numSteps
466 | var step = Math.floor(progress)
467 | currentStep = Math.min(Math.max(step, 0), numSteps - 1)
468 | }
469 | }
470 |
471 | var checkEnterExit = function() {
472 | var bottomFromTop = bbBottom - viewportHeight
473 | var bottom
474 | var fixed
475 |
476 | if (bbTop < 0 && bottomFromTop > 0) {
477 | bottom = false
478 | fixed = true
479 | } else if (bbTop < 0 && bottomFromTop < 0) {
480 | bottom = true
481 | fixed = false
482 | } else {
483 | bottom = false
484 | fixed = false
485 | }
486 |
487 | toggle(fixed, bottom)
488 | }
489 |
490 | var handleScroll = function() {
491 | var bb = graphicEl.getBoundingClientRect()
492 | bbTop = bb.top
493 | bbBottom = bb.bottom
494 |
495 | checkTrigger()
496 | checkEnterExit()
497 | }
498 |
499 | window.addEventListener('scroll', throttle(handleScroll, 50))
500 |
501 | var render = function() {
502 | if (currentStep !== prevStep) {
503 | prevStep = currentStep
504 | graphic.update(currentStep)
505 | }
506 |
507 | window.requestAnimationFrame(render)
508 | }
509 | render()
510 |
511 | ...
512 |
513 |
514 |
Expand to see full code
515 |
516 |
517 |
518 |
519 |
So what to chose? The best library all depends on your use case, but here are some recommendations based on my findings:
520 |
521 | For highly customized stories you will want ScrollMagic or Waypoints.
522 |
523 |
524 | For the beginner you might want to check out ScrollStory, especially if you lean on jQuery.
525 |
526 |
527 | For the d3 lover you should explore graph-scroll.js, but be ready to accept the defaults or be ready to tinker.
528 |
529 |
530 | The full code is available on
531 | github . If there are any good libraries out there I missed, give me a shout
532 | @codenberg .
533 |
534 |
535 |
536 |
537 |
538 |
566 |
567 |
568 |
569 |
--------------------------------------------------------------------------------
/js/prism.js:
--------------------------------------------------------------------------------
1 | /* http://prismjs.com/download.html?themes=prism-okaidia&languages=clike+javascript */
2 | var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-(\w+)\b/i,t=0,n=_self.Prism={util:{encode:function(e){return e instanceof a?new a(e.type,n.util.encode(e.content),e.alias):"Array"===n.util.type(e)?e.map(n.util.encode):e.replace(/&/g,"&").replace(/e.length)break e;if(!(v instanceof a)){u.lastIndex=0;var b=u.exec(v),k=1;if(!b&&h&&m!=r.length-1){if(u.lastIndex=y,b=u.exec(e),!b)break;for(var w=b.index+(c?b[1].length:0),_=b.index+b[0].length,A=m,P=y,j=r.length;j>A&&_>P;++A)P+=r[A].length,w>=P&&(++m,y=P);if(r[m]instanceof a||r[A-1].greedy)continue;k=A-m,v=e.slice(y,P),b.index-=y}if(b){c&&(f=b[1].length);var w=b.index+f,b=b[0].slice(f),_=w+b.length,x=v.slice(0,w),O=v.slice(_),S=[m,k];x&&S.push(x);var N=new a(l,g?n.tokenize(b,g):b,d,b,h);S.push(N),O&&S.push(O),Array.prototype.splice.apply(r,S)}}}}}return r},hooks:{all:{},add:function(e,t){var a=n.hooks.all;a[e]=a[e]||[],a[e].push(t)},run:function(e,t){var a=n.hooks.all[e];if(a&&a.length)for(var r,i=0;r=a[i++];)r(t)}}},a=n.Token=function(e,t,n,a,r){this.type=e,this.content=t,this.alias=n,this.length=0|(a||"").length,this.greedy=!!r};if(a.stringify=function(e,t,r){if("string"==typeof e)return e;if("Array"===n.util.type(e))return e.map(function(n){return a.stringify(n,t,e)}).join("");var i={type:e.type,content:a.stringify(e.content,t,r),tag:"span",classes:["token",e.type],attributes:{},language:t,parent:r};if("comment"==i.type&&(i.attributes.spellcheck="true"),e.alias){var l="Array"===n.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(i.classes,l)}n.hooks.run("wrap",i);var o=Object.keys(i.attributes).map(function(e){return e+'="'+(i.attributes[e]||"").replace(/"/g,""")+'"'}).join(" ");return"<"+i.tag+' class="'+i.classes.join(" ")+'"'+(o?" "+o:"")+">"+i.content+""+i.tag+">"},!_self.document)return _self.addEventListener?(_self.addEventListener("message",function(e){var t=JSON.parse(e.data),a=t.language,r=t.code,i=t.immediateClose;_self.postMessage(n.highlight(r,n.languages[a],a)),i&&_self.close()},!1),_self.Prism):_self.Prism;var r=document.currentScript||[].slice.call(document.getElementsByTagName("script")).pop();return r&&(n.filename=r.src,document.addEventListener&&!r.hasAttribute("data-manual")&&("loading"!==document.readyState?window.requestAnimationFrame?window.requestAnimationFrame(n.highlightAll):window.setTimeout(n.highlightAll,16):document.addEventListener("DOMContentLoaded",n.highlightAll))),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism);
3 | Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\w\W]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:{pattern:/(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(true|false)\b/,"function":/[a-z0-9_]+(?=\()/i,number:/\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)\b/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/};
4 | Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\b/,number:/\b-?(0x[\dA-Fa-f]+|0b[01]+|0o[0-7]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|Infinity)\b/,"function":/[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*(?=\()/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*\*?|\/|~|\^|%|\.{3}/}),Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^\/])\/(?!\/)(\[.+?]|\\.|[^\/\\\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/,lookbehind:!0,greedy:!0}}),Prism.languages.insertBefore("javascript","string",{"template-string":{pattern:/`(?:\\\\|\\?[^\\])*?`/,greedy:!0,inside:{interpolation:{pattern:/\$\{[^}]+\}/,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/(