smooth-scroll.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790
  1. //
  2. // SmoothScroll for websites v1.4.10 (Balazs Galambosi)
  3. // http://www.smoothscroll.net/
  4. //
  5. // Licensed under the terms of the MIT license.
  6. //
  7. // You may use it in your theme if you credit me.
  8. // It is also free to use on any individual website.
  9. //
  10. // Exception:
  11. // The only restriction is to not publish any
  12. // extension for browsers or native application
  13. // without getting a written permission first.
  14. //
  15. (function () {
  16. // Scroll Variables (tweakable)
  17. var defaultOptions = {
  18. // Scrolling Core
  19. frameRate : 150, // [Hz]
  20. animationTime : 400, // [ms]
  21. stepSize : 100, // [px]
  22. // Pulse (less tweakable)
  23. // ratio of "tail" to "acceleration"
  24. pulseAlgorithm : true,
  25. pulseScale : 4,
  26. pulseNormalize : 1,
  27. // Acceleration
  28. accelerationDelta : 50, // 50
  29. accelerationMax : 3, // 3
  30. // Keyboard Settings
  31. keyboardSupport : true, // option
  32. arrowScroll : 50, // [px]
  33. // Other
  34. fixedBackground : true,
  35. excluded : ''
  36. };
  37. var options = defaultOptions;
  38. // Other Variables
  39. var isExcluded = false;
  40. var isFrame = false;
  41. var direction = { x: 0, y: 0 };
  42. var initDone = false;
  43. var root = document.documentElement;
  44. var activeElement;
  45. var observer;
  46. var refreshSize;
  47. var deltaBuffer = [];
  48. var deltaBufferTimer;
  49. var isMac = /^Mac/.test(navigator.platform);
  50. var key = { left: 37, up: 38, right: 39, down: 40, spacebar: 32,
  51. pageup: 33, pagedown: 34, end: 35, home: 36 };
  52. var arrowKeys = { 37: 1, 38: 1, 39: 1, 40: 1 };
  53. /***********************************************
  54. * INITIALIZE
  55. ***********************************************/
  56. /**
  57. * Tests if smooth scrolling is allowed. Shuts down everything if not.
  58. */
  59. function initTest() {
  60. if (options.keyboardSupport) {
  61. addEvent('keydown', keydown);
  62. }
  63. }
  64. /**
  65. * Sets up scrolls array, determines if frames are involved.
  66. */
  67. function init() {
  68. if (initDone || !document.body) return;
  69. initDone = true;
  70. var body = document.body;
  71. var html = document.documentElement;
  72. var windowHeight = window.innerHeight;
  73. var scrollHeight = body.scrollHeight;
  74. // check compat mode for root element
  75. root = (document.compatMode.indexOf('CSS') >= 0) ? html : body;
  76. activeElement = body;
  77. initTest();
  78. // Checks if this script is running in a frame
  79. if (top != self) {
  80. isFrame = true;
  81. }
  82. /**
  83. * Safari 10 fixed it, Chrome fixed it in v45:
  84. * This fixes a bug where the areas left and right to
  85. * the content does not trigger the onmousewheel event
  86. * on some pages. e.g.: html, body { height: 100% }
  87. */
  88. else if (isOldSafari &&
  89. scrollHeight > windowHeight &&
  90. (body.offsetHeight <= windowHeight ||
  91. html.offsetHeight <= windowHeight)) {
  92. var fullPageElem = document.createElement('div');
  93. fullPageElem.style.cssText = 'position:absolute; z-index:-10000; ' +
  94. 'top:0; left:0; right:0; height:' +
  95. root.scrollHeight + 'px';
  96. document.body.appendChild(fullPageElem);
  97. // DOM changed (throttled) to fix height
  98. var pendingRefresh;
  99. refreshSize = function () {
  100. if (pendingRefresh) return; // could also be: clearTimeout(pendingRefresh);
  101. pendingRefresh = setTimeout(function () {
  102. if (isExcluded) return; // could be running after cleanup
  103. fullPageElem.style.height = '0';
  104. fullPageElem.style.height = root.scrollHeight + 'px';
  105. pendingRefresh = null;
  106. }, 500); // act rarely to stay fast
  107. };
  108. setTimeout(refreshSize, 10);
  109. addEvent('resize', refreshSize);
  110. // TODO: attributeFilter?
  111. var config = {
  112. attributes: true,
  113. childList: true,
  114. characterData: false
  115. // subtree: true
  116. };
  117. observer = new MutationObserver(refreshSize);
  118. observer.observe(body, config);
  119. if (root.offsetHeight <= windowHeight) {
  120. var clearfix = document.createElement('div');
  121. clearfix.style.clear = 'both';
  122. body.appendChild(clearfix);
  123. }
  124. }
  125. // disable fixed background
  126. if (!options.fixedBackground && !isExcluded) {
  127. body.style.backgroundAttachment = 'scroll';
  128. html.style.backgroundAttachment = 'scroll';
  129. }
  130. }
  131. /**
  132. * Removes event listeners and other traces left on the page.
  133. */
  134. function cleanup() {
  135. observer && observer.disconnect();
  136. removeEvent(wheelEvent, wheel);
  137. removeEvent('mousedown', mousedown);
  138. removeEvent('keydown', keydown);
  139. removeEvent('resize', refreshSize);
  140. removeEvent('load', init);
  141. }
  142. /************************************************
  143. * SCROLLING
  144. ************************************************/
  145. var que = [];
  146. var pending = false;
  147. var lastScroll = Date.now();
  148. /**
  149. * Pushes scroll actions to the scrolling queue.
  150. */
  151. function scrollArray(elem, left, top) {
  152. directionCheck(left, top);
  153. if (options.accelerationMax != 1) {
  154. var now = Date.now();
  155. var elapsed = now - lastScroll;
  156. if (elapsed < options.accelerationDelta) {
  157. var factor = (1 + (50 / elapsed)) / 2;
  158. if (factor > 1) {
  159. factor = Math.min(factor, options.accelerationMax);
  160. left *= factor;
  161. top *= factor;
  162. }
  163. }
  164. lastScroll = Date.now();
  165. }
  166. // push a scroll command
  167. que.push({
  168. x: left,
  169. y: top,
  170. lastX: (left < 0) ? 0.99 : -0.99,
  171. lastY: (top < 0) ? 0.99 : -0.99,
  172. start: Date.now()
  173. });
  174. // don't act if there's a pending queue
  175. if (pending) {
  176. return;
  177. }
  178. var scrollRoot = getScrollRoot();
  179. var isWindowScroll = (elem === scrollRoot || elem === document.body);
  180. // if we haven't already fixed the behavior,
  181. // and it needs fixing for this sesh
  182. if (elem.$scrollBehavior == null && isScrollBehaviorSmooth(elem)) {
  183. elem.$scrollBehavior = elem.style.scrollBehavior;
  184. elem.style.scrollBehavior = 'auto';
  185. }
  186. var step = function (time) {
  187. var now = Date.now();
  188. var scrollX = 0;
  189. var scrollY = 0;
  190. for (var i = 0; i < que.length; i++) {
  191. var item = que[i];
  192. var elapsed = now - item.start;
  193. var finished = (elapsed >= options.animationTime);
  194. // scroll position: [0, 1]
  195. var position = (finished) ? 1 : elapsed / options.animationTime;
  196. // easing [optional]
  197. if (options.pulseAlgorithm) {
  198. position = pulse(position);
  199. }
  200. // only need the difference
  201. var x = (item.x * position - item.lastX) >> 0;
  202. var y = (item.y * position - item.lastY) >> 0;
  203. // add this to the total scrolling
  204. scrollX += x;
  205. scrollY += y;
  206. // update last values
  207. item.lastX += x;
  208. item.lastY += y;
  209. // delete and step back if it's over
  210. if (finished) {
  211. que.splice(i, 1); i--;
  212. }
  213. }
  214. // scroll left and top
  215. if (isWindowScroll) {
  216. window.scrollBy(scrollX, scrollY);
  217. }
  218. else {
  219. if (scrollX) elem.scrollLeft += scrollX;
  220. if (scrollY) elem.scrollTop += scrollY;
  221. }
  222. // clean up if there's nothing left to do
  223. if (!left && !top) {
  224. que = [];
  225. }
  226. if (que.length) {
  227. requestFrame(step, elem, (1000 / options.frameRate + 1));
  228. } else {
  229. pending = false;
  230. // restore default behavior at the end of scrolling sesh
  231. if (elem.$scrollBehavior != null) {
  232. elem.style.scrollBehavior = elem.$scrollBehavior;
  233. elem.$scrollBehavior = null;
  234. }
  235. }
  236. };
  237. // start a new queue of actions
  238. requestFrame(step, elem, 0);
  239. pending = true;
  240. }
  241. /***********************************************
  242. * EVENTS
  243. ***********************************************/
  244. /**
  245. * Mouse wheel handler.
  246. * @param {Object} event
  247. */
  248. function wheel(event) {
  249. if (!initDone) {
  250. init();
  251. }
  252. var target = event.target;
  253. // leave early if default action is prevented
  254. // or it's a zooming event with CTRL
  255. if (event.defaultPrevented || event.ctrlKey) {
  256. return true;
  257. }
  258. // leave embedded content alone (flash & pdf)
  259. if (isNodeName(activeElement, 'embed') ||
  260. (isNodeName(target, 'embed') && /\.pdf/i.test(target.src)) ||
  261. isNodeName(activeElement, 'object') ||
  262. target.shadowRoot) {
  263. return true;
  264. }
  265. var deltaX = -event.wheelDeltaX || event.deltaX || 0;
  266. var deltaY = -event.wheelDeltaY || event.deltaY || 0;
  267. if (isMac) {
  268. if (event.wheelDeltaX && isDivisible(event.wheelDeltaX, 120)) {
  269. deltaX = -120 * (event.wheelDeltaX / Math.abs(event.wheelDeltaX));
  270. }
  271. if (event.wheelDeltaY && isDivisible(event.wheelDeltaY, 120)) {
  272. deltaY = -120 * (event.wheelDeltaY / Math.abs(event.wheelDeltaY));
  273. }
  274. }
  275. // use wheelDelta if deltaX/Y is not available
  276. if (!deltaX && !deltaY) {
  277. deltaY = -event.wheelDelta || 0;
  278. }
  279. // line based scrolling (Firefox mostly)
  280. if (event.deltaMode === 1) {
  281. deltaX *= 40;
  282. deltaY *= 40;
  283. }
  284. var overflowing = overflowingAncestor(target);
  285. // nothing to do if there's no element that's scrollable
  286. if (!overflowing) {
  287. // except Chrome iframes seem to eat wheel events, which we need to
  288. // propagate up, if the iframe has nothing overflowing to scroll
  289. if (isFrame && isChrome) {
  290. // change target to iframe element itself for the parent frame
  291. Object.defineProperty(event, "target", {value: window.frameElement});
  292. return parent.wheel(event);
  293. }
  294. return true;
  295. }
  296. // check if it's a touchpad scroll that should be ignored
  297. if (isTouchpad(deltaY)) {
  298. return true;
  299. }
  300. // scale by step size
  301. // delta is 120 most of the time
  302. // synaptics seems to send 1 sometimes
  303. if (Math.abs(deltaX) > 1.2) {
  304. deltaX *= options.stepSize / 120;
  305. }
  306. if (Math.abs(deltaY) > 1.2) {
  307. deltaY *= options.stepSize / 120;
  308. }
  309. scrollArray(overflowing, deltaX, deltaY);
  310. event.preventDefault();
  311. scheduleClearCache();
  312. }
  313. /**
  314. * Keydown event handler.
  315. * @param {Object} event
  316. */
  317. function keydown(event) {
  318. var target = event.target;
  319. var modifier = event.ctrlKey || event.altKey || event.metaKey ||
  320. (event.shiftKey && event.keyCode !== key.spacebar);
  321. // our own tracked active element could've been removed from the DOM
  322. if (!document.body.contains(activeElement)) {
  323. activeElement = document.activeElement;
  324. }
  325. // do nothing if user is editing text
  326. // or using a modifier key (except shift)
  327. // or in a dropdown
  328. // or inside interactive elements
  329. var inputNodeNames = /^(textarea|select|embed|object)$/i;
  330. var buttonTypes = /^(button|submit|radio|checkbox|file|color|image)$/i;
  331. if ( event.defaultPrevented ||
  332. inputNodeNames.test(target.nodeName) ||
  333. isNodeName(target, 'input') && !buttonTypes.test(target.type) ||
  334. isNodeName(activeElement, 'video') ||
  335. isInsideYoutubeVideo(event) ||
  336. target.isContentEditable ||
  337. modifier ) {
  338. return true;
  339. }
  340. // [spacebar] should trigger button press, leave it alone
  341. if ((isNodeName(target, 'button') ||
  342. isNodeName(target, 'input') && buttonTypes.test(target.type)) &&
  343. event.keyCode === key.spacebar) {
  344. return true;
  345. }
  346. // [arrwow keys] on radio buttons should be left alone
  347. if (isNodeName(target, 'input') && target.type == 'radio' &&
  348. arrowKeys[event.keyCode]) {
  349. return true;
  350. }
  351. var shift, x = 0, y = 0;
  352. var overflowing = overflowingAncestor(activeElement);
  353. if (!overflowing) {
  354. // Chrome iframes seem to eat key events, which we need to
  355. // propagate up, if the iframe has nothing overflowing to scroll
  356. return (isFrame && isChrome) ? parent.keydown(event) : true;
  357. }
  358. var clientHeight = overflowing.clientHeight;
  359. if (overflowing == document.body) {
  360. clientHeight = window.innerHeight;
  361. }
  362. switch (event.keyCode) {
  363. case key.up:
  364. y = -options.arrowScroll;
  365. break;
  366. case key.down:
  367. y = options.arrowScroll;
  368. break;
  369. case key.spacebar: // (+ shift)
  370. shift = event.shiftKey ? 1 : -1;
  371. y = -shift * clientHeight * 0.9;
  372. break;
  373. case key.pageup:
  374. y = -clientHeight * 0.9;
  375. break;
  376. case key.pagedown:
  377. y = clientHeight * 0.9;
  378. break;
  379. case key.home:
  380. if (overflowing == document.body && document.scrollingElement)
  381. overflowing = document.scrollingElement;
  382. y = -overflowing.scrollTop;
  383. break;
  384. case key.end:
  385. var scroll = overflowing.scrollHeight - overflowing.scrollTop;
  386. var scrollRemaining = scroll - clientHeight;
  387. y = (scrollRemaining > 0) ? scrollRemaining + 10 : 0;
  388. break;
  389. case key.left:
  390. x = -options.arrowScroll;
  391. break;
  392. case key.right:
  393. x = options.arrowScroll;
  394. break;
  395. default:
  396. return true; // a key we don't care about
  397. }
  398. scrollArray(overflowing, x, y);
  399. event.preventDefault();
  400. scheduleClearCache();
  401. }
  402. /**
  403. * Mousedown event only for updating activeElement
  404. */
  405. function mousedown(event) {
  406. activeElement = event.target;
  407. }
  408. /***********************************************
  409. * OVERFLOW
  410. ***********************************************/
  411. var uniqueID = (function () {
  412. var i = 0;
  413. return function (el) {
  414. return el.uniqueID || (el.uniqueID = i++);
  415. };
  416. })();
  417. var cacheX = {}; // cleared out after a scrolling session
  418. var cacheY = {}; // cleared out after a scrolling session
  419. var clearCacheTimer;
  420. var smoothBehaviorForElement = {};
  421. //setInterval(function () { cache = {}; }, 10 * 1000);
  422. function scheduleClearCache() {
  423. clearTimeout(clearCacheTimer);
  424. clearCacheTimer = setInterval(function () {
  425. cacheX = cacheY = smoothBehaviorForElement = {};
  426. }, 1*1000);
  427. }
  428. function setCache(elems, overflowing, x) {
  429. var cache = x ? cacheX : cacheY;
  430. for (var i = elems.length; i--;)
  431. cache[uniqueID(elems[i])] = overflowing;
  432. return overflowing;
  433. }
  434. function getCache(el, x) {
  435. return (x ? cacheX : cacheY)[uniqueID(el)];
  436. }
  437. // (body) (root)
  438. // | hidden | visible | scroll | auto |
  439. // hidden | no | no | YES | YES |
  440. // visible | no | YES | YES | YES |
  441. // scroll | no | YES | YES | YES |
  442. // auto | no | YES | YES | YES |
  443. function overflowingAncestor(el) {
  444. var elems = [];
  445. var body = document.body;
  446. var rootScrollHeight = root.scrollHeight;
  447. do {
  448. var cached = getCache(el, false);
  449. if (cached) {
  450. return setCache(elems, cached);
  451. }
  452. elems.push(el);
  453. if (rootScrollHeight === el.scrollHeight) {
  454. var topOverflowsNotHidden = overflowNotHidden(root) && overflowNotHidden(body);
  455. var isOverflowCSS = topOverflowsNotHidden || overflowAutoOrScroll(root);
  456. if (isFrame && isContentOverflowing(root) ||
  457. !isFrame && isOverflowCSS) {
  458. return setCache(elems, getScrollRoot());
  459. }
  460. } else if (isContentOverflowing(el) && overflowAutoOrScroll(el)) {
  461. return setCache(elems, el);
  462. }
  463. } while ((el = el.parentElement));
  464. }
  465. function isContentOverflowing(el) {
  466. return (el.clientHeight + 10 < el.scrollHeight);
  467. }
  468. // typically for <body> and <html>
  469. function overflowNotHidden(el) {
  470. var overflow = getComputedStyle(el, '').getPropertyValue('overflow-y');
  471. return (overflow !== 'hidden');
  472. }
  473. // for all other elements
  474. function overflowAutoOrScroll(el) {
  475. var overflow = getComputedStyle(el, '').getPropertyValue('overflow-y');
  476. return (overflow === 'scroll' || overflow === 'auto');
  477. }
  478. // for all other elements
  479. function isScrollBehaviorSmooth(el) {
  480. var id = uniqueID(el);
  481. if (smoothBehaviorForElement[id] == null) {
  482. var scrollBehavior = getComputedStyle(el, '')['scroll-behavior'];
  483. smoothBehaviorForElement[id] = ('smooth' == scrollBehavior);
  484. }
  485. return smoothBehaviorForElement[id];
  486. }
  487. /***********************************************
  488. * HELPERS
  489. ***********************************************/
  490. function addEvent(type, fn, arg) {
  491. window.addEventListener(type, fn, arg || false);
  492. }
  493. function removeEvent(type, fn, arg) {
  494. window.removeEventListener(type, fn, arg || false);
  495. }
  496. function isNodeName(el, tag) {
  497. return el && (el.nodeName||'').toLowerCase() === tag.toLowerCase();
  498. }
  499. function directionCheck(x, y) {
  500. x = (x > 0) ? 1 : -1;
  501. y = (y > 0) ? 1 : -1;
  502. if (direction.x !== x || direction.y !== y) {
  503. direction.x = x;
  504. direction.y = y;
  505. que = [];
  506. lastScroll = 0;
  507. }
  508. }
  509. if (window.localStorage && localStorage.SS_deltaBuffer) {
  510. try { // #46 Safari throws in private browsing for localStorage
  511. deltaBuffer = localStorage.SS_deltaBuffer.split(',');
  512. } catch (e) { }
  513. }
  514. function isTouchpad(deltaY) {
  515. if (!deltaY) return;
  516. if (!deltaBuffer.length) {
  517. deltaBuffer = [deltaY, deltaY, deltaY];
  518. }
  519. deltaY = Math.abs(deltaY);
  520. deltaBuffer.push(deltaY);
  521. deltaBuffer.shift();
  522. clearTimeout(deltaBufferTimer);
  523. deltaBufferTimer = setTimeout(function () {
  524. try { // #46 Safari throws in private browsing for localStorage
  525. localStorage.SS_deltaBuffer = deltaBuffer.join(',');
  526. } catch (e) { }
  527. }, 1000);
  528. var dpiScaledWheelDelta = deltaY > 120 && allDeltasDivisableBy(deltaY); // win64
  529. var tp = !allDeltasDivisableBy(120) && !allDeltasDivisableBy(100) && !dpiScaledWheelDelta;
  530. if (deltaY < 50) return true;
  531. return tp;
  532. }
  533. function isDivisible(n, divisor) {
  534. return (Math.floor(n / divisor) == n / divisor);
  535. }
  536. function allDeltasDivisableBy(divisor) {
  537. return (isDivisible(deltaBuffer[0], divisor) &&
  538. isDivisible(deltaBuffer[1], divisor) &&
  539. isDivisible(deltaBuffer[2], divisor));
  540. }
  541. function isInsideYoutubeVideo(event) {
  542. var elem = event.target;
  543. var isControl = false;
  544. if (document.URL.indexOf ('www.youtube.com/watch') != -1) {
  545. do {
  546. isControl = (elem.classList &&
  547. elem.classList.contains('html5-video-controls'));
  548. if (isControl) break;
  549. } while ((elem = elem.parentNode));
  550. }
  551. return isControl;
  552. }
  553. var requestFrame = (function () {
  554. return (window.requestAnimationFrame ||
  555. window.webkitRequestAnimationFrame ||
  556. window.mozRequestAnimationFrame ||
  557. function (callback, element, delay) {
  558. window.setTimeout(callback, delay || (1000/60));
  559. });
  560. })();
  561. var MutationObserver = (window.MutationObserver ||
  562. window.WebKitMutationObserver ||
  563. window.MozMutationObserver);
  564. var getScrollRoot = (function() {
  565. var SCROLL_ROOT = document.scrollingElement;
  566. return function() {
  567. if (!SCROLL_ROOT) {
  568. var dummy = document.createElement('div');
  569. dummy.style.cssText = 'height:10000px;width:1px;';
  570. document.body.appendChild(dummy);
  571. var bodyScrollTop = document.body.scrollTop;
  572. var docElScrollTop = document.documentElement.scrollTop;
  573. window.scrollBy(0, 3);
  574. if (document.body.scrollTop != bodyScrollTop)
  575. (SCROLL_ROOT = document.body);
  576. else
  577. (SCROLL_ROOT = document.documentElement);
  578. window.scrollBy(0, -3);
  579. document.body.removeChild(dummy);
  580. }
  581. return SCROLL_ROOT;
  582. };
  583. })();
  584. /***********************************************
  585. * PULSE (by Michael Herf)
  586. ***********************************************/
  587. /**
  588. * Viscous fluid with a pulse for part and decay for the rest.
  589. * - Applies a fixed force over an interval (a damped acceleration), and
  590. * - Lets the exponential bleed away the velocity over a longer interval
  591. * - Michael Herf, http://stereopsis.com/stopping/
  592. */
  593. function pulse_(x) {
  594. var val, start, expx;
  595. // test
  596. x = x * options.pulseScale;
  597. if (x < 1) { // acceleartion
  598. val = x - (1 - Math.exp(-x));
  599. } else { // tail
  600. // the previous animation ended here:
  601. start = Math.exp(-1);
  602. // simple viscous drag
  603. x -= 1;
  604. expx = 1 - Math.exp(-x);
  605. val = start + (expx * (1 - start));
  606. }
  607. return val * options.pulseNormalize;
  608. }
  609. function pulse(x) {
  610. if (x >= 1) return 1;
  611. if (x <= 0) return 0;
  612. if (options.pulseNormalize == 1) {
  613. options.pulseNormalize /= pulse_(1);
  614. }
  615. return pulse_(x);
  616. }
  617. /***********************************************
  618. * FIRST RUN
  619. ***********************************************/
  620. var userAgent = window.navigator.userAgent;
  621. var isEdge = /Edge/.test(userAgent); // thank you MS
  622. var isChrome = /chrome/i.test(userAgent) && !isEdge;
  623. var isSafari = /safari/i.test(userAgent) && !isEdge;
  624. var isMobile = /mobile/i.test(userAgent);
  625. var isIEWin7 = /Windows NT 6.1/i.test(userAgent) && /rv:11/i.test(userAgent);
  626. var isOldSafari = isSafari && (/Version\/8/i.test(userAgent) || /Version\/9/i.test(userAgent));
  627. var isEnabledForBrowser = (isChrome || isSafari || isIEWin7) && !isMobile;
  628. var supportsPassive = false;
  629. try {
  630. window.addEventListener("test", null, Object.defineProperty({}, 'passive', {
  631. get: function () {
  632. supportsPassive = true;
  633. }
  634. }));
  635. } catch(e) {}
  636. var wheelOpt = supportsPassive ? { passive: false } : false;
  637. var wheelEvent = 'onwheel' in document.createElement('div') ? 'wheel' : 'mousewheel';
  638. if (wheelEvent && isEnabledForBrowser) {
  639. addEvent(wheelEvent, wheel, wheelOpt);
  640. addEvent('mousedown', mousedown);
  641. addEvent('load', init);
  642. }
  643. /***********************************************
  644. * PUBLIC INTERFACE
  645. ***********************************************/
  646. function SmoothScroll(optionsToSet) {
  647. for (var key in optionsToSet)
  648. if (defaultOptions.hasOwnProperty(key))
  649. options[key] = optionsToSet[key];
  650. }
  651. SmoothScroll.destroy = cleanup;
  652. if (window.SmoothScrollOptions) // async API
  653. SmoothScroll(window.SmoothScrollOptions);
  654. if (typeof define === 'function' && define.amd)
  655. define(function() {
  656. return SmoothScroll;
  657. });
  658. else if ('object' == typeof exports)
  659. module.exports = SmoothScroll;
  660. else
  661. window.SmoothScroll = SmoothScroll;
  662. })();