diff --git a/.gitignore b/.gitignore index 7adc2e10694ddc6e6d2b831cbc725b36fb268fdb..503adc275e1cea4621a83c7e4d061dce756d1e9f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,5 @@ .idea/ .rpt2_cache/ -dist/ node_modules/ -visual.dist.* yarn.lock diff --git a/README.md b/README.md index 431827449fe4b9e0cf4fd30db2e680aec7cd9169..f22c531e38c1a00a8e36bb6fd6d29388f7d83226 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,77 @@ # @grifart/smoothscroll -As smoothscroll functionality is nice and more user-friendly, this library solves these two things which were often repeating in our code: +Aim of this library is to provide more user-friendly +and less epileptic scrolling effect on a long content +by creating a customized easing and to apply this easing +to all various types of scrolling on a page +(see [Covered scenarios](#Covered scenarios)). -1. Enables smooth scrolling on all anchors starting with `#` character. -2. Enables smooth scrolling when page is entered with `#` character in URL. -Additionally this library comes with custom easing function registered by default which works as basic ease-in-out with one modification that it skips content if it is too long. This results in nicer transition between two parts of a page. - - -## Installation and usage +## Installation ```bash +npm install @grifart/smoothscroll +# or yarn add @grifart/smoothscroll ``` -```javascript -import SmoothScroll from '@grifart/smoothscroll'; +## Usage + +`@grifart/smoothscroll` exposes various functions to handle +different types of scrolling in your application. Each function +is importable through standard ES `import` directive. -// use defaults -SmoothScroll.enable(); +### Global scrolling behavior -// customize -SmoothScroll.enable({ - scrollOnLoad: false, - scrollOnLinkClick: true, -}); +| Function | Description | +|---|---| +| **`handleOnLoadScroll()`** | Attaches scrolling to anchored element when the page is loaded.\* | +| **`handleOnLinkClickScroll()`** | Attaches scrolling to given element when user clicks on an `a` tag having `href` starting with `#` character. | + +Use these functions **in top-level code**: + +```javascript +import {handleOnLoadScroll, handleOnLinkClickScroll} from '@grifart/smoothscroll'; + +handleOnLoadScroll(); +handleOnLinkClickScroll(); ``` -### Options +\*Note: when page load lasts more than 500 ms, load scroll effect +is disabled as it would lead to user-unfriendly behaviour like +jumping on the page up and down due to browser native behaviour. + +### scrollToX functions + +|Function|Parameters| +|---|---| +|`scrollToElement(element[, onScrollFinishedCallback])`|`element`: element to scroll to; `onScrollFinishedCallback` (optional): callback to trigger when scrolling is finished| +|`scrollToOffset(topOffset[, onScrollFinishedCallback])`|`topOffset`: scroll offset from top of document; `onScrollFinishedCallback` (optional): callback to trigger when scrolling is finished| +|`scrollToTarget(hashTarget[, onScrollFinishedCallback])`|`hashTarget`: instance of `HashTarget` object\* or `string`\*\*; `onScrollFinishedCallback` (optional): callback to trigger when scrolling is finished| + +\* `HashTarget` is a value object representing a target to scroll to. +You can easily initalize it with named constructor: +`HashTarget.fromString('#some-identifier', document)` +\*\* In case of passing `string`, `HashTarget` object will be +instantiated automatically with current `document` context. + +## More about + +### Custom scrolling effect -| Option | Value | Default value | Description | -| --- | --- | --- | --- | -| `scrollOnLoad`\* | `true`/`false` | `true` | Causes smooth scroll to anchored element when the page is loaded. -| `scrollOnLinkClick` | `true`/`false` | `true` | Causes smooth scroll on given element when user clicks on an `a` tag having `href` starting with `#` character. +Improved scrolling effect (internally called `ease-in-skip-out`) is registered +by default and it works as basic ease-in-out with one modification +that it skips content if it is too long. This results in nicer transition +between two distant parts in a page. -\*Note: when the page load lasts more than 500 ms, load smooth scroll effect is disabled as it would lead to user-unfriendly behaviour like jumping on the page up and down due to browser native behaviour. +### Covered scenarios +- scroll when clicking a link with an anchor (`<a href="#anchor">whatever</a>`) +- scroll when page is entered with an anchor (`https://example.com/whatever#anchor`) +- scroll when programatically needed to scroll to: + - given position (top offset) + - given element + - given target (`id` attribute) ## Development @@ -44,5 +80,6 @@ yarn install yarn dev ``` -Every piece of this library comes with its unit test sitting alongside the script. Whole library is covered by integration test sitting in `src` folder. +Every piece of this library comes with its unit test sitting alongside the script. +Whole library is covered by integration test sitting in `src` folder. Note that you have to build assets first (`yarn build`) before running a test. diff --git a/dist/HashTarget.d.ts b/dist/HashTarget.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..aa7eedad7c5f77ab0e6ee173ffc6047b2c846f43 --- /dev/null +++ b/dist/HashTarget.d.ts @@ -0,0 +1,8 @@ +export declare class HashTarget { + private readonly value; + private readonly targetElement; + private constructor(); + static fromString(value: string, document: HTMLDocument): HashTarget; + getHash(): string; + getElement(): HTMLElement; +} diff --git a/dist/assert.d.ts b/dist/assert.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..dd7367d3f4304204154bda86117995b54b4e8404 --- /dev/null +++ b/dist/assert.d.ts @@ -0,0 +1,3 @@ +export declare class AssertionError extends Error { +} +export declare function assert(condition: any, message?: string): asserts condition; diff --git a/dist/esImportTest.d.ts b/dist/esImportTest.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..cb0ff5c3b541f646105198ee23ac0fc3d805023e --- /dev/null +++ b/dist/esImportTest.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/handlers/linkClickScroll/initializeOnLinkClickScroll.d.ts b/dist/handlers/linkClickScroll/initializeOnLinkClickScroll.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..3bf38f0edfd4ad2f293ae440e0fe0520fa2c7cdb --- /dev/null +++ b/dist/handlers/linkClickScroll/initializeOnLinkClickScroll.d.ts @@ -0,0 +1 @@ +export declare function initializeOnLinkClickScroll(): void; diff --git a/dist/handlers/loadScroll/initializeOnLoadScroll.d.ts b/dist/handlers/loadScroll/initializeOnLoadScroll.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..f762e2d1a4c91d6d9b19157b9982ed042b26d56f --- /dev/null +++ b/dist/handlers/loadScroll/initializeOnLoadScroll.d.ts @@ -0,0 +1 @@ +export declare function initializeOnLoadScroll(): void; diff --git a/dist/index.d.ts b/dist/index.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..f22f8f6df32e785046fdd87d959ef4c3a1c65a85 --- /dev/null +++ b/dist/index.d.ts @@ -0,0 +1,7 @@ +import { scrollToElement } from './scrollers/scrollToElement'; +import { scrollToOffset } from './scrollers/scrollToOffset'; +import { scrollToTarget } from './scrollers/scrollToTarget'; +import { HashTarget } from './HashTarget'; +declare function handleOnLoadScroll(): void; +declare function handleOnLinkClickScroll(): void; +export { HashTarget, handleOnLoadScroll, handleOnLinkClickScroll, scrollToElement, scrollToOffset, scrollToTarget, }; diff --git a/dist/index.esm.js b/dist/index.esm.js new file mode 100644 index 0000000000000000000000000000000000000000..0ec3668cb4d4a7dfe90bf3a2083c8dae63f78279 --- /dev/null +++ b/dist/index.esm.js @@ -0,0 +1,229 @@ +import * as Velocity from 'velocity-animate'; +import { animate } from 'velocity-animate'; + +var EASE_IN_SKIP_OUT_EASING = 'ease-in-skip-out'; // e.g. (5, 5, 10, 500, 1000) => 500 +// e.g. (5, 0, 10, 500, 1000) => 750 + +var mapIntervalLinear = function mapIntervalLinear(number, originalFrom, originalTo, newFrom, newTo) { + var oldDistance = originalTo - originalFrom; + var newDistance = newTo - newFrom; // normalize value into interval 0 .. 1 + + var normalized = (number - originalFrom) / oldDistance; // extend and move normalized value into new interval + + return normalized * newDistance + newFrom; +}; +/** + * Composes easings together, splits time into half. + * + * @param firstHalfEasingFn first half of easing + * @param secondHalfEasingFn second half of easing + * @return {function(*=, *=, *=)} the composed easing + */ + + +var composeEasing = function composeEasing(firstHalfEasingFn, secondHalfEasingFn) { + // time: The call's completion percentage (decimal value). + // opts (optional): The options object passed into the triggering Velocity call. + // tweenDelta (optional): The difference between the animating property's ending value and its starting value. + return function (time, opts, tweenDelta) { + if (time < 0.5) { + var normalizedTime = mapIntervalLinear(time, 0, 0.5, 0, 1); // map 0 - 0.5 => 0 - 1 + + return mapIntervalLinear(firstHalfEasingFn(normalizedTime, opts, tweenDelta), 0, 1, 0, 0.5); // map 1 - 0 => 0 - 0.5 + } else { + var _normalizedTime = mapIntervalLinear(time, 0.5, 1, 0, 1); // map 0 - 0.5 => 0 - 1 + + + return mapIntervalLinear(secondHalfEasingFn(_normalizedTime, opts, tweenDelta), 0, 1, 0.5, 1); // map 1 - 0 => 0 - 0.5 + } + }; +}; + +var computeHowMuchToSkip = function computeHowMuchToSkip(tweenDelta) { + var howManyScreens = Math.abs(tweenDelta) / window.innerHeight; // 0 .. 1 (percents) + + var howMuchToSkip = 0; // by testing in browser we have found following values as smooth: + // howManyScreens .. howMuchToSkip + // 1 .. 0% + // 2 .. 0% + // 3 .. 30% + // 8 .. 60% + // 30 .. 85% + // 60 .. 90% + // 100 .. 90% + + if (howManyScreens <= 2) { + howMuchToSkip = 0; + } else if (howManyScreens <= 4) { + // 2 - 4 screens; skip 0% - 30% of content + howMuchToSkip = mapIntervalLinear(howManyScreens, 2, 4, 0, 0.3); + } else if (howManyScreens <= 8) { + // 4 - 8 screens; skip 30% - 60% of content + howMuchToSkip = mapIntervalLinear(howManyScreens, 4, 8, 0.3, 0.6); + } else if (howManyScreens <= 30) { + // 8 - 30 screens; skip 60% - 85% of content + howMuchToSkip = mapIntervalLinear(howManyScreens, 8, 30, 0.6, 0.85); + } else if (howManyScreens <= 60) { + // 30 - 60 screens; skip 85% - 30% of content + howMuchToSkip = mapIntervalLinear(howManyScreens, 30, 60, 0.85, 0.9); + } else { + // > 60 screens; skip 90% of content + howMuchToSkip = 0.9; + } + + return howMuchToSkip; +}; + +var bindEasingToVelocity = function bindEasingToVelocity(velocity) { + velocity.Easings[EASE_IN_SKIP_OUT_EASING] = composeEasing(function (time, opts, tweenDelta) { + return mapIntervalLinear(velocity.Easings['ease-in'](time, opts, tweenDelta), 0, 1, // from interval + 0, 1 - computeHowMuchToSkip(tweenDelta) // to interval + ); + }, function (time, opts, tweenDelta) { + return mapIntervalLinear(velocity.Easings['ease-out'](time, opts, tweenDelta), 0, 1, // from interval + computeHowMuchToSkip(tweenDelta), 1 // to interval + ); + }); +}; + +var HashTarget = (function () { + function HashTarget(value, targetElement) { + this.value = value; + this.targetElement = targetElement; + } + HashTarget.fromString = function (value, document) { + if (value === '' || value === '#') { + throw new Error('Hash does not contain any fragment.'); + } + var targetElementId = value.substring(1); + var targetElement = document.getElementById(targetElementId); + if (targetElement === null) { + throw new Error("No referenced element with ID " + targetElementId + " exists."); + } + return new this(value, targetElement); + }; + HashTarget.prototype.getHash = function () { + return this.value; + }; + HashTarget.prototype.getElement = function () { + return this.targetElement; + }; + return HashTarget; +}()); + +function scrollToElement(element, onScrollFinishedCallback) { + animate(element, 'scroll', { + duration: 1200, + easing: EASE_IN_SKIP_OUT_EASING, + complete: function () { return onScrollFinishedCallback !== undefined && onScrollFinishedCallback(); }, + }); +} + +/*! ***************************************************************************** +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at http://www.apache.org/licenses/LICENSE-2.0 + +THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED +WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +MERCHANTABLITY OR NON-INFRINGEMENT. + +See the Apache Version 2.0 License for specific language governing permissions +and limitations under the License. +***************************************************************************** */ +/* global Reflect, Promise */ + +var extendStatics = function(d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); +}; + +function __extends(d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +} + +var AssertionError = (function (_super) { + __extends(AssertionError, _super); + function AssertionError() { + return _super !== null && _super.apply(this, arguments) || this; + } + return AssertionError; +}(Error)); +function assert(condition, message) { + if (!condition) { + throw new AssertionError(message); + } +} + +function scrollToTarget(hashTarget, onScrollFinishedCallback) { + if (typeof hashTarget === 'string') { + hashTarget = HashTarget.fromString(hashTarget, document); + } + scrollToElement(hashTarget.getElement(), function () { + assert(hashTarget instanceof HashTarget); + window.location.hash = hashTarget.getHash(); + onScrollFinishedCallback !== undefined && onScrollFinishedCallback(); + }); +} + +function initializeOnLoadScroll() { + var hash = window.location.hash; + if (hash === '' || hash === '#') { + return; + } + var hashTarget = null; + var start = performance.now(); + document.addEventListener('DOMContentLoaded', function () { + hashTarget = HashTarget.fromString(hash, document); + }); + window.addEventListener('load', function () { + var end = performance.now(); + if (end - start > 500) { + return; + } + window.scroll({ top: 0, left: 0 }); + assert(hashTarget !== null, 'Hash target should be set on DOM loaded.'); + scrollToTarget(hashTarget); + }); +} + +function initializeOnLinkClickScroll() { + document.addEventListener('DOMContentLoaded', function () { + return document.querySelectorAll('a[href^="#"]').forEach(function (item) { + return item.addEventListener('click', function (event) { + var element = event.currentTarget; + assert(element !== null); + if (element.hash === '' || element.hash === '#') { + return; + } + event.preventDefault(); + scrollToTarget(HashTarget.fromString(element.hash, document)); + }); + }); + }); +} + +function scrollToOffset(topOffset, onScrollFinishedCallback) { + animate(document.documentElement, 'scroll', { + duration: 1200, + offset: topOffset, + easing: EASE_IN_SKIP_OUT_EASING, + complete: onScrollFinishedCallback, + }); +} + +bindEasingToVelocity(Velocity); +function handleOnLoadScroll() { + initializeOnLoadScroll(); +} +function handleOnLinkClickScroll() { + initializeOnLinkClickScroll(); +} + +export { HashTarget, handleOnLoadScroll, handleOnLinkClickScroll, scrollToElement, scrollToOffset, scrollToTarget }; diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000000000000000000000000000000000000..43bc650a1a0211ff794c554189330dc1e835b4af --- /dev/null +++ b/dist/index.js @@ -0,0 +1,241 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('velocity-animate')) : + typeof define === 'function' && define.amd ? define(['exports', 'velocity-animate'], factory) : + (factory((global.SmoothScroll = {}),global.Velocity)); +}(this, (function (exports,Velocity) { 'use strict'; + + var EASE_IN_SKIP_OUT_EASING = 'ease-in-skip-out'; // e.g. (5, 5, 10, 500, 1000) => 500 + // e.g. (5, 0, 10, 500, 1000) => 750 + + var mapIntervalLinear = function mapIntervalLinear(number, originalFrom, originalTo, newFrom, newTo) { + var oldDistance = originalTo - originalFrom; + var newDistance = newTo - newFrom; // normalize value into interval 0 .. 1 + + var normalized = (number - originalFrom) / oldDistance; // extend and move normalized value into new interval + + return normalized * newDistance + newFrom; + }; + /** + * Composes easings together, splits time into half. + * + * @param firstHalfEasingFn first half of easing + * @param secondHalfEasingFn second half of easing + * @return {function(*=, *=, *=)} the composed easing + */ + + + var composeEasing = function composeEasing(firstHalfEasingFn, secondHalfEasingFn) { + // time: The call's completion percentage (decimal value). + // opts (optional): The options object passed into the triggering Velocity call. + // tweenDelta (optional): The difference between the animating property's ending value and its starting value. + return function (time, opts, tweenDelta) { + if (time < 0.5) { + var normalizedTime = mapIntervalLinear(time, 0, 0.5, 0, 1); // map 0 - 0.5 => 0 - 1 + + return mapIntervalLinear(firstHalfEasingFn(normalizedTime, opts, tweenDelta), 0, 1, 0, 0.5); // map 1 - 0 => 0 - 0.5 + } else { + var _normalizedTime = mapIntervalLinear(time, 0.5, 1, 0, 1); // map 0 - 0.5 => 0 - 1 + + + return mapIntervalLinear(secondHalfEasingFn(_normalizedTime, opts, tweenDelta), 0, 1, 0.5, 1); // map 1 - 0 => 0 - 0.5 + } + }; + }; + + var computeHowMuchToSkip = function computeHowMuchToSkip(tweenDelta) { + var howManyScreens = Math.abs(tweenDelta) / window.innerHeight; // 0 .. 1 (percents) + + var howMuchToSkip = 0; // by testing in browser we have found following values as smooth: + // howManyScreens .. howMuchToSkip + // 1 .. 0% + // 2 .. 0% + // 3 .. 30% + // 8 .. 60% + // 30 .. 85% + // 60 .. 90% + // 100 .. 90% + + if (howManyScreens <= 2) { + howMuchToSkip = 0; + } else if (howManyScreens <= 4) { + // 2 - 4 screens; skip 0% - 30% of content + howMuchToSkip = mapIntervalLinear(howManyScreens, 2, 4, 0, 0.3); + } else if (howManyScreens <= 8) { + // 4 - 8 screens; skip 30% - 60% of content + howMuchToSkip = mapIntervalLinear(howManyScreens, 4, 8, 0.3, 0.6); + } else if (howManyScreens <= 30) { + // 8 - 30 screens; skip 60% - 85% of content + howMuchToSkip = mapIntervalLinear(howManyScreens, 8, 30, 0.6, 0.85); + } else if (howManyScreens <= 60) { + // 30 - 60 screens; skip 85% - 30% of content + howMuchToSkip = mapIntervalLinear(howManyScreens, 30, 60, 0.85, 0.9); + } else { + // > 60 screens; skip 90% of content + howMuchToSkip = 0.9; + } + + return howMuchToSkip; + }; + + var bindEasingToVelocity = function bindEasingToVelocity(velocity) { + velocity.Easings[EASE_IN_SKIP_OUT_EASING] = composeEasing(function (time, opts, tweenDelta) { + return mapIntervalLinear(velocity.Easings['ease-in'](time, opts, tweenDelta), 0, 1, // from interval + 0, 1 - computeHowMuchToSkip(tweenDelta) // to interval + ); + }, function (time, opts, tweenDelta) { + return mapIntervalLinear(velocity.Easings['ease-out'](time, opts, tweenDelta), 0, 1, // from interval + computeHowMuchToSkip(tweenDelta), 1 // to interval + ); + }); + }; + + var HashTarget = (function () { + function HashTarget(value, targetElement) { + this.value = value; + this.targetElement = targetElement; + } + HashTarget.fromString = function (value, document) { + if (value === '' || value === '#') { + throw new Error('Hash does not contain any fragment.'); + } + var targetElementId = value.substring(1); + var targetElement = document.getElementById(targetElementId); + if (targetElement === null) { + throw new Error("No referenced element with ID " + targetElementId + " exists."); + } + return new this(value, targetElement); + }; + HashTarget.prototype.getHash = function () { + return this.value; + }; + HashTarget.prototype.getElement = function () { + return this.targetElement; + }; + return HashTarget; + }()); + + function scrollToElement(element, onScrollFinishedCallback) { + Velocity.animate(element, 'scroll', { + duration: 1200, + easing: EASE_IN_SKIP_OUT_EASING, + complete: function () { return onScrollFinishedCallback !== undefined && onScrollFinishedCallback(); }, + }); + } + + /*! ***************************************************************************** + Copyright (c) Microsoft Corporation. All rights reserved. + Licensed under the Apache License, Version 2.0 (the "License"); you may not use + this file except in compliance with the License. You may obtain a copy of the + License at http://www.apache.org/licenses/LICENSE-2.0 + + THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED + WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, + MERCHANTABLITY OR NON-INFRINGEMENT. + + See the Apache Version 2.0 License for specific language governing permissions + and limitations under the License. + ***************************************************************************** */ + /* global Reflect, Promise */ + + var extendStatics = function(d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + + function __extends(d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + } + + var AssertionError = (function (_super) { + __extends(AssertionError, _super); + function AssertionError() { + return _super !== null && _super.apply(this, arguments) || this; + } + return AssertionError; + }(Error)); + function assert(condition, message) { + if (!condition) { + throw new AssertionError(message); + } + } + + function scrollToTarget(hashTarget, onScrollFinishedCallback) { + if (typeof hashTarget === 'string') { + hashTarget = HashTarget.fromString(hashTarget, document); + } + scrollToElement(hashTarget.getElement(), function () { + assert(hashTarget instanceof HashTarget); + window.location.hash = hashTarget.getHash(); + onScrollFinishedCallback !== undefined && onScrollFinishedCallback(); + }); + } + + function initializeOnLoadScroll() { + var hash = window.location.hash; + if (hash === '' || hash === '#') { + return; + } + var hashTarget = null; + var start = performance.now(); + document.addEventListener('DOMContentLoaded', function () { + hashTarget = HashTarget.fromString(hash, document); + }); + window.addEventListener('load', function () { + var end = performance.now(); + if (end - start > 500) { + return; + } + window.scroll({ top: 0, left: 0 }); + assert(hashTarget !== null, 'Hash target should be set on DOM loaded.'); + scrollToTarget(hashTarget); + }); + } + + function initializeOnLinkClickScroll() { + document.addEventListener('DOMContentLoaded', function () { + return document.querySelectorAll('a[href^="#"]').forEach(function (item) { + return item.addEventListener('click', function (event) { + var element = event.currentTarget; + assert(element !== null); + if (element.hash === '' || element.hash === '#') { + return; + } + event.preventDefault(); + scrollToTarget(HashTarget.fromString(element.hash, document)); + }); + }); + }); + } + + function scrollToOffset(topOffset, onScrollFinishedCallback) { + Velocity.animate(document.documentElement, 'scroll', { + duration: 1200, + offset: topOffset, + easing: EASE_IN_SKIP_OUT_EASING, + complete: onScrollFinishedCallback, + }); + } + + bindEasingToVelocity(Velocity); + function handleOnLoadScroll() { + initializeOnLoadScroll(); + } + function handleOnLinkClickScroll() { + initializeOnLinkClickScroll(); + } + + exports.HashTarget = HashTarget; + exports.handleOnLoadScroll = handleOnLoadScroll; + exports.handleOnLinkClickScroll = handleOnLinkClickScroll; + exports.scrollToElement = scrollToElement; + exports.scrollToOffset = scrollToOffset; + exports.scrollToTarget = scrollToTarget; + + Object.defineProperty(exports, '__esModule', { value: true }); + +}))); diff --git a/dist/scrollers/scrollToElement.d.ts b/dist/scrollers/scrollToElement.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..4baccaed7ba2af0e0b6eaaee5ef551b2f655632d --- /dev/null +++ b/dist/scrollers/scrollToElement.d.ts @@ -0,0 +1 @@ +export declare function scrollToElement(element: HTMLElement, onScrollFinishedCallback?: () => void): void; diff --git a/dist/scrollers/scrollToElements.d.ts b/dist/scrollers/scrollToElements.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..4baccaed7ba2af0e0b6eaaee5ef551b2f655632d --- /dev/null +++ b/dist/scrollers/scrollToElements.d.ts @@ -0,0 +1 @@ +export declare function scrollToElement(element: HTMLElement, onScrollFinishedCallback?: () => void): void; diff --git a/dist/scrollers/scrollToOffset.d.ts b/dist/scrollers/scrollToOffset.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..116e65ccfb00ebc7de94de5c0e727acf946da416 --- /dev/null +++ b/dist/scrollers/scrollToOffset.d.ts @@ -0,0 +1 @@ +export declare function scrollToOffset(topOffset: number, onScrollFinishedCallback?: () => void): void; diff --git a/dist/scrollers/scrollToTarget.d.ts b/dist/scrollers/scrollToTarget.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..12c01b01b5eaf0e67dfc81c10d28dc7f8fdbcfe4 --- /dev/null +++ b/dist/scrollers/scrollToTarget.d.ts @@ -0,0 +1,2 @@ +import { HashTarget } from '../HashTarget'; +export declare function scrollToTarget(hashTarget: HashTarget | string, onScrollFinishedCallback?: () => void): void; diff --git a/src/easing/bindEasingToVelocity.d.ts b/src/easing/bindEasingToVelocity.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..eaf8629848b6f9847c1d7e0c74e9569fb2dab59e --- /dev/null +++ b/src/easing/bindEasingToVelocity.d.ts @@ -0,0 +1,5 @@ +import * as Velocity from 'velocity-animate'; + +declare const bindEasingToVelocity: (velocity: typeof Velocity) => void; +export declare const EASE_IN_SKIP_OUT_EASING: string; +export default bindEasingToVelocity; diff --git a/src/easing/setupVelocity.js b/src/easing/bindEasingToVelocity.js similarity index 97% rename from src/easing/setupVelocity.js rename to src/easing/bindEasingToVelocity.js index 4d0264e822519e4276ba9bb5b15d0a4675ae53f5..60337df72f834881f0ee38b8a9bc27114160e519 100644 --- a/src/easing/setupVelocity.js +++ b/src/easing/bindEasingToVelocity.js @@ -68,7 +68,7 @@ const computeHowMuchToSkip = (tweenDelta) => { return howMuchToSkip; }; -const setupVelocity = (velocity) => { +const bindEasingToVelocity = (velocity) => { velocity.Easings[EASE_IN_SKIP_OUT_EASING] = composeEasing( (time, opts, tweenDelta) => mapIntervalLinear( velocity.Easings['ease-in'](time, opts, tweenDelta), @@ -83,4 +83,4 @@ const setupVelocity = (velocity) => { ); }; -export default setupVelocity; +export default bindEasingToVelocity; diff --git a/src/easing/setupVelocity.d.ts b/src/easing/setupVelocity.d.ts deleted file mode 100644 index 7f66e6b6c14b071a30704e3ad54a74f1bcf2bf1a..0000000000000000000000000000000000000000 --- a/src/easing/setupVelocity.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -import * as Velocity from 'velocity-animate'; - -declare const setupVelocity: (velocity: typeof Velocity) => void; -export declare const EASE_IN_SKIP_OUT_EASING: string; -export default setupVelocity; diff --git a/src/esImportTest.ts b/src/esImportTest.ts new file mode 100644 index 0000000000000000000000000000000000000000..672ba18c036e12c2a5748aac1c6b64b168a426bb --- /dev/null +++ b/src/esImportTest.ts @@ -0,0 +1,7 @@ +// dummy test +// if import statements do not scream this test passes +import {handleOnLoadScroll} from './index'; +import {handleOnLinkClickScroll} from './index'; +import {scrollToElement} from './index'; +import {scrollToOffset} from './index'; +import {scrollToTarget} from './index'; diff --git a/src/handlers/linkClickScroll/unitTest.html b/src/handlers/linkClickScroll/unitTest.html index c53bbe8ff75c53b4cf07650a53f36efc6cc390b5..6f2311486390e83c9e58b2d6a7c19b554119eaf4 100644 --- a/src/handlers/linkClickScroll/unitTest.html +++ b/src/handlers/linkClickScroll/unitTest.html @@ -7,10 +7,7 @@ <script src="https://cdn.jsdelivr.net/npm/velocity-animate@1.5/velocity.min.js"></script> <script src="../../../dist/index.js"></script> <script> - SmoothScroll.enable({ - scrollOnLoad: false, - scrollOnLinkClick: true, - }); + SmoothScroll.handleOnLinkClickScroll(); </script> <style type="text/css"> body { diff --git a/src/handlers/loadScroll/unitTest.html b/src/handlers/loadScroll/unitTest.html index 5877c499aaa2424f8e19321776817e99dcc870a1..aa3bb3affa5c962e1b3e8ca4aa1d7e50ed478257 100644 --- a/src/handlers/loadScroll/unitTest.html +++ b/src/handlers/loadScroll/unitTest.html @@ -7,10 +7,7 @@ <script src="https://cdn.jsdelivr.net/npm/velocity-animate@1.5/velocity.min.js"></script> <script src="../../../dist/index.js"></script> <script> - SmoothScroll.enable({ - scrollOnLoad: true, - scrollOnLinkClick: false, - }); + SmoothScroll.handleOnLoadScroll(); </script> <style type="text/css"> body { diff --git a/src/index.ts b/src/index.ts index 134ba664c8d610fb7ab8ba69713cf08baf7495b1..2c0337517a877842ef2954f54c602ec4401cd99a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,30 +1,32 @@ import * as Velocity from 'velocity-animate'; -import setupVelocity from './easing/setupVelocity'; +import bindEasingToVelocity from './easing/bindEasingToVelocity'; import {initializeOnLoadScroll} from './handlers/loadScroll/initializeOnLoadScroll'; import {initializeOnLinkClickScroll} from './handlers/linkClickScroll/initializeOnLinkClickScroll'; +import {scrollToElement} from './scrollers/scrollToElement'; +import {scrollToOffset} from './scrollers/scrollToOffset'; +import {scrollToTarget} from './scrollers/scrollToTarget'; +import {HashTarget} from './HashTarget'; -export interface SmoothScrollOptions { - readonly scrollOnLoad?: boolean; - readonly scrollOnLinkClick?: boolean; -} -/** - * Wrapped into class for intuitive API use – `SmoothScroll.enable()` - */ -class SmoothScroll -{ - public static enable(options?: SmoothScrollOptions): void - { - setupVelocity(Velocity); +// bind automatically on library import +bindEasingToVelocity(Velocity); + - if ( ! (options && options.scrollOnLoad === false)) { - initializeOnLoadScroll(); - } +function handleOnLoadScroll(): void +{ + initializeOnLoadScroll(); +} - if ( ! (options && options.scrollOnLinkClick === false)) { - initializeOnLinkClickScroll(); - } - } +function handleOnLinkClickScroll(): void +{ + initializeOnLinkClickScroll(); } -export default SmoothScroll; +export { + HashTarget, + handleOnLoadScroll, + handleOnLinkClickScroll, + scrollToElement, + scrollToOffset, + scrollToTarget, +}; diff --git a/src/integrationTest.html b/src/integrationTest.html index 29398681cee6d4489b95fc27116af908eb47a6e0..9dbe03805a72d56fe18ac5e383aa1b16e7f28a3b 100644 --- a/src/integrationTest.html +++ b/src/integrationTest.html @@ -7,15 +7,27 @@ <script src="https://cdn.jsdelivr.net/npm/velocity-animate@1.5/velocity.min.js"></script> <script src="../dist/index.js" type="text/javascript"></script> <script> - SmoothScroll.enable({ - scrollOnLoad: true, - scrollOnLinkClick: true, + SmoothScroll.handleOnLoadScroll(); + SmoothScroll.handleOnLinkClickScroll(); + + document.addEventListener('DOMContentLoaded', () => { + document.getElementById('scrollToElement').addEventListener('click', (event) => { + event.preventDefault(); // prevent jump to top as there is `#` in `href` attribute + SmoothScroll.scrollToElement(document.getElementById('footer')); + }); + + document.getElementById('scrollToOffset').addEventListener('click', () => + SmoothScroll.scrollToOffset(5000)); + + document.getElementById('scrollToTarget').addEventListener('click', () => + SmoothScroll.scrollToTarget('#item3')); }); </script> <style type="text/css"> body { margin: 0 auto; max-width: 40rem; + padding-bottom: 150vh; } .goToTop { @@ -34,7 +46,6 @@ </style> </head> <body> -<!-- @todo: improve this file --> <div id="nahoru"></div> <a href="#item1">Item1</a> @@ -42,6 +53,9 @@ <a href="#item3">Item3</a> <a href="#item4">Item4</a> <a href="#item5">Item5</a> + <a href="#" id="scrollToElement">Scroll to element</a> + <button type="button" id="scrollToOffset">Scroll to 5000px top offset</button> + <a href="#" id="scrollToTarget">Scroll to target</a> <div> <div id="item1"> @@ -222,5 +236,15 @@ Go to top </a> </div> + + <div id="footer"> + <p> + Footer + </p> + + <p> + Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. + </p> + </div> </body> </html> diff --git a/src/scrollers/scrollToElement.ts b/src/scrollers/scrollToElement.ts new file mode 100644 index 0000000000000000000000000000000000000000..0d7f21c0704090aeab117db472f1ecf29899582d --- /dev/null +++ b/src/scrollers/scrollToElement.ts @@ -0,0 +1,11 @@ +import * as Velocity from 'velocity-animate'; +import {EASE_IN_SKIP_OUT_EASING} from '../easing/bindEasingToVelocity'; + +export function scrollToElement(element: HTMLElement, onScrollFinishedCallback?: () => void): void +{ + Velocity.animate(element, 'scroll', { + duration: 1200, // todo: different depending on offset from page top? + easing: EASE_IN_SKIP_OUT_EASING, + complete: () => onScrollFinishedCallback !== undefined && onScrollFinishedCallback(), + }); +} diff --git a/src/scrollers/scrollToElement.unitTest.html b/src/scrollers/scrollToElement.unitTest.html new file mode 100644 index 0000000000000000000000000000000000000000..2ca9acdcb380d8cc708f22eb2b9b5af7668f763e --- /dev/null +++ b/src/scrollers/scrollToElement.unitTest.html @@ -0,0 +1,63 @@ +<!doctype html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>scrollToElement() scroll test</title> + <script src="https://cdn.jsdelivr.net/npm/velocity-animate@1.5/velocity.min.js"></script> + <script src="../../dist/index.js"></script> + <style type="text/css"> + body { + margin: 0 auto; + max-width: 40rem; + padding-bottom: 200vh; /* make some space so that scrolling target can reach top of viewport */ + } + </style> +</head> +<body> + +<div> + <p> + <strong>Click button below. View should scroll down.</strong> + </p> + <button type="button" id="triggerer">▶ Run test</button> + <script> + document.getElementById('triggerer').addEventListener('click', () => + SmoothScroll.scrollToElement( + document.getElementById('test'), + () => document.getElementById('greenText').style.color = 'green', + ) + ); + </script> + + <!-- placeholder text so that custom easing is visible --> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + + <div id="test"> + <h1>Target on <code>SmoothScroll.scrollToElement()</code> call</h1> + <p> + <strong>If view was scrolled smoothly to heading above, you see it complete and <span id="greenText">this text is green</span>, the test is passing. ✔</strong> + </p> + </div> +</div> +</body> +</html> diff --git a/src/scrollers/scrollToOffset.ts b/src/scrollers/scrollToOffset.ts new file mode 100644 index 0000000000000000000000000000000000000000..ade7c3afb8b452b07e19e35c922707d2134dec19 --- /dev/null +++ b/src/scrollers/scrollToOffset.ts @@ -0,0 +1,19 @@ +import * as Velocity from 'velocity-animate'; +import {EASE_IN_SKIP_OUT_EASING} from '../easing/bindEasingToVelocity'; + +export function scrollToOffset( + topOffset: number, + onScrollFinishedCallback?: () => void, +): void +{ + /** + * Setting `<html>` as the element to scroll to with offset simulates `window.scrollTo()` behavior. + * See last paragraph at http://velocityjs.org/#scroll + */ + Velocity.animate(document.documentElement, 'scroll', { + duration: 1200, // todo: different depending on offset from page top? + offset: topOffset, + easing: EASE_IN_SKIP_OUT_EASING, + complete: onScrollFinishedCallback, + }); +} diff --git a/src/scrollers/scrollToOffset.unitTest.html b/src/scrollers/scrollToOffset.unitTest.html new file mode 100644 index 0000000000000000000000000000000000000000..7b067ad77bd3d1ab8a5869155a5d7ef180272fd0 --- /dev/null +++ b/src/scrollers/scrollToOffset.unitTest.html @@ -0,0 +1,46 @@ +<!doctype html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>scrollToOffset() scroll test</title> + <script src="https://cdn.jsdelivr.net/npm/velocity-animate@1.5/velocity.min.js"></script> + <script src="../../dist/index.js"></script> + <style type="text/css"> + body { + margin: 0 auto; + max-width: 40rem; + } + + .target { + position: absolute; + top: 5000px; + padding-bottom: 200vh; /* make some space so that scrolling target can reach top of viewport */ + } + </style> +</head> +<body> + +<div> + <p> + <strong>Click button below. View should scroll down.</strong> + </p> + <button type="button" id="triggerer">▶ Run test</button> + <script> + document.getElementById('triggerer').addEventListener('click', () => + SmoothScroll.scrollToOffset( + 5000, + () => document.getElementById('greenText').style.color = 'green', + ) + ); + </script> + + <div class="target"> + <h1>Target on <code>SmootScroll.scrollToOffset()</code> call</h1> + <p> + <strong>If view was scrolled smoothly to heading above, you see it complete and <span id="greenText">this text is green</span>, the test is passing. ✔</strong> + </p> + </div> +</div> +</body> +</html> diff --git a/src/scrollers/scrollToTarget.ts b/src/scrollers/scrollToTarget.ts index d9c9ffc7c5481c07442a836ea0559d6bc5c3c0a2..a31ec01a924844f14d9b9036a38710a438309ade 100644 --- a/src/scrollers/scrollToTarget.ts +++ b/src/scrollers/scrollToTarget.ts @@ -1,12 +1,19 @@ -import * as Velocity from 'velocity-animate'; import {HashTarget} from '../HashTarget'; -import {EASE_IN_SKIP_OUT_EASING} from '../easing/setupVelocity'; +import {scrollToElement} from './scrollToElement'; +import {assert} from '../assert'; -export function scrollToTarget(hashTarget: HashTarget): void +export function scrollToTarget(hashTarget: HashTarget|string, onScrollFinishedCallback?: () => void): void { - Velocity.animate(hashTarget.getElement(), 'scroll', { - duration: 1200, // todo: different depending on offset from page top? - easing: EASE_IN_SKIP_OUT_EASING, - complete: () => window.location.hash = hashTarget.getHash(), - }); + if (typeof hashTarget === 'string') { + hashTarget = HashTarget.fromString(hashTarget, document); + } + + scrollToElement( + hashTarget.getElement(), + () => { + assert(hashTarget instanceof HashTarget); + window.location.hash = hashTarget.getHash(); + onScrollFinishedCallback !== undefined && onScrollFinishedCallback(); + }, + ); } diff --git a/src/scrollers/scrollToTarget.unitTest.html b/src/scrollers/scrollToTarget.unitTest.html new file mode 100644 index 0000000000000000000000000000000000000000..19ac6537cfd26d0bc6393efaee0b24841121b6f4 --- /dev/null +++ b/src/scrollers/scrollToTarget.unitTest.html @@ -0,0 +1,63 @@ +<!doctype html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>scrollTarget() scroll test</title> + <script src="https://cdn.jsdelivr.net/npm/velocity-animate@1.5/velocity.min.js"></script> + <script src="../../dist/index.js"></script> + <style type="text/css"> + body { + margin: 0 auto; + max-width: 40rem; + padding-bottom: 200vh; /* make some space so that scrolling target can reach top of viewport */ + } + </style> +</head> +<body> + +<div> + <p> + <strong>Click button below. View should scroll down.</strong> + </p> + <button type="button" id="triggerer">▶ Run test</button> + <script> + document.getElementById('triggerer').addEventListener('click', () => + SmoothScroll.scrollToTarget( + '#test', + () => document.getElementById('greenText').style.color = 'green', + ) + ); + </script> + + <!-- placeholder text so that custom easing is visible --> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam ornare wisi eu metus. Pellentesque ipsum. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Donec ipsum massa, ullamcorper in, auctor et, scelerisque sed, est. Mauris tincidunt sem sed arcu. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Sed ac dolor sit amet purus malesuada congue. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris dolor felis, sagittis at, luctus sed, aliquam non, tellus. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Etiam posuere lacus quis dolor. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat. Integer tempor. Morbi scelerisque luctus velit. Sed vel lectus. Donec odio tempus molestie, porttitor ut, iaculis quis, sem. Suspendisse sagittis ultrices augue.</p> + + <div id="test"> + <h1>Target on <code>SmoothScroll.scrollToTarget()</code> call</h1> + <p> + <strong>If view was scrolled smoothly to heading above, you see it complete and <span id="greenText">this text is green</span>, the test is passing. ✔</strong> + </p> + </div> +</div> +</body> +</html>