diff --git a/README.md b/README.md index 7355337..95b75ae 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,8 @@ Smooth animated scrolling. Move elements into view, or scroll to any vertical position. -1.1 kilobyte of vanilla JavaScript. No dependencies. +1.2 kilobyte of vanilla JavaScript. No dependencies. + ## About @@ -21,15 +22,17 @@ Zenscroll is a vanilla JavaScript module that enables animated vertical scrollin Features: -- Smooth animated scrolling, using the browser’s built-in smooth-behavior if it’s enabled. +- Smooth animated scrolling. - Automatic smooth-scolling on links within the same page. +- Works even if the Back and Forward buttons are used (on compatible browsers). - Scroll to the top of a specific element. - Scrolling an element into view, making sure both top & bottom are visible, if possible. - Scroll to an element and center it on the screen. - Customize the duration of the individual scroll operations. - If you provide a callback function it will be executed when the scrolling is done. - Specify the spacing between the element and the edge of the screen (e.g., for fixed navigation bars and footers). -- Just 1.1 kilobyte minimized & gzipped. +- Uses the browser’s built-in smooth-behavior if it’s enabled. +- Just 1.2 kilobyte minimized & gzipped. - No dependencies. Full support tested and works under: @@ -52,6 +55,7 @@ Limited support (programmatic animated scroll in document) tested and works unde - Internet Explorer 6+ - iOS Safari 3+ + ## Getting Started ### Installing Zenscroll @@ -78,7 +82,7 @@ If you want to leverage the native smooth-scrolling by the browser (currently av body, .smooth-container { scroll-behavior: smooth } ```` -In this case Zenscroll will use the browser’s built-in support for all scroll functions. However, note that if you use the native smooth-scrolling then you loose the finer control options that Zenscroll offers: the speed of the animation, and the edge offset for links within the page. Only set this CSS property on the `body` or on the elements if you don’t need this level of control. +If this is set and the browser supports it, Zenscroll will use the browser’s built-in support for smooth-scrolling. However, note that if you use the native smooth-scrolling then you loose the finer control options that Zenscroll offers: the speed settings of the animation, and the edge offset for links within the page. Only set this CSS property on the `body` or on the elements if you don’t need this level of control. ### Disabling automatic smoothing on local links @@ -93,12 +97,13 @@ If you want to use Zenscroll programmatically but you don’t need the automatic (I consider this a rare scenario that’s why I keep the default behavior of installing the event handler.) + ## How to use ### 1. Smooth scroll within your page -If Zenscroll is included in your page it will automatically animate the scrolling to anchors on the same page. +If Zenscroll is included in your page it will automatically animate the scrolling to anchors on the same page. However, automatic smooth scrolling within the same page is not enabled in these two cases: @@ -106,8 +111,10 @@ However, automatic smooth scrolling within the same page is not enabled in these 2. If the `scroll-behavior` CSS property is set to `smooth` on the `body` (see [above](#enablingnativesmooth-scrollinginthebrowser)). In this case the browser is already smooth-scrolling within the same page. If you want only some of the links to be excluded from the automatic smoothing then start with the path of the page. E.g., instead of writing `` use ``. + +The scroll is also animated when the Back and Forward buttons are used. (Note that it remembers the vertical scroll position but it doesn’t calculate changes caused by browser window resizing or collapsing/expanding elements, etc.) This functionality requires browser support for `history.scrollRestoration` which is available in Chrome 46+ and Firefox 46+. WebKit already has a [ticket](https://bugs.webkit.org/show_bug.cgi?id=147782) for it, in the meantime you can use a polyfill for Safari, like [scroll-restoration-polyfill](https://github.com/bfred-it/scroll-restoration-polyfill). -Automatic smooth-scrolling works also with content you dynamically load via AJAX, as Zenscroll uses a generic click handler. Internal links are intentionally not added to the history to save the users from having to hit the Back button too many times afterwards. Since the automatic smooth-scrolling is implemented a progressive enhancement, all internal links still work even in old browsers. +Automatic smooth-scrolling works also with content you dynamically load via AJAX, as Zenscroll uses a generic click handler. Since the automatic smooth-scrolling is implemented a progressive enhancement, internal links work in older browsers as well. ### 2. Scroll to the top of an element @@ -223,7 +230,7 @@ myScroller.intoView(target) You can provide a callback function to all four scroll functions, which is executed when the scroll operation is finished. For example, you change some UI elements but first you want to make sure that the relevant elements are visible. -If you look at the code examples above under the previous point, [7. Scroll inside a scrollable DIV](#7.scrollinsideascrollablediv) they are actually implemented like this: +If you look at the code examples above under the previous point, [Scroll inside a scrollable DIV](#7.scrollinsideascrollablediv) they are actually implemented like this: ````js // Last line of example 1: @@ -292,12 +299,15 @@ To stop the current scrolling: zenscroll.stop() ```` + ## License [Public Domain](http://unlicense.org). You can do with it whatever you want and I am not responsible for anything. + ## Other projects by me: - [Zenfonts](https://github.com/zengabor/zenfonts), a tiny JavaScript helper for @font-face web font loading. - [Zenvite.com](http://zenvite.com/): Create invitation pages & get everybody on the same page. + diff --git a/package.json b/package.json index d7a0c3f..bea0c53 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zenscroll", - "version": "3.2.3", + "version": "3.3.0", "description": "A module to smooth-scroll web pages and containers (DIVs)", "main": "zenscroll.js", "files": ["zenscroll.js", "zenscroll-min.js"], diff --git a/zenscroll-min.js b/zenscroll-min.js index 0204e8f..6f4895a 100644 --- a/zenscroll-min.js +++ b/zenscroll-min.js @@ -1 +1 @@ -!function(t,e){"function"==typeof define&&define.amd?define([],e()):"object"==typeof module&&module.exports?module.exports=e():t.zenscroll=e()}(this,function(){"use strict";if("undefined"==typeof window||!("document"in window))return{};var t=function(t,e,n){e=e||999,n||0===n||(n=9);var o,i=function(t){o=t},r=document.documentElement,c=function(){return"getComputedStyle"in window&&"smooth"===window.getComputedStyle(t?t:document.body)["scroll-behavior"]},u=function(){return t?t.scrollTop:window.scrollY||r.scrollTop},f=function(){return t?Math.min(t.offsetHeight,window.innerHeight):window.innerHeight||r.clientHeight},l=function(e){return t?e.offsetTop:e.getBoundingClientRect().top+u()-r.offsetTop},a=function(){clearTimeout(o),i(0)},d=function(n,o,l){if(a(),c())(t||window).scrollTo(0,n),l&&l();else{var d=u(),s=Math.max(n,0)-d;o=o||Math.min(Math.abs(s),e);var m=(new Date).getTime();!function e(){i(setTimeout(function(){var n=Math.min(((new Date).getTime()-m)/o,1),i=Math.max(Math.floor(d+s*(n<.5?2*n*n:n*(4-2*n)-1)),0);t?t.scrollTop=i:window.scrollTo(0,i),n<1&&f()+i<(t||r).scrollHeight?e():(setTimeout(a,99),l&&l())},9))}()}},s=function(t,e,o){d(l(t)-n,e,o)},m=function(t,e,o){var i=t.getBoundingClientRect().height,r=l(t),c=r+i,a=f(),m=u(),h=m+a;r-na?s(t,e,o):c+n>h?d(c-a+n,e,o):o&&o()},h=function(t,e,n,o){d(Math.max(l(t)-f()/2+(n||t.getBoundingClientRect().height/2),0),e,o)},w=function(t,o){t&&(e=t),(0===o||o)&&(n=o)};return{setup:w,to:s,toY:d,intoView:m,center:h,stop:a,moving:function(){return!!o}}},e=t();if("addEventListener"in window&&"smooth"!==document.body.style.scrollBehavior&&!window.noZensmooth){var n=function(t){try{history.replaceState({},"",window.location.href.split("#")[0]+t)}catch(t){}};window.addEventListener("click",function(t){for(var o=t.target;o&&"A"!==o.tagName;)o=o.parentNode;if(!(!o||1!==t.which||t.shiftKey||t.metaKey||t.ctrlKey||t.altKey)){var i=o.getAttribute("href")||"";if(0===i.indexOf("#"))if("#"===i)t.preventDefault(),e.toY(0),n("");else{var r=o.hash.substring(1),c=document.getElementById(r);c&&(t.preventDefault(),e.to(c),n("#"+r))}}},!1)}return{createScroller:t,setup:e.setup,to:e.to,toY:e.toY,intoView:e.intoView,center:e.center,stop:e.stop,moving:e.moving}}); \ No newline at end of file +!function(t,e){"function"==typeof define&&define.amd?define([],e()):"object"==typeof module&&module.exports?module.exports=e():t.zenscroll=e()}(this,function(){"use strict";var t=function(t){return"getComputedStyle"in window&&"smooth"===window.getComputedStyle(t)["scroll-behavior"]};if("undefined"==typeof window||!("document"in window))return{};var e=function(e,n,o){n=n||999,o||0===o||(o=9);var i,r=function(t){i=t},c=document.documentElement,u=function(){return e?e.scrollTop:window.scrollY||c.scrollTop},l=function(){return e?Math.min(e.offsetHeight,window.innerHeight):window.innerHeight||c.clientHeight},a=function(t){return e?t.offsetTop:t.getBoundingClientRect().top+u()-c.offsetTop},s=function(){clearTimeout(i),r(0)},f=function(o,i,a){if(s(),t(e?e:document.body))(e||window).scrollTo(0,o),a&&a();else{var f=u(),d=Math.max(o,0)-f;i=i||Math.min(Math.abs(d),n);var h=(new Date).getTime();!function t(){r(setTimeout(function(){var n=Math.min(((new Date).getTime()-h)/i,1),o=Math.max(Math.floor(f+d*(n<.5?2*n*n:n*(4-2*n)-1)),0);e?e.scrollTop=o:window.scrollTo(0,o),n<1&&l()+o<(e||c).scrollHeight?t():(setTimeout(s,99),a&&a())},9))}()}},d=function(t,e,n){var i=a(t)-o;return f(i,e,n),i},h=function(t,e,n){var i=t.getBoundingClientRect().height,r=a(t),c=r+i,s=l(),h=u(),w=h+s;r-os?d(t,e,n):c+o>w?f(c-s+o,e,n):n&&n()},w=function(t,e,n,o){f(Math.max(a(t)-l()/2+(n||t.getBoundingClientRect().height/2),0),e,o)},m=function(t,e){t&&(n=t),(0===e||e)&&(o=e)};return{setup:m,to:d,toY:f,intoView:h,center:w,stop:s,moving:function(){return!!i},getY:u}},n=e();if("addEventListener"in window&&!t(document.body)&&!window.noZensmooth){"scrollRestoration"in history&&(history.scrollRestoration="manual",window.addEventListener("popstate",function(t){t.state&&t.state.scrollY&&n.toY(t.state.scrollY)},!1));var o=function(t,e){try{history.replaceState({scrollY:n.getY()},""),history.pushState({scrollY:e},"",t)}catch(t){}};window.addEventListener("click",function(t){for(var e=t.target;e&&"A"!==e.tagName;)e=e.parentNode;if(!(!e||1!==t.which||t.shiftKey||t.metaKey||t.ctrlKey||t.altKey)){var i=e.getAttribute("href")||"";if(0===i.indexOf("#"))if("#"===i)t.preventDefault(),n.toY(0),o(window.location.href.split("#")[0],0);else{var r=e.hash.substring(1),c=document.getElementById(r);c&&(t.preventDefault(),o("#"+r,n.to(c)))}}},!1)}return{createScroller:e,setup:n.setup,to:n.to,toY:n.toY,intoView:n.intoView,center:n.center,stop:n.stop,moving:n.moving}}); \ No newline at end of file diff --git a/zenscroll.js b/zenscroll.js index 1fc9006..aaf7a46 100644 --- a/zenscroll.js +++ b/zenscroll.js @@ -1,5 +1,5 @@ /** - * Zenscroll 3.2.3 + * Zenscroll 3.3.0 * https://github.com/zengabor/zenscroll/ * * Copyright 2015–2016 Gabor Lenard @@ -46,6 +46,12 @@ } }(this, function () { "use strict" + + // Detect if the browser already supports native smooth scrolling (e.g., Firefox 36+ and Chrome 49+) and it is enabled: + var isNativeSmoothScrollEnabledOn = function (elem) { + return ("getComputedStyle" in window) && + window.getComputedStyle(elem)["scroll-behavior"] === "smooth" + } // Exit if it’s not a browser environment: if (typeof window === "undefined" || !("document" in window)) { @@ -66,12 +72,6 @@ } var docElem = document.documentElement - // Detect if the browser already supports native smooth scrolling (e.g., Firefox 36+ and Chrome 49+) and it is enabled: - var nativeSmoothScrollEnabled = function () { - return ("getComputedStyle" in window) && - window.getComputedStyle(scrollContainer ? scrollContainer : document.body)["scroll-behavior"] === "smooth" - } - var getScrollTop = function () { if (scrollContainer) { return scrollContainer.scrollTop @@ -111,10 +111,11 @@ * @param {duration} Optionally the duration of the scroll operation. * If 0 or not provided it is automatically calculated based on the * distance and the default duration. + * @param {onDone} Callback function to be invoken once the scroll finishes. */ var scrollToY = function (endY, duration, onDone) { stopScroll() - if (nativeSmoothScrollEnabled()) { + if (isNativeSmoothScrollEnabledOn(scrollContainer ? scrollContainer : document.body)) { (scrollContainer || window).scrollTo(0, endY) if (onDone) { onDone() @@ -152,9 +153,13 @@ * @param {elem} The element. * @param {duration} Optionally the duration of the scroll operation. * A value of 0 is ignored. + * @param {onDone} Callback function to be invoken once the scroll finishes. + * @returns {endY} The new vertical scoll position that will be valid once the scroll finishes. */ var scrollToElem = function (elem, duration, onDone) { - scrollToY(getRelativeTopOf(elem) - edgeOffset, duration, onDone) + var endY = getRelativeTopOf(elem) - edgeOffset + scrollToY(endY, duration, onDone) + return endY } /** @@ -163,6 +168,7 @@ * @param {elem} The element. * @param {duration} Optionally the duration of the scroll operation. * A value of 0 is ignored. + * @param {onDone} Callback function to be invoken once the scroll finishes. */ var scrollIntoView = function (elem, duration, onDone) { var elemHeight = elem.getBoundingClientRect().height @@ -189,6 +195,7 @@ * @param {duration} Optionally the duration of the scroll operation. * @param {offset} Optionally the offset of the top of the element from the center of the screen. * A value of 0 is ignored. + * @param {onDone} Callback function to be invoken once the scroll finishes. */ var scrollToCenterOf = function (elem, duration, offset, onDone) { scrollToY( @@ -224,7 +231,8 @@ intoView: scrollIntoView, center: scrollToCenterOf, stop: stopScroll, - moving: function () { return !!scrollTimeoutId } + moving: function () { return !!scrollTimeoutId }, + getY: getScrollTop } } @@ -233,14 +241,23 @@ var defaultScroller = createScroller() // Create listeners for the documentElement only & exclude IE8- - if ("addEventListener" in window && document.body.style.scrollBehavior !== "smooth" && !window.noZensmooth) { - var replaceUrl = function (hash) { + if ("addEventListener" in window && !(isNativeSmoothScrollEnabledOn(document.body) || window.noZensmooth)) { + if ("scrollRestoration" in history) { + history.scrollRestoration = "manual" + window.addEventListener("popstate", function (event) { + if (event.state && "scrollY" in event.state) { + defaultScroller.toY(event.state.scrollY) + } + }, false) + } + var replaceUrl = function (hash, newY) { try { - history.replaceState({}, "", window.location.href.split("#")[0] + hash) + history.replaceState({scrollY:defaultScroller.getY()}, "") // remember the scroll position before scrolling + history.pushState({scrollY:newY}, "", window.location.href.split("#")[0] + hash) // remember the new scroll position (which will be after scrolling) } catch (e) { // To avoid the Security exception in Chrome when the page was opened via the file protocol, e.g., file://index.html } - } + } window.addEventListener("click", function (event) { var anchor = event.target while (anchor && anchor.tagName !== "A") { @@ -255,14 +272,13 @@ if (href === "#") { event.preventDefault() defaultScroller.toY(0) - replaceUrl("") + replaceUrl("", 0) } else { var targetId = anchor.hash.substring(1) var targetElem = document.getElementById(targetId) if (targetElem) { event.preventDefault() - defaultScroller.to(targetElem) - replaceUrl("#" + targetId) + replaceUrl("#" + targetId, defaultScroller.to(targetElem)) } } }