UNPKG

just-add-juice

Version:

A responsive frontend framework with modular components.

550 lines (478 loc) 19 kB
/* ======================================================================== JUICE -> COMPONENTS -> SMOOTH SCROLL ======================================================================== */ ;(function (root, factory) { // Set the plugin name const plugin_name = 'SmoothScroll'; // Check if instantiation should be via amd, commonjs or the browser if (typeof define === 'function' && define.amd) { define([], factory(plugin_name)); } else if (typeof exports === 'object') { module.exports = factory(plugin_name); } else { root[plugin_name] = factory(plugin_name); } }((window || module || {}), function(plugin_name) { // Use strict mode 'use strict'; // Create an empty plugin object const plugin = {}; // Set the plugin defaults const defaults = { onLoad: true, onLoadSilently: true, history: true, historyBottom: '#bottom', historyTop: '#top', targetHorizontal: 'center', targetVertical: 'center', callbackInitializeBefore: () => { console.log('Smooth Scroll: callbackInitializeBefore'); }, callbackInitializeAfter: () => { console.log('Smooth Scroll: callbackInitializeAfter'); }, callbackScrollBefore: () => { console.log('Smooth Scroll: callbackScrollBefore'); }, callbackScrollAfter: () => { console.log('Smooth Scroll: callbackScrollAfter'); }, callbackScrollBottomBefore: () => { console.log('Smooth Scroll: callbackScrollBottomBefore'); }, callbackScrollBottomAfter: () => { console.log('Smooth Scroll: callbackScrollBottomAfter'); }, callbackScrollTopBefore: () => { console.log('Smooth Scroll: callbackScrollTopBefore'); }, callbackScrollTopAfter: () => { console.log('Smooth Scroll: callbackScrollTopAfter'); }, callbackHistoryBefore: () => { console.log('Smooth Scroll: callbackHistoryBefore'); }, callbackHistoryAfter: () => { console.log('Smooth Scroll: callbackHistoryAfter'); }, callbackRefreshBefore: () => { console.log('Smooth Scroll: callbackRefreshBefore'); }, callbackRefreshAfter: () => { console.log('Smooth Scroll: callbackRefreshAfter'); }, callbackDestroyBefore: () => { console.log('Smooth Scroll: callbackDestroyBefore'); }, callbackDestroyAfter: () => { console.log('Smooth Scroll: callbackDestroyAfter'); } }; /** * Constructor. * @param {object} options The plugin options. * @return {void} */ function Plugin(options) { // Set the plugin object plugin.this = this; plugin.name = plugin_name; plugin.defaults = defaults; plugin.options = options; plugin.settings = Object.assign({}, defaults, options); // Initialize the plugin plugin.this.initialize(); } /** * Set the target horizontal and vertical positions after scroll. * @param {element} $target The target. * @return {object} The positions. */ const setTargetPositions = ($target) => { // Set the target position properties let target_horizontal; let target_vertical; // Check if the target exists if (document.body.contains($target)) { // Set the target horizontal scroll position after scroll target_horizontal = $target.dataset.smoothScrollTargetHorizontal || plugin.settings.targetHorizontal // Set the target vertical scroll position after scroll target_vertical = $target.dataset.smoothScrollTargetVertical || plugin.settings.targetVertical } // Return the positions return { target_horizontal, target_vertical }; }; /** * Update the browser history. * @param {string} hash The hash. * @return {void} */ const updateBrowserHistory = (hash) => { // Check if the browser history should be updated if (plugin.settings.history) { // Update the browser history history.pushState(null, null, hash); } }; /** * Smooth scroll to an anchor on page load. * @return {void} */ const smoothScrollToAnchorOnLoad = () => { // Check to see if a hash exists in the url if (location.hash) { // Start a timer setTimeout(() => { // Scroll to the top window.scrollTo({ behavior: 'auto', left: 0, top: 0 }); // Set the hash const hash = window.location.hash.split('#')[1]; // Set the target element const $target = document.querySelector(`#${hash}`); // Set the target positions const positions = setTargetPositions($target); // Smooth scroll to the target plugin.this.scroll(`#${hash}`, $target, positions, plugin.settings.onLoadSilently); }, 1); } }; /** * Event handler to smooth scroll to a specific anchor on click. * @param {object} event The event object. * @return {void} */ const clickSmoothScrollEventHandler = (event) => { // Set the container let $container; // Set the clicked element const $clicked = event.target; // Start a switch statement for the following true values switch (true) { // Default default: // Check if the element is an anchor tag if ($clicked.nodeName == 'A') { // Set the element href const href = $clicked.getAttribute('href'); // Check if the href starts with a hash and isn't an empty hash if (href.startsWith('#') && href != '#') { // Prevent the default behaviour event.preventDefault(); // Set the target const $target = document.querySelector(href); // Set the target positions const positions = setTargetPositions($target); // Smooth scroll to the target plugin.this.scroll(href, $target, positions); } } break; // Scroll to element case $clicked.classList.contains('js-smooth-scroll-element'): // Prevent the default behaviour event.preventDefault(); // Set the hash const hash = $clicked.dataset.smoothScrollTarget; // Set the target const $target = document.querySelector(hash); // Set the target positions const positions = setTargetPositions($target); // Smooth scroll to the target plugin.this.scroll(hash, $target, positions); break; // Scroll to top case $clicked.classList.contains('js-smooth-scroll-top'): // Prevent the default behaviour event.preventDefault(); // Set the container $container = document.querySelector($clicked.dataset.smoothScrollContainer) || window; // Smooth scroll to top plugin.this.scrollTop(plugin.settings.historyTop, $container); break; // Scroll to bottom case $clicked.classList.contains('js-smooth-scroll-bottom'): // Prevent the default behaviour event.preventDefault(); // Set the container $container = document.querySelector($clicked.dataset.smoothScrollContainer) || window; // Smooth scroll to bottom plugin.this.scrollBottom(plugin.settings.historyBottom, $container); break; } }; /** * Public variables and methods. * @type {object} */ Plugin.prototype = { /** * Initialize the plugin. * @param {bool} silent Suppress callbacks. * @return {void} */ initialize: (silent = false) => { // Destroy the existing initialization silently plugin.this.destroySilently(); // Check if the callbacks should not be suppressed if (!silent) { // Call the initialize before callback plugin.settings.callbackInitializeBefore.call(); } // Check if smooth scrolling should if (plugin.settings.onLoad) { // Smooth scroll to an anchor on page load smoothScrollToAnchorOnLoad(); } // Add a click event handler to the body to smooth scroll to anchors/elements document.body.addEventListener('click', clickSmoothScrollEventHandler); // Check if the callbacks should not be suppressed if (!silent) { // Call the initialize after callback plugin.settings.callbackInitializeAfter.call(); } }, /** * Smooth scroll to a target. * @param {string} hash The browser history hash. * @param {element} $target The target. * @param {object} positions The targets horizontal and vertical positions after scroll. * @param {bool} silent Suppress callbacks. * @return {void} */ scroll: (hash, $target, positions = {}, silent = false) => { // Check if the callbacks should not be suppressed if (!silent) { // Call the scroll before callback plugin.settings.callbackScrollBefore.call(); } // Check if the element exists if (document.body.contains($target)) { // Scroll the target into view $target.scrollIntoView({ behavior: 'smooth', block: positions.target_vertical, inline: positions.target_horizontal }); // Update the browser history updateBrowserHistory(hash); } // Check if the callbacks should not be suppressed if (!silent) { // Call the scroll after callback plugin.settings.callbackScrollAfter.call(); } }, /** * Smooth scroll the window or container to the top. * @param {string} hash The browser history hash. * @param {mixed} $container The container element or window. * @param {bool} silent Suppress callbacks. * @return {void} */ scrollTop: (hash, $container = window, silent = false) => { // Check if the callbacks should not be suppressed if (!silent) { // Call the scroll top before callback plugin.settings.callbackScrollTopBefore.call(); } // Start a switch statement for the container switch ($container) { // Default default: // Check if the container exists if (document.body.contains($container)) { // Scroll the container to the top $container.scrollTo({ behavior: 'smooth', left: 0, top: 0 }); } break; // Window case (window): // Scroll the window to the top window.scrollTo({ behavior: 'smooth', left: 0, top: 0 }); // Update the browser history updateBrowserHistory(hash); break; } // Check if the callbacks should not be suppressed if (!silent) { // Call the scroll top after callback plugin.settings.callbackScrollTopAfter.call(); } }, /** * Smooth scroll the window or container to the bottom. * @param {string} hash The browser history hash. * @param {mixed} $container The container element or window. * @param {bool} silent Suppress callbacks. * @return {void} */ scrollBottom: (hash, $container = window, silent = false) => { // Check if the callbacks should not be suppressed if (!silent) { // Call the scroll bottom before callback plugin.settings.callbackScrollBottomBefore.call(); } // Start a switch statement for the container switch ($container) { // Default default: // Check if the container exists if (document.body.contains($container)) { // Create a heights array const heights = [ $container.offsetHeight, $container.scrollHeight ]; // Set the bottom const bottom = Math.max(...heights); // Scroll the container to the bottom $container.scrollTo({ behavior: 'smooth', left: 0, top: `${bottom}` }); } break; // Window case (window): // Create a heights array const heights = [ document.body.offsetHeight, document.body.scrollHeight, document.documentElement.clientHeight, document.documentElement.offsetHeight, document.documentElement.scrollHeight ]; // Set the bottom const bottom = Math.max(...heights); // Scroll the window to the bottom window.scrollTo({ behavior: 'smooth', left: 0, top: `${bottom}` }); // Update the browser history updateBrowserHistory(hash); break; } // Check if the callbacks should not be suppressed if (!silent) { // Call the scroll bottom after callback plugin.settings.callbackScrollBottomAfter.call(); } }, /** * Refresh the plugins initialization. * @param {bool} silent Suppress callbacks. * @return {void} */ refresh: (silent = false) => { // Check if the callbacks should not be suppressed if (!silent) { // Call the refresh before callback plugin.settings.callbackRefreshBefore.call(); } // Destroy the existing initialization plugin.this.destroy(silent); // Initialize the plugin plugin.this.initialize(silent); // Check if the callbacks should not be suppressed if (!silent) { // Call the refresh after callback plugin.settings.callbackRefreshAfter.call(); } }, /** * Destroy an existing initialization. * @param {bool} silent Suppress callbacks. * @return {void} */ destroy: (silent = false) => { // Check if the callbacks should not be suppressed if (!silent) { // Call the destroy before callback plugin.settings.callbackDestroyBefore.call(); } // Remove the click event handler from the body document.body.addEventListener('click', clickSmoothScrollEventHandler); // Check if the callbacks should not be suppressed if (!silent) { // Call the destroy after callback plugin.settings.callbackDestroyAfter.call(); } }, /** * Call the scroll method silently. * @param {string} hash The browser history hash. * @param {element} $target The target. * @param {object} positions The targets horizontal and vertical positions after scroll. * @return {void} */ scrollSilently: (hash, $target, positions = {}) => { // Call the scroll method silently plugin.this.scroll(hash, $target, positions, true); }, /** * Call the scroll top method silently. * @param {string} hash The browser history hash. * @param {mixed} $container The container element or window. * @return {void} */ scrollTopSilently: (hash, $container = window) => { // Call the scroll top method silently plugin.this.scrollTop(hash, $container, true); }, /** * Call the scroll bottom method silently. * @param {string} hash The browser history hash. * @param {mixed} $container The container element or window. * @return {void} */ scrollBottomSilently: (hash, $container = window) => { // Call the scroll top method silently plugin.this.scrollBottom(hash, $container, true); }, /** * Call the refresh method silently. * @return {void} */ refreshSilently: () => { // Call the refresh method silently plugin.this.refresh(true); }, /** * Call the destroy method silently. * @return {void} */ destroySilently: () => { // Call the destroy method silently plugin.this.destroy(true); } }; // Return the plugin return Plugin; }));