web-html5-scripts/web-techniques/vanilla-js-smooth-scroll/script.js

176 lines
5.8 KiB
JavaScript
Raw Normal View History

2020-01-20 21:00:54 +01:00
/**
* 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();
});