source: node_modules/zenscroll/zenscroll.js@ 65b6638

main
Last change on this file since 65b6638 was d24f17c, checked in by Aleksandar Panovski <apano77@…>, 15 months ago

Initial commit

  • Property mode set to 100644
File size: 12.3 KB
Line 
1/**
2 * Zenscroll 4.0.2
3 * https://github.com/zengabor/zenscroll/
4 *
5 * Copyright 2015–2018 Gabor Lenard
6 *
7 * This is free and unencumbered software released into the public domain.
8 *
9 * Anyone is free to copy, modify, publish, use, compile, sell, or
10 * distribute this software, either in source code form or as a compiled
11 * binary, for any purpose, commercial or non-commercial, and by any
12 * means.
13 *
14 * In jurisdictions that recognize copyright laws, the author or authors
15 * of this software dedicate any and all copyright interest in the
16 * software to the public domain. We make this dedication for the benefit
17 * of the public at large and to the detriment of our heirs and
18 * successors. We intend this dedication to be an overt act of
19 * relinquishment in perpetuity of all present and future rights to this
20 * software under copyright law.
21 *
22 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
25 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
26 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
27 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
28 * OTHER DEALINGS IN THE SOFTWARE.
29 *
30 * For more information, please refer to <http://unlicense.org>
31 *
32 */
33
34/*jshint devel:true, asi:true */
35
36/*global define, module */
37
38
39(function (root, factory) {
40 if (typeof define === "function" && define.amd) {
41 define([], factory())
42 } else if (typeof module === "object" && module.exports) {
43 module.exports = factory()
44 } else {
45 (function install() {
46 // To make sure Zenscroll can be referenced from the header, before `body` is available
47 if (document && document.body) {
48 root.zenscroll = factory()
49 } else {
50 // retry 9ms later
51 setTimeout(install, 9)
52 }
53 })()
54 }
55}(this, function () {
56 "use strict"
57
58
59 // Detect if the browser already supports native smooth scrolling (e.g., Firefox 36+ and Chrome 49+) and it is enabled:
60 var isNativeSmoothScrollEnabledOn = function (elem) {
61 return elem && "getComputedStyle" in window &&
62 window.getComputedStyle(elem)["scroll-behavior"] === "smooth"
63 }
64
65
66 // Exit if it’s not a browser environment:
67 if (typeof window === "undefined" || !("document" in window)) {
68 return {}
69 }
70
71
72 var makeScroller = function (container, defaultDuration, edgeOffset) {
73
74 // Use defaults if not provided
75 defaultDuration = defaultDuration || 999 //ms
76 if (!edgeOffset && edgeOffset !== 0) {
77 // When scrolling, this amount of distance is kept from the edges of the container:
78 edgeOffset = 9 //px
79 }
80
81 // Handling the life-cycle of the scroller
82 var scrollTimeoutId
83 var setScrollTimeoutId = function (newValue) {
84 scrollTimeoutId = newValue
85 }
86
87 /**
88 * Stop the current smooth scroll operation immediately
89 */
90 var stopScroll = function () {
91 clearTimeout(scrollTimeoutId)
92 setScrollTimeoutId(0)
93 }
94
95 var getTopWithEdgeOffset = function (elem) {
96 return Math.max(0, container.getTopOf(elem) - edgeOffset)
97 }
98
99 /**
100 * Scrolls to a specific vertical position in the document.
101 *
102 * @param {targetY} The vertical position within the document.
103 * @param {duration} Optionally the duration of the scroll operation.
104 * If not provided the default duration is used.
105 * @param {onDone} An optional callback function to be invoked once the scroll finished.
106 */
107 var scrollToY = function (targetY, duration, onDone) {
108 stopScroll()
109 if (duration === 0 || (duration && duration < 0) || isNativeSmoothScrollEnabledOn(container.body)) {
110 container.toY(targetY)
111 if (onDone) {
112 onDone()
113 }
114 } else {
115 var startY = container.getY()
116 var distance = Math.max(0, targetY) - startY
117 var startTime = new Date().getTime()
118 duration = duration || Math.min(Math.abs(distance), defaultDuration);
119 (function loopScroll() {
120 setScrollTimeoutId(setTimeout(function () {
121 // Calculate percentage:
122 var p = Math.min(1, (new Date().getTime() - startTime) / duration)
123 // Calculate the absolute vertical position:
124 var y = Math.max(0, Math.floor(startY + distance*(p < 0.5 ? 2*p*p : p*(4 - p*2)-1)))
125 container.toY(y)
126 if (p < 1 && (container.getHeight() + y) < container.body.scrollHeight) {
127 loopScroll()
128 } else {
129 setTimeout(stopScroll, 99) // with cooldown time
130 if (onDone) {
131 onDone()
132 }
133 }
134 }, 9))
135 })()
136 }
137 }
138
139 /**
140 * Scrolls to the top of a specific element.
141 *
142 * @param {elem} The element to scroll to.
143 * @param {duration} Optionally the duration of the scroll operation.
144 * @param {onDone} An optional callback function to be invoked once the scroll finished.
145 */
146 var scrollToElem = function (elem, duration, onDone) {
147 scrollToY(getTopWithEdgeOffset(elem), duration, onDone)
148 }
149
150 /**
151 * Scrolls an element into view if necessary.
152 *
153 * @param {elem} The element.
154 * @param {duration} Optionally the duration of the scroll operation.
155 * @param {onDone} An optional callback function to be invoked once the scroll finished.
156 */
157 var scrollIntoView = function (elem, duration, onDone) {
158 var elemHeight = elem.getBoundingClientRect().height
159 var elemBottom = container.getTopOf(elem) + elemHeight
160 var containerHeight = container.getHeight()
161 var y = container.getY()
162 var containerBottom = y + containerHeight
163 if (getTopWithEdgeOffset(elem) < y || (elemHeight + edgeOffset) > containerHeight) {
164 // Element is clipped at top or is higher than screen.
165 scrollToElem(elem, duration, onDone)
166 } else if ((elemBottom + edgeOffset) > containerBottom) {
167 // Element is clipped at the bottom.
168 scrollToY(elemBottom - containerHeight + edgeOffset, duration, onDone)
169 } else if (onDone) {
170 onDone()
171 }
172 }
173
174 /**
175 * Scrolls to the center of an element.
176 *
177 * @param {elem} The element.
178 * @param {duration} Optionally the duration of the scroll operation.
179 * @param {offset} Optionally the offset of the top of the element from the center of the screen.
180 * A value of 0 is ignored.
181 * @param {onDone} An optional callback function to be invoked once the scroll finished.
182 */
183 var scrollToCenterOf = function (elem, duration, offset, onDone) {
184 scrollToY(Math.max(0, container.getTopOf(elem) - container.getHeight()/2 + (offset || elem.getBoundingClientRect().height/2)), duration, onDone)
185 }
186
187 /**
188 * Changes default settings for this scroller.
189 *
190 * @param {newDefaultDuration} Optionally a new value for default duration, used for each scroll method by default.
191 * Ignored if null or undefined.
192 * @param {newEdgeOffset} Optionally a new value for the edge offset, used by each scroll method by default. Ignored if null or undefined.
193 * @returns An object with the current values.
194 */
195 var setup = function (newDefaultDuration, newEdgeOffset) {
196 if (newDefaultDuration === 0 || newDefaultDuration) {
197 defaultDuration = newDefaultDuration
198 }
199 if (newEdgeOffset === 0 || newEdgeOffset) {
200 edgeOffset = newEdgeOffset
201 }
202 return {
203 defaultDuration: defaultDuration,
204 edgeOffset: edgeOffset
205 }
206 }
207
208 return {
209 setup: setup,
210 to: scrollToElem,
211 toY: scrollToY,
212 intoView: scrollIntoView,
213 center: scrollToCenterOf,
214 stop: stopScroll,
215 moving: function () { return !!scrollTimeoutId },
216 getY: container.getY,
217 getTopOf: container.getTopOf
218 }
219
220 }
221
222
223 var docElem = document.documentElement
224 var getDocY = function () { return window.scrollY || docElem.scrollTop }
225
226 // Create a scroller for the document:
227 var zenscroll = makeScroller({
228 body: document.scrollingElement || document.body,
229 toY: function (y) { window.scrollTo(0, y) },
230 getY: getDocY,
231 getHeight: function () { return window.innerHeight || docElem.clientHeight },
232 getTopOf: function (elem) { return elem.getBoundingClientRect().top + getDocY() - docElem.offsetTop }
233 })
234
235
236 /**
237 * Creates a scroller from the provided container element (e.g., a DIV)
238 *
239 * @param {scrollContainer} The vertical position within the document.
240 * @param {defaultDuration} Optionally a value for default duration, used for each scroll method by default.
241 * Ignored if 0 or null or undefined.
242 * @param {edgeOffset} Optionally a value for the edge offset, used by each scroll method by default.
243 * Ignored if null or undefined.
244 * @returns A scroller object, similar to `zenscroll` but controlling the provided element.
245 */
246 zenscroll.createScroller = function (scrollContainer, defaultDuration, edgeOffset) {
247 return makeScroller({
248 body: scrollContainer,
249 toY: function (y) { scrollContainer.scrollTop = y },
250 getY: function () { return scrollContainer.scrollTop },
251 getHeight: function () { return Math.min(scrollContainer.clientHeight, window.innerHeight || docElem.clientHeight) },
252 getTopOf: function (elem) { return elem.offsetTop }
253 }, defaultDuration, edgeOffset)
254 }
255
256
257 // Automatic link-smoothing on achors
258 // Exclude IE8- or when native is enabled or Zenscroll auto- is disabled
259 if ("addEventListener" in window && !window.noZensmooth && !isNativeSmoothScrollEnabledOn(document.body)) {
260
261 var isHistorySupported = "history" in window && "pushState" in history
262 var isScrollRestorationSupported = isHistorySupported && "scrollRestoration" in history
263
264 // On first load & refresh make sure the browser restores the position first
265 if (isScrollRestorationSupported) {
266 history.scrollRestoration = "auto"
267 }
268
269 window.addEventListener("load", function () {
270
271 if (isScrollRestorationSupported) {
272 // Set it to manual
273 setTimeout(function () { history.scrollRestoration = "manual" }, 9)
274 window.addEventListener("popstate", function (event) {
275 if (event.state && "zenscrollY" in event.state) {
276 zenscroll.toY(event.state.zenscrollY)
277 }
278 }, false)
279 }
280
281 // Add edge offset on first load if necessary
282 // This may not work on IE (or older computer?) as it requires more timeout, around 100 ms
283 if (window.location.hash) {
284 setTimeout(function () {
285 // Adjustment is only needed if there is an edge offset:
286 var edgeOffset = zenscroll.setup().edgeOffset
287 if (edgeOffset) {
288 var targetElem = document.getElementById(window.location.href.split("#")[1])
289 if (targetElem) {
290 var targetY = Math.max(0, zenscroll.getTopOf(targetElem) - edgeOffset)
291 var diff = zenscroll.getY() - targetY
292 // Only do the adjustment if the browser is very close to the element:
293 if (0 <= diff && diff < 9 ) {
294 window.scrollTo(0, targetY)
295 }
296 }
297 }
298 }, 9)
299 }
300
301 }, false)
302
303 // Handling clicks on anchors
304 var RE_noZensmooth = new RegExp("(^|\\s)noZensmooth(\\s|$)")
305 window.addEventListener("click", function (event) {
306 var anchor = event.target
307 while (anchor && anchor.tagName !== "A") {
308 anchor = anchor.parentNode
309 }
310 // Let the browser handle the click if it wasn't with the primary button, or with some modifier keys:
311 if (!anchor || event.which !== 1 || event.shiftKey || event.metaKey || event.ctrlKey || event.altKey) {
312 return
313 }
314 // Save the current scrolling position so it can be used for scroll restoration:
315 if (isScrollRestorationSupported) {
316 var historyState = history.state && typeof history.state === "object" ? history.state : {}
317 historyState.zenscrollY = zenscroll.getY()
318 try {
319 history.replaceState(historyState, "")
320 } catch (e) {
321 // Avoid the Chrome Security exception on file protocol, e.g., file://index.html
322 }
323 }
324 // Find the referenced ID:
325 var href = anchor.getAttribute("href") || ""
326 if (href.indexOf("#") === 0 && !RE_noZensmooth.test(anchor.className)) {
327 var targetY = 0
328 var targetElem = document.getElementById(href.substring(1))
329 if (href !== "#") {
330 if (!targetElem) {
331 // Let the browser handle the click if the target ID is not found.
332 return
333 }
334 targetY = zenscroll.getTopOf(targetElem)
335 }
336 event.preventDefault()
337 // By default trigger the browser's `hashchange` event...
338 var onDone = function () { window.location = href }
339 // ...unless there is an edge offset specified
340 var edgeOffset = zenscroll.setup().edgeOffset
341 if (edgeOffset) {
342 targetY = Math.max(0, targetY - edgeOffset)
343 if (isHistorySupported) {
344 onDone = function () { history.pushState({}, "", href) }
345 }
346 }
347 zenscroll.toY(targetY, null, onDone)
348 }
349 }, false)
350
351 }
352
353
354 return zenscroll
355
356
357}));
Note: See TracBrowser for help on using the repository browser.