/** * Vanilla JS Smooth Scroll * Author: Bastian Fießinger * Version: 1.0.1 */ 'use strict'; HTMLAnchorElement.prototype.scrollToSmooth = function (settings) { if (typeof (settings) == 'undefined') { settings = {}; } // Setup Default Settings Object let defaults = { speed: 400, easing: 'linear', callback: null, fixedHeader: null }; /** * Merge two or more objects together. * @param {Object} objects The objects to merge together * @returns {Object} Merged values of defaults and settings */ let extend = function () { var merged = {}; Array.prototype.forEach.call(arguments, (obj) => { for (var key in obj) { if (!obj.hasOwnProperty(key)) return; merged[key] = obj[key]; } }); return merged; }; // Available Easing Functions const easings = { linear(t) { return t; }, easeInQuad(t) { return t * t; }, easeOutQuad(t) { return t * (2 - t); }, easeInOutQuad(t) { return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t; }, easeInCubic(t) { return t * t * t; }, easeOutCubic(t) { return (--t) * t * t + 1; }, easeInOutCubic(t) { return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1; }, easeInQuart(t) { return t * t * t * t; }, easeOutQuart(t) { return 1 - (--t) * t * t * t; }, easeInOutQuart(t) { return t < 0.5 ? 8 * t * t * t * t : 1 - 8 * (--t) * t * t * t; }, easeInQuint(t) { return t * t * t * t * t; }, easeOutQuint(t) { return 1 + (--t) * t * t * t * t; }, easeInOutQuint(t) { return t < 0.5 ? 16 * t * t * t * t * t : 1 + 16 * (--t) * t * t * t * t; }, easeInElastic: function (t) { return (.04 - .04 / t) * Math.sin(25 * t) + 1 }, easeOutElastic: function (t) { return .04 * t / (--t) * Math.sin(25 * t) }, easeInOutElastic: function (t) { return (t -= .5) < 0 ? (.02 + .01 / t) * Math.sin(50 * t) : (.02 - .01 / t) * Math.sin(50 * t) + 1 } }; // Build the Settings Object from Defaults and user defined settings settings = extend(defaults, settings || {}); const reqAnimFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; const cancelAnimFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame; // Get All Links with hashes based on the current URI const findHashLinks = () => { // Make sure to only apply to hash links if (this.href.indexOf(this.baseURI.replace(/\/+$/, '')) != -1 && this.href.indexOf('#') != -1 && this.hash != '') { this.addEventListener('click', clickHandler); } } const clickHandler = (e) => { // Prevent Default Behaviour of how the browser would treat the click event e.preventDefault(); // Evaluate the current Target Element const currentTargetIdSliced = this.hash.slice(1); let currentTarget = document.getElementById(currentTargetIdSliced); // If no ID for the current target is present try to find an element with it's name attribute. currentTarget = currentTarget ? currentTarget : document.querySelector('[name="' + currentTargetIdSliced + '"]'); if (!currentTarget) return; const windowStartPos = window.pageYOffset; const startTime = 'now' in window.performance ? performance.now() : new Date().getTime(); const docHeight = Math.max(document.body.scrollHeight, document.body.offsetHeight, document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight); const winHeight = window.innerHeight || document.documentElement.clientHeight || document.getElementsByTagName('body')[0].clientHeight; const targetOffset = currentTarget.offsetTop; let distToScroll = Math.round(docHeight - targetOffset < winHeight ? docHeight - winHeight : targetOffset); if (settings.fixedHeader !== null) { const fixedHeader = document.querySelector(settings.fixedHeader); if (fixedHeader.tagName) { distToScroll -= Math.round(fixedHeader.getBoundingClientRect().height); } } // Distance can't be negative distToScroll = (distToScroll < 0) ? 0 : distToScroll; scrollToTarget(0, distToScroll, windowStartPos, startTime); } // Animate the ScrollTop const scrollToTarget = (timestamp, distToScroll, startPos, startTime) => { const now = 'now' in window.performance ? performance.now() : new Date().getTime(); const time = Math.min(1, ((now - startTime) / settings.speed)); const timeFunction = easings[settings.easing](time); let curScrollPosition = window.pageYOffset || document.body.scrollTop || document.documentElement.scrollTop; window.scroll(0, Math.ceil((timeFunction * (distToScroll - startPos)) + startPos)); if (Math.ceil(curScrollPosition) === distToScroll) { if (settings.callback) { settings.callback(); } // Stop when the element is reached return; } let scrollAnimationFrame = reqAnimFrame((timestamp) => { scrollToTarget(timestamp, distToScroll, startPos, startTime); }); // Cancel Animation on User Scroll Interaction let cancelAnimationOnEvents = ['mousewheel', 'wheel', 'touchstart']; cancelAnimationOnEvents.forEach((ev) => { window.addEventListener(ev, () => { cancelAnimationFrame( scrollAnimationFrame ); }); }); } const BindEvents = () => { window.addEventListener('load', findHashLinks); } this.init = () => { // Bind Events BindEvents.call(this); }; this.init(); } document.addEventListener('DOMContentLoaded', function () { let links = document.getElementsByTagName('a'); [].forEach.call(links, (el) => { el.scrollToSmooth({ speed: 500, easing: 'easeInOutQuint', callback: function () { console.log('we reached it!'); }, fixedHeader: null }); }); // Init Highlight JS hljs.initHighlightingOnLoad(); });