[d24f17c] | 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 | }));
|
---|