├── package.json ├── asset ├── css │ ├── linechart.css │ └── toggle.css └── js │ └── linechart.js ├── README.md ├── LICENSE ├── scrolly-slides.js └── index.html /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scrolly-slides", 3 | "version": "1.0.1", 4 | "description": "", 5 | "keywords": [ 6 | "scroll", 7 | "slides", 8 | "scrollytelling" 9 | ], 10 | "author": "Mariko Kosaka", 11 | "license": "MIT" 12 | } 13 | -------------------------------------------------------------------------------- /asset/css/linechart.css: -------------------------------------------------------------------------------- 1 | svg { 2 | font: 10px sans-serif; 3 | } 4 | 5 | .line { 6 | fill: none; 7 | stroke: hotpink; 8 | stroke-width: 4px; 9 | } 10 | 11 | .axis path, 12 | .axis line { 13 | fill: none; 14 | stroke: #000; 15 | shape-rendering: crispEdges; 16 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scrolly Slide 2 | Presentation slides in "Scrollytelling" format 3 | 4 | ## Get Started 5 | 6 | ### Demo: http://kosamari.github.io/scrolly-slides/ 7 | 8 | Also, take a look at `index.html` and `scrolly-slides.js` to see how it's built. 9 | 10 | ## Resources 11 | 12 | ### Learn more about Scrollytelling 13 | 14 | - [SO YOU THINK YOU CAN SCROLL](https://www.youtube.com/watch?v=fYQGgaE_b4I) by Jim Vallandingham 15 | - [graph-scroll.js](https://1wheel.github.io/graph-scroll/) by Adam Pearce 16 | 17 | ### Don't wanna scroll? 18 | 19 | - [SimpleSlides](https://github.com/jennschiffer/SimpleSlides) by Jenn Schiffer 20 | 21 | ## Changelog 22 | #### v1.1 23 | 24 | - fix opacity focus issue for very 1st element 25 | 26 | #### v1.0 27 | 28 | - the original commit -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016, Mariko Kosaka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /asset/js/linechart.js: -------------------------------------------------------------------------------- 1 | var data = [] 2 | 3 | var margin = {top: 20, right: 20, bottom: 20, left: 40}, 4 | width = 500 - margin.left - margin.right, 5 | height = 400 - margin.top - margin.bottom 6 | 7 | var x = d3.scale.linear() 8 | .domain([0, 255]) 9 | .range([0, width]) 10 | 11 | var y = d3.scale.linear() 12 | .domain([0, 255]) 13 | .range([height, 0]) 14 | 15 | var line = d3.svg.line() 16 | .x(function(d, i) { return x(d.x) }) 17 | .y(function(d, i) { return y(d.y) }) 18 | 19 | var svg = d3.select('#chart').append('svg') 20 | .attr('width', width + margin.left + margin.right) 21 | .attr('height', height + margin.top + margin.bottom) 22 | .append('g') 23 | .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') 24 | 25 | svg.append('g') 26 | .attr('class', 'x axis') 27 | .attr('transform', 'translate(0,' + y(0) + ')') 28 | .call(d3.svg.axis().scale(x).orient('bottom')) 29 | 30 | svg.append('g') 31 | .attr('class', 'y axis') 32 | .call(d3.svg.axis().scale(y).orient('left')) 33 | 34 | 35 | var path = svg.append('path') 36 | var drawn = false 37 | function draw() { 38 | drawn = true 39 | data = [{x:0,y:0},{x:64, y:64,},{x:192,y:192},{x:255,y:255}] 40 | path.datum(data) 41 | path.attr('class', 'line') 42 | .attr('d', line) 43 | } 44 | 45 | function update() { 46 | if(!drawn){ 47 | draw() 48 | } 49 | data[1] = {x:64, y:255} 50 | data[2] = {x:192, y:0} 51 | path.transition() 52 | .duration(500) 53 | .ease('linear') 54 | .attr('d', line) 55 | } 56 | 57 | 58 | window.addEventListener('draw', draw) 59 | window.addEventListener('update', update) -------------------------------------------------------------------------------- /asset/css/toggle.css: -------------------------------------------------------------------------------- 1 | /* ============================================================ 2 | COMMON 3 | ============================================================ */ 4 | .cmn-toggle { 5 | position: absolute; 6 | margin-left: -9999px; 7 | visibility: hidden; 8 | } 9 | .cmn-toggle + label { 10 | display: block; 11 | position: relative; 12 | cursor: pointer; 13 | outline: none; 14 | -webkit-user-select: none; 15 | -moz-user-select: none; 16 | -ms-user-select: none; 17 | user-select: none; 18 | } 19 | 20 | /* ============================================================ 21 | SWITCH 2 - ROUND FLAT 22 | ============================================================ */ 23 | input.cmn-toggle-round-flat + label { 24 | padding: 2px; 25 | width: 120px; 26 | height: 60px; 27 | background-color: #dddddd; 28 | -webkit-border-radius: 60px; 29 | -moz-border-radius: 60px; 30 | -ms-border-radius: 60px; 31 | -o-border-radius: 60px; 32 | border-radius: 60px; 33 | -webkit-transition: background 0.4s; 34 | -moz-transition: background 0.4s; 35 | -o-transition: background 0.4s; 36 | transition: background 0.4s; 37 | } 38 | input.cmn-toggle-round-flat + label:before, input.cmn-toggle-round-flat + label:after { 39 | display: block; 40 | position: absolute; 41 | content: ""; 42 | } 43 | input.cmn-toggle-round-flat + label:before { 44 | top: 2px; 45 | left: 2px; 46 | bottom: 2px; 47 | right: 2px; 48 | background-color: #fff; 49 | -webkit-border-radius: 60px; 50 | -moz-border-radius: 60px; 51 | -ms-border-radius: 60px; 52 | -o-border-radius: 60px; 53 | border-radius: 60px; 54 | -webkit-transition: background 0.4s; 55 | -moz-transition: background 0.4s; 56 | -o-transition: background 0.4s; 57 | transition: background 0.4s; 58 | } 59 | input.cmn-toggle-round-flat + label:after { 60 | top: 4px; 61 | left: 4px; 62 | bottom: 4px; 63 | width: 52px; 64 | background-color: #dddddd; 65 | -webkit-border-radius: 52px; 66 | -moz-border-radius: 52px; 67 | -ms-border-radius: 52px; 68 | -o-border-radius: 52px; 69 | border-radius: 52px; 70 | -webkit-transition: margin 0.4s, background 0.4s; 71 | -moz-transition: margin 0.4s, background 0.4s; 72 | -o-transition: margin 0.4s, background 0.4s; 73 | transition: margin 0.4s, background 0.4s; 74 | } 75 | input.cmn-toggle-round-flat:checked + label { 76 | background-color: #8ce196; 77 | } 78 | input.cmn-toggle-round-flat:checked + label:after { 79 | margin-left: 60px; 80 | background-color: #8ce196; 81 | } 82 | .switch { 83 | display: table-cell; 84 | vertical-align: middle; 85 | padding: 10px; 86 | } -------------------------------------------------------------------------------- /scrolly-slides.js: -------------------------------------------------------------------------------- 1 | function Scrolly () { 2 | var articleWidth = 340 3 | var sectionPositions = [] 4 | var currentSection = 0 5 | var $el = { 6 | window: $(window), 7 | article: $('.scrolly-article'), 8 | slides: $('.scrolly-slide'), 9 | sections: $('.scrolly-section') 10 | } 11 | 12 | /* 13 | * UTILITIES 14 | */ 15 | 16 | function findClosest (array, target) { 17 | return array.map(function (val, i) { 18 | return [val, i, Math.abs(val - target)] 19 | }).reduce(function (memo, val) { 20 | return (memo[2] < val[2]) ? memo : val 21 | }) 22 | } 23 | 24 | // Scrolly Slide adds page number hash 25 | function readURL () { 26 | var id = window.location.hash.split('#')[1] 27 | return id ? parseInt(id, 10) : 0 28 | } 29 | 30 | function writeURL (loc) { 31 | window.location.hash = loc 32 | } 33 | 34 | /* 35 | * EVENT HANDLERS 36 | */ 37 | 38 | function handleWindowResize () { 39 | // resize width of slide elements 40 | var margin = parseInt($el.slides.css('margin-left'), 10) 41 | var paddingL = parseInt($el.slides.css('padding-left'), 10) 42 | var paddingR = parseInt($el.slides.css('padding-right'), 10) 43 | 44 | if (margin) { // article view is visible 45 | $el.slides.css({width: window.innerWidth - margin - paddingL - paddingR}) 46 | } else { 47 | $el.slides.css({width: window.innerWidth - paddingL - paddingR}) 48 | } 49 | 50 | // add bottom margin to the last section 51 | var $lastel = $('.scrolly-section-last') 52 | var marginBottom = window.innerHeight - ($lastel[0].getBoundingClientRect().bottom - $lastel[0].getBoundingClientRect().top) 53 | 54 | $lastel.css({'margin-bottom': marginBottom + 'px'}) 55 | } 56 | 57 | function handleScroll (newSectionIndex) { 58 | // get data of current section from data attributes 59 | var $sctionEl = $($el.sections[newSectionIndex]) 60 | var slideId = $sctionEl.data('slideId') 61 | var fragmentIds = $sctionEl.data('fragmentIds') 62 | var eventName = $sctionEl.data('eventName') 63 | 64 | // transform fragmentIds to an Array 65 | if (fragmentIds) { 66 | fragmentIds = fragmentIds.toString().split(',').map(function (e) { return parseInt(e, 10) }) 67 | } 68 | 69 | // update current section counter 70 | currentSection = newSectionIndex 71 | 72 | // change page hash 73 | writeURL(newSectionIndex) 74 | 75 | // update opacity of sections (current section is set to opacity 1) 76 | $el.sections.each(function (index, sectionEl) { 77 | var el = $(sectionEl) 78 | if (index === currentSection) { 79 | return el.css({'opacity': 1}) 80 | } 81 | return el.css({'opacity': 0.2}) 82 | }) 83 | 84 | // update each slide element based on data attributes of section element 85 | $el.slides.each(function (index, slideEl) { 86 | var el = $(slideEl) 87 | 88 | // Set visiblity of slide element 89 | if(slideId === el.data('slideId')){ 90 | // trigger Event 91 | if (eventName) { 92 | window.dispatchEvent(new Event(eventName)) 93 | } 94 | 95 | // check fragments to render 96 | if (fragmentIds) { 97 | el.children().each(function (index, cel) { 98 | var $cel = $(cel) 99 | fragmentIds.indexOf($cel.data('fragmentId')) >= 0 100 | ? $cel.show() 101 | : $cel.hide() 102 | }) 103 | } 104 | return el.show() 105 | } 106 | return el.hide() 107 | }) 108 | } 109 | 110 | /* 111 | * PUBLIC METHODS 112 | */ 113 | 114 | // Toggle article view 115 | this.toggleArticle = function (show) { 116 | var width = show ? articleWidth : 0 117 | var paddingL = parseInt($el.slides.css('padding-left'), 10) 118 | var paddingR = parseInt($el.slides.css('padding-right'), 10) 119 | $el.slides.animate({ 120 | 'margin-left': width + 'px', 121 | 'width': (window.innerWidth - width - paddingL - paddingR) + 'px' 122 | }) 123 | } 124 | 125 | // init method 126 | this.init = function (opt) { 127 | opt = opt || {} 128 | 129 | // set width of article 130 | $el.article.css({width: articleWidth + 'px'}) 131 | 132 | // show/hide article based on option passed 133 | var width = opt.show_article ? articleWidth : 0 134 | $el.slides.css({'margin-left': width + 'px'}) 135 | 136 | // create position map of sections 137 | var startPos 138 | $el.sections.each(function (i, el) { 139 | var top = el.getBoundingClientRect().top 140 | if (i === 0) { 141 | startPos = top 142 | } 143 | sectionPositions.push(top - startPos) 144 | }) 145 | 146 | // hide everything except very 1st .scrolly-slide element 147 | $el.slides.each(function (index, slideEl) { 148 | if (index) { 149 | $(slideEl).hide() 150 | } 151 | }) 152 | 153 | // bind window resize event 154 | handleWindowResize() 155 | $el.window.on('resize', function () { 156 | handleWindowResize() 157 | }) 158 | 159 | 160 | // bind scroll event 161 | $el.window.scroll(function () { 162 | var position = window.pageYOffset - 10 163 | var newSectionIndex = findClosest(sectionPositions, position)[1] 164 | if (currentSection !== newSectionIndex) { 165 | handleScroll(newSectionIndex) 166 | } 167 | }) 168 | 169 | // bind key event 170 | $el.window.keydown(function (e) { 171 | var prev = currentSection - 1 172 | var next = currentSection + 1 173 | switch (e.keyCode) { 174 | case 39: // right arrow 175 | e.preventDefault() 176 | if (next < sectionPositions.length) { 177 | window.scrollTo(0, sectionPositions[next]) 178 | } 179 | break 180 | case 40: // down arrow 181 | e.preventDefault() 182 | if (next < sectionPositions.length) { 183 | window.scrollTo(0, sectionPositions[next]) 184 | } 185 | break 186 | case 34: // page down 187 | e.preventDefault() 188 | if (next < sectionPositions.length) { 189 | window.scrollTo(0, sectionPositions[next]) 190 | } 191 | break 192 | case 32: // space 193 | e.preventDefault() 194 | if (next < sectionPositions.length) { 195 | window.scrollTo(0, sectionPositions[next]) 196 | } 197 | break 198 | case 37: // left arrow 199 | e.preventDefault() 200 | if (prev >= 0) { 201 | window.scrollTo(0, sectionPositions[prev]) 202 | } 203 | break 204 | case 38: // up arrow 205 | e.preventDefault() 206 | if (prev >= 0) { 207 | window.scrollTo(0, sectionPositions[prev]) 208 | } 209 | break 210 | case 33: // page up 211 | e.preventDefault() 212 | if (prev >= 0) { 213 | window.scrollTo(0, sectionPositions[prev]) 214 | } 215 | break 216 | default: return 217 | } 218 | }) 219 | 220 | // Lastly, look up page hash & scroll to appropriate location 221 | var url = readURL() 222 | if (!url) { handleScroll(0) } 223 | window.scrollTo(0, sectionPositions[readURL()]) 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Scrolly Slides 6 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 90 |
91 | 92 |
93 |

Article Panel

94 |

This is article panel. In regular scrollytelling this is where most of text is displayed. For presentation slide, you can use this space for speaker notes.

95 |

96 |

You can display this view by calling the toggle method scrolly.toggleArticle(true). To hide this view, pass false.

97 |
98 | 99 | 100 |
101 |

Section Element

102 |

Each section in article panel should have scrolly-section class and data-slide-id attribute corresponding to the same attribute in slide element.

103 |

Mark up for this section looks like this

104 |
<section class="scrolly-section" data-slide-id="myslideid">
105 |   <h1>How to controll</h1>
106 | </section>
107 |

When each section is active (clocest to top), corresponding slide element will be displaced.

108 |
109 | 110 | 111 |
112 |

Fragment

113 |

You can specify which fragmented element to be displayed in a slide by setting data-fragment-ids

114 |
115 | 116 |
117 |

For exampe, I have data-fragment-ids="1,2" set for this section.

118 |
119 | 120 |
121 |

Pretty cool, right ?

122 |
123 | 124 | 125 |
126 |

Event

127 |

You can use data-event-name attribute to trigger new Event("<-event-name->").

128 |

For example, once next section become active, it"ll trigger update event. I have D3 event listener set up to update the chart.

129 |
130 | 131 |
132 |

Booon...

133 |

Event sent

134 |
135 | 136 | 137 |
138 |

Last section

139 |

make sure your last section has .scrolly-section-last class added. 140 | This will add necessary margin at the bottom.

141 |
<section class="scrolly-section-last scrolly-section" data-slide-id="4">
142 |   <h1>Last section</h1>
143 | </section>
144 |
145 | 146 |
147 | 148 | 149 | 153 | 154 |
155 |

Scrolly Slides

156 |

Presentation slides in "Scrollytelling" format

157 |

Toggle this to see speaker notes! 158 |

159 | 160 | 161 |
162 |

163 |
164 | 165 | 166 |
167 |

Slide Element

168 |

Each slide element must have scrolly-slide class and data-slide-id attribute.

169 |

data-slide-id can be any string, but must be same as corresponding section element in article view. 170 |

171 |

Markup for this element looks like this. 172 |

173 |
<div class="scrolly-slide" data-slide-id="myslideid">
174 |   <h3>Necessary class and attributes</h3>
175 | </div>
176 |
177 | 178 | 179 |
180 | 181 |

Fragment

182 |

You can set fragment with data-fragment-id attribute.

183 |
184 |

185 | just like ... this 186 |

187 |
<div class="scrolly-slide" data-slide-id="fragment">
188 |   <span  data-fragment-id="1">
189 |     <h3>Fragment</h3>
190 |   </span>
191 |   <p data-fragment-id="2">
192 |     2nd fragment
193 |   </p>
194 |   <p data-fragment-id="3">
195 |     3rd fragment
196 |   </p>
197 | </div>
198 |
199 | 200 | 201 |
202 |

This Wonderful Chart

203 |
204 |
Updated by event
205 |
206 | 207 | 208 |
209 |

Get started

210 |
Code: https://github.com/kosamari/scrolly-slides
211 |
Made by @kosamari
212 |
License: MIT
213 | 214 |
Learn more about Scrollytelling
215 |
  • SO YOU THINK YOU CAN SCROLL by Jim Vallandingham
  • 216 |
  • graph-scroll.js by Adam Pearce
  • 217 | 218 |
    Don't wanna scroll?
    219 |
  • SimpleSlides by Jenn Schiffer
  • 220 |
    221 | 222 | 223 | 224 | 225 | 226 | 227 | 231 | 232 | --------------------------------------------------------------------------------