| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357 |
- /*!
- jQuery Smooth Scroll
- Version: 2.2.0
- Plugin URL: https://github.com/kswedberg/jquery-smooth-scroll
- License: Copyright (c) 2017 Karl Swedberg | Licensed under MIT
- !*/
- (function(factory) {
- if (typeof define === 'function' && define.amd) {
- // AMD. Register as an anonymous module.
- define(['jquery'], factory);
- } else if (typeof module === 'object' && module.exports) {
- // CommonJS
- factory(require('jquery'));
- } else {
- // Browser globals
- factory(jQuery);
- }
- }(function($) {
- var version = '2.2.0';
- var optionOverrides = {};
- var defaults = {
- exclude: [],
- excludeWithin: [],
- offset: 0,
- // one of 'top' or 'left'
- direction: 'top',
- // if set, bind click events through delegation
- // supported since jQuery 1.4.2
- delegateSelector: null,
- // jQuery set of elements you wish to scroll (for $.smoothScroll).
- // if null (default), $('html, body').firstScrollable() is used.
- scrollElement: null,
- // only use if you want to override default behavior
- scrollTarget: null,
- // automatically focus the target element after scrolling to it
- autoFocus: false,
- // fn(opts) function to be called before scrolling occurs.
- // `this` is the element(s) being scrolled
- beforeScroll: function() {},
- // fn(opts) function to be called after scrolling occurs.
- // `this` is the triggering element
- afterScroll: function() {},
- // easing name. jQuery comes with "swing" and "linear." For others, you'll need an easing plugin
- // from jQuery UI or elsewhere
- easing: 'swing',
- // speed can be a number or 'auto'
- // if 'auto', the speed will be calculated based on the formula:
- // (current scroll position - target scroll position) / autoCoeffic
- speed: 400,
- // coefficient for "auto" speed
- autoCoefficient: 2,
- // $.fn.smoothScroll only: whether to prevent the default click action
- preventDefault: true
- };
- var getScrollable = function(opts) {
- var scrollable = [];
- var scrolled = false;
- var dir = opts.dir && opts.dir === 'left' ? 'scrollLeft' : 'scrollTop';
- this.each(function() {
- var el = $(this);
- if (this === document || this === window) {
- return;
- }
- if (document.scrollingElement && (this === document.documentElement || this === document.body)) {
- scrollable.push(document.scrollingElement);
- return false;
- }
- if (el[dir]() > 0) {
- scrollable.push(this);
- } else {
- // if scroll(Top|Left) === 0, nudge the element 1px and see if it moves
- el[dir](1);
- scrolled = el[dir]() > 0;
- if (scrolled) {
- scrollable.push(this);
- }
- // then put it back, of course
- el[dir](0);
- }
- });
- if (!scrollable.length) {
- this.each(function() {
- // If no scrollable elements and <html> has scroll-behavior:smooth because
- // "When this property is specified on the root element, it applies to the viewport instead."
- // and "The scroll-behavior property of the … body element is *not* propagated to the viewport."
- // → https://drafts.csswg.org/cssom-view/#propdef-scroll-behavior
- if (this === document.documentElement && $(this).css('scrollBehavior') === 'smooth') {
- scrollable = [this];
- }
- // If still no scrollable elements, fall back to <body>,
- // if it's in the jQuery collection
- // (doing this because Safari sets scrollTop async,
- // so can't set it to 1 and immediately get the value.)
- if (!scrollable.length && this.nodeName === 'BODY') {
- scrollable = [this];
- }
- });
- }
- // Use the first scrollable element if we're calling firstScrollable()
- if (opts.el === 'first' && scrollable.length > 1) {
- scrollable = [scrollable[0]];
- }
- return scrollable;
- };
- var rRelative = /^([\-\+]=)(\d+)/;
- $.fn.extend({
- scrollable: function(dir) {
- var scrl = getScrollable.call(this, {dir: dir});
- return this.pushStack(scrl);
- },
- firstScrollable: function(dir) {
- var scrl = getScrollable.call(this, {el: 'first', dir: dir});
- return this.pushStack(scrl);
- },
- smoothScroll: function(options, extra) {
- options = options || {};
- if (options === 'options') {
- if (!extra) {
- return this.first().data('ssOpts');
- }
- return this.each(function() {
- var $this = $(this);
- var opts = $.extend($this.data('ssOpts') || {}, extra);
- $(this).data('ssOpts', opts);
- });
- }
- var opts = $.extend({}, $.fn.smoothScroll.defaults, options);
- var clickHandler = function(event) {
- var escapeSelector = function(str) {
- return str.replace(/(:|\.|\/)/g, '\\$1');
- };
- var link = this;
- var $link = $(this);
- var thisOpts = $.extend({}, opts, $link.data('ssOpts') || {});
- var exclude = opts.exclude;
- var excludeWithin = thisOpts.excludeWithin;
- var elCounter = 0;
- var ewlCounter = 0;
- var include = true;
- var clickOpts = {};
- var locationPath = $.smoothScroll.filterPath(location.pathname);
- var linkPath = $.smoothScroll.filterPath(link.pathname);
- var hostMatch = location.hostname === link.hostname || !link.hostname;
- var pathMatch = thisOpts.scrollTarget || (linkPath === locationPath);
- var thisHash = escapeSelector(link.hash);
- if (thisHash && !$(thisHash).length) {
- include = false;
- }
- if (!thisOpts.scrollTarget && (!hostMatch || !pathMatch || !thisHash)) {
- include = false;
- } else {
- while (include && elCounter < exclude.length) {
- if ($link.is(escapeSelector(exclude[elCounter++]))) {
- include = false;
- }
- }
- while (include && ewlCounter < excludeWithin.length) {
- if ($link.closest(excludeWithin[ewlCounter++]).length) {
- include = false;
- }
- }
- }
- if (include) {
- if (thisOpts.preventDefault) {
- event.preventDefault();
- }
- $.extend(clickOpts, thisOpts, {
- scrollTarget: thisOpts.scrollTarget || thisHash,
- link: link
- });
- $.smoothScroll(clickOpts);
- }
- };
- if (options.delegateSelector !== null) {
- this
- .off('click.smoothscroll', options.delegateSelector)
- .on('click.smoothscroll', options.delegateSelector, clickHandler);
- } else {
- this
- .off('click.smoothscroll')
- .on('click.smoothscroll', clickHandler);
- }
- return this;
- }
- });
- var getExplicitOffset = function(val) {
- var explicit = {relative: ''};
- var parts = typeof val === 'string' && rRelative.exec(val);
- if (typeof val === 'number') {
- explicit.px = val;
- } else if (parts) {
- explicit.relative = parts[1];
- explicit.px = parseFloat(parts[2]) || 0;
- }
- return explicit;
- };
- var onAfterScroll = function(opts) {
- var $tgt = $(opts.scrollTarget);
- if (opts.autoFocus && $tgt.length) {
- $tgt[0].focus();
- if (!$tgt.is(document.activeElement)) {
- $tgt.prop({tabIndex: -1});
- $tgt[0].focus();
- }
- }
- opts.afterScroll.call(opts.link, opts);
- };
- $.smoothScroll = function(options, px) {
- if (options === 'options' && typeof px === 'object') {
- return $.extend(optionOverrides, px);
- }
- var opts, $scroller, speed, delta;
- var explicitOffset = getExplicitOffset(options);
- var scrollTargetOffset = {};
- var scrollerOffset = 0;
- var offPos = 'offset';
- var scrollDir = 'scrollTop';
- var aniProps = {};
- var aniOpts = {};
- if (explicitOffset.px) {
- opts = $.extend({link: null}, $.fn.smoothScroll.defaults, optionOverrides);
- } else {
- opts = $.extend({link: null}, $.fn.smoothScroll.defaults, options || {}, optionOverrides);
- if (opts.scrollElement) {
- offPos = 'position';
- if (opts.scrollElement.css('position') === 'static') {
- opts.scrollElement.css('position', 'relative');
- }
- }
- if (px) {
- explicitOffset = getExplicitOffset(px);
- }
- }
- scrollDir = opts.direction === 'left' ? 'scrollLeft' : scrollDir;
- if (opts.scrollElement) {
- $scroller = opts.scrollElement;
- if (!explicitOffset.px && !(/^(?:HTML|BODY)$/).test($scroller[0].nodeName)) {
- scrollerOffset = $scroller[scrollDir]();
- }
- } else {
- $scroller = $('html, body').firstScrollable(opts.direction);
- }
- // beforeScroll callback function must fire before calculating offset
- opts.beforeScroll.call($scroller, opts);
- scrollTargetOffset = explicitOffset.px ? explicitOffset : {
- relative: '',
- px: ($(opts.scrollTarget)[offPos]() && $(opts.scrollTarget)[offPos]()[opts.direction]) || 0
- };
- aniProps[scrollDir] = scrollTargetOffset.relative + (scrollTargetOffset.px + scrollerOffset + opts.offset);
- speed = opts.speed;
- // automatically calculate the speed of the scroll based on distance / coefficient
- if (speed === 'auto') {
- // $scroller[scrollDir]() is position before scroll, aniProps[scrollDir] is position after
- // When delta is greater, speed will be greater.
- delta = Math.abs(aniProps[scrollDir] - $scroller[scrollDir]());
- // Divide the delta by the coefficient
- speed = delta / opts.autoCoefficient;
- }
- aniOpts = {
- duration: speed,
- easing: opts.easing,
- complete: function() {
- onAfterScroll(opts);
- }
- };
- if (opts.step) {
- aniOpts.step = opts.step;
- }
- if ($scroller.length) {
- $scroller.stop().animate(aniProps, aniOpts);
- } else {
- onAfterScroll(opts);
- }
- };
- $.smoothScroll.version = version;
- $.smoothScroll.filterPath = function(string) {
- string = string || '';
- return string
- .replace(/^\//, '')
- .replace(/(?:index|default).[a-zA-Z]{3,4}$/, '')
- .replace(/\/$/, '');
- };
- // default options
- $.fn.smoothScroll.defaults = defaults;
- }));
|