Skip to content

Instantly share code, notes, and snippets.

@mabrodeur
Created April 3, 2019 15:28
Show Gist options
  • Save mabrodeur/535908298f196343bec6ced3a9587a7a to your computer and use it in GitHub Desktop.
Save mabrodeur/535908298f196343bec6ced3a9587a7a to your computer and use it in GitHub Desktop.
// EDITED BY : Lima Carlie
// Handle passive wheel event correctly
// Also completely reset variables on destroy so it works correctly on ajax websites
/*!
* jQuery Scrollify
* Version 1.0.19
*
* Requires:
* - jQuery 1.7 or higher
*
* https://github.com/lukehaas/Scrollify
*
* Copyright 2016, Luke Haas
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
if touchScroll is false - update index
*/
(function (global, factory) {
"use strict";
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['jquery'], function ($) {
return factory($, global, global.document);
});
} else if (typeof module === 'object' && module.exports) {
// Node/CommonJS
module.exports = factory(require('jquery'), global, global.document);
} else {
// Browser globals
factory(jQuery, global, global.document);
}
}(typeof window !== 'undefined' ? window : this, function ($, window, document, undefined) {
"use strict";
var heights = [],
names = [],
elements = [],
overflow = [],
index = 0,
currentIndex = 0,
interstitialIndex = 1,
hasLocation = false,
timeoutId,
timeoutId2,
$window = $(window),
portHeight,
top = $window.scrollTop(),
scrollable = false,
locked = false,
scrolled = false,
manualScroll,
swipeScroll,
util,
disabled = false,
scrollSamples = [],
scrollTime = new Date().getTime(),
firstLoad = true,
initialised = false,
destination = 0,
wheelEvent = 'onwheel' in document ? 'wheel' : document.onmousewheel !== undefined ? 'mousewheel' : 'DOMMouseScroll',
settings = {
//section should be an identifier that is the same for each section
section: ".section",
sectionName: "section-name",
interstitialSection: "",
easing: "easeOutExpo",
scrollSpeed: 1100,
offset: 0,
scrollbars: true,
target: "html,body",
standardScrollElements: false,
setHeights: true,
overflowScroll: true,
updateHash: true,
touchScroll: true,
before: function () {
},
after: function () {
},
afterResize: function () {
},
afterRender: function () {
}
};
function getportHeight() {
return ($window.height() + settings.offset);
}
function animateScroll(index, instant, callbacks, toTop) {
if (currentIndex === index) {
callbacks = false;
}
if (disabled === true) {
return true;
}
if (names[index]) {
scrollable = false;
if (firstLoad === true) {
firstLoad = false;
settings.afterRender();
}
if (callbacks) {
if (typeof settings.before == 'function' && settings.before(index, elements) === false) {
return true;
}
}
interstitialIndex = 1;
destination = heights[index];
if (firstLoad === false && currentIndex > index && toTop === false) {
//We're going backwards
if (overflow[index]) {
portHeight = getportHeight();
interstitialIndex = parseInt(elements[index].outerHeight() / portHeight);
destination = parseInt(heights[index]) + (elements[index].outerHeight() - portHeight);
}
}
if (settings.updateHash && settings.sectionName && !(firstLoad === true && index === 0)) {
if (history.pushState) {
try {
history.replaceState(null, null, names[index]);
} catch (e) {
if (window.console) {
console.warn("Scrollify warning: Page must be hosted to manipulate the hash value.");
}
}
} else {
window.location.hash = names[index];
}
}
currentIndex = index;
if (instant) {
$(settings.target).stop().scrollTop(destination);
if (callbacks) {
settings.after(index, elements);
}
} else {
locked = true;
if ($().velocity) {
$(settings.target).stop().velocity('scroll', {
duration: settings.scrollSpeed,
easing: settings.easing,
offset: destination,
mobileHA: false
});
} else {
$(settings.target).stop().animate({
scrollTop: destination
}, settings.scrollSpeed, settings.easing);
}
if (window.location.hash.length && settings.sectionName && window.console) {
try {
if ($(window.location.hash).length) {
console.warn("Scrollify warning: ID matches hash value - this will cause the page to anchor.");
}
} catch (e) {
}
}
$(settings.target).promise().done(function () {
locked = false;
// firstLoad = false;
if (callbacks) {
settings.after(index, elements);
}
});
}
}
}
function isAccelerating(samples) {
function average(num) {
var sum = 0;
var lastElements = samples.slice(Math.max(samples.length - num, 1));
for (var i = 0; i < lastElements.length; i++) {
sum += lastElements[i];
}
return Math.ceil(sum / num);
}
var avEnd = average(10);
var avMiddle = average(70);
if (avEnd >= avMiddle) {
return true;
} else {
return false;
}
}
var scrollify = function (options) {
initialised = true;
$.easing['easeOutExpo'] = function (x, t, b, c, d) {
return (t == d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b;
};
manualScroll = {
handleMousedown: function () {
if (disabled === true) {
return true;
}
scrollable = false;
scrolled = false;
},
handleMouseup: function () {
if (disabled === true) {
return true;
}
scrollable = true;
if (scrolled) {
//instant,callbacks
manualScroll.calculateNearest(false, true);
}
},
handleScroll: function () {
if (disabled === true) {
return true;
}
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(function () {
scrolled = true;
if (scrollable === false) {
return false;
}
scrollable = false;
//instant,callbacks
manualScroll.calculateNearest(false, true);
}, 200);
},
calculateNearest: function (instant, callbacks) {
top = $window.scrollTop();
var i = 1,
max = heights.length,
closest = 0,
prev = Math.abs(heights[0] - top),
diff;
for (; i < max; i++) {
diff = Math.abs(heights[i] - top);
if (diff < prev) {
prev = diff;
closest = i;
}
}
if ((atBottom() && closest > index) || atTop()) {
index = closest;
//index, instant, callbacks, toTop
animateScroll(closest, instant, callbacks, false);
}
},
wheelHandler: function (e) {
if (disabled === true) {
return true;
} else if (settings.standardScrollElements) {
if ($(e.target).is(settings.standardScrollElements) || $(e.target).closest(settings.standardScrollElements).length) {
return true;
}
}
var currentScrollTime = new Date().getTime();
e = e || window.event;
if (!overflow[index]) {
e.preventDefault();
}
var value = e.wheelDelta || -e.deltaY || -e.detail;
var delta = Math.max(-1, Math.min(1, value));
//delta = delta || -e.originalEvent.detail / 3 || e.originalEvent.wheelDelta / 120;
if (scrollSamples.length > 149) {
scrollSamples.shift();
}
//scrollSamples.push(Math.abs(delta*10));
scrollSamples.push(Math.abs(value));
if ((currentScrollTime - scrollTime) > 200) {
scrollSamples = [];
}
scrollTime = currentScrollTime;
if (locked) {
return false;
}
if (delta < 0) {
if (index < heights.length - 1) {
if (atBottom()) {
if (isAccelerating(scrollSamples)) {
e.preventDefault();
index++;
locked = true;
//index, instant, callbacks, toTop
animateScroll(index, false, true, false);
} else {
return false;
}
}
}
} else if (delta > 0) {
if (index > 0) {
if (atTop()) {
if (isAccelerating(scrollSamples)) {
e.preventDefault();
index--;
locked = true;
//index, instant, callbacks, toTop
animateScroll(index, false, true, false);
} else {
return false
}
}
}
}
},
keyHandler: function (e) {
if (disabled === true || document.activeElement.readOnly === false) {
return true;
} else if (settings.standardScrollElements) {
if ($(e.target).is(settings.standardScrollElements) || $(e.target).closest(settings.standardScrollElements).length) {
return true;
}
}
if (locked === true) {
return false;
}
if (e.keyCode == 38 || e.keyCode == 33) {
if (index > 0) {
if (atTop()) {
e.preventDefault();
index--;
//index, instant, callbacks, toTop
animateScroll(index, false, true, false);
}
}
} else if (e.keyCode == 40 || e.keyCode == 34) {
if (index < heights.length - 1) {
if (atBottom()) {
e.preventDefault();
index++;
//index, instant, callbacks, toTop
animateScroll(index, false, true, false);
}
}
}
},
init: function () {
if (settings.scrollbars) {
$window.on('mousedown', manualScroll.handleMousedown);
$window.on('mouseup', manualScroll.handleMouseup);
$window.on('scroll', manualScroll.handleScroll);
} else {
$("body").css({"overflow": "hidden"});
}
// $window.on(wheelEvent,manualScroll.wheelHandler);
$window.get(0).addEventListener(wheelEvent, manualScroll.wheelHandler, {
capture: true,
passive: false
});
//$(document).bind(wheelEvent,manualScroll.wheelHandler);
$window.on('keydown', manualScroll.keyHandler);
}
};
swipeScroll = {
touches: {
"touchstart": {"y": -1, "x": -1},
"touchmove": {"y": -1, "x": -1},
"touchend": false,
"direction": "undetermined"
},
options: {
"distance": 30,
"timeGap": 800,
"timeStamp": new Date().getTime()
},
touchHandler: function (event) {
if (disabled === true) {
return true;
} else if (settings.standardScrollElements) {
if ($(event.target).is(settings.standardScrollElements) || $(event.target).closest(settings.standardScrollElements).length) {
return true;
}
}
var touch;
if (typeof event !== 'undefined') {
if (typeof event.touches !== 'undefined') {
touch = event.touches[0];
switch (event.type) {
case 'touchstart':
swipeScroll.touches.touchstart.y = touch.pageY;
swipeScroll.touches.touchmove.y = -1;
swipeScroll.touches.touchstart.x = touch.pageX;
swipeScroll.touches.touchmove.x = -1;
swipeScroll.options.timeStamp = new Date().getTime();
swipeScroll.touches.touchend = false;
case 'touchmove':
swipeScroll.touches.touchmove.y = touch.pageY;
swipeScroll.touches.touchmove.x = touch.pageX;
if (swipeScroll.touches.touchstart.y !== swipeScroll.touches.touchmove.y && (Math.abs(swipeScroll.touches.touchstart.y - swipeScroll.touches.touchmove.y) > Math.abs(swipeScroll.touches.touchstart.x - swipeScroll.touches.touchmove.x))) {
//if(!overflow[index]) {
event.preventDefault();
//}
swipeScroll.touches.direction = "y";
if ((swipeScroll.options.timeStamp + swipeScroll.options.timeGap) < (new Date().getTime()) && swipeScroll.touches.touchend == false) {
swipeScroll.touches.touchend = true;
if (swipeScroll.touches.touchstart.y > -1) {
if (Math.abs(swipeScroll.touches.touchmove.y - swipeScroll.touches.touchstart.y) > swipeScroll.options.distance) {
if (swipeScroll.touches.touchstart.y < swipeScroll.touches.touchmove.y) {
swipeScroll.up();
} else {
swipeScroll.down();
}
}
}
}
}
break;
case 'touchend':
if (swipeScroll.touches[event.type] === false) {
swipeScroll.touches[event.type] = true;
if (swipeScroll.touches.touchstart.y > -1 && swipeScroll.touches.touchmove.y > -1 && swipeScroll.touches.direction === "y") {
if (Math.abs(swipeScroll.touches.touchmove.y - swipeScroll.touches.touchstart.y) > swipeScroll.options.distance) {
if (swipeScroll.touches.touchstart.y < swipeScroll.touches.touchmove.y) {
swipeScroll.up();
} else {
swipeScroll.down();
}
}
swipeScroll.touches.touchstart.y = -1;
swipeScroll.touches.touchstart.x = -1;
swipeScroll.touches.direction = "undetermined";
}
}
default:
break;
}
}
}
},
down: function () {
if (index < heights.length) {
if (atBottom() && index < heights.length - 1) {
index++;
//index, instant, callbacks, toTop
animateScroll(index, false, true, false);
} else {
portHeight = getportHeight();
if (Math.floor(elements[index].height() / portHeight) > interstitialIndex) {
interstitialScroll(parseInt(heights[index]) + (portHeight * interstitialIndex));
interstitialIndex += 1;
} else {
interstitialScroll(parseInt(heights[index]) + (elements[index].outerHeight() - portHeight));
}
}
}
},
up: function () {
if (index >= 0) {
if (atTop() && index > 0) {
index--;
//index, instant, callbacks, toTop
animateScroll(index, false, true, false);
} else {
if (interstitialIndex > 2) {
portHeight = getportHeight();
interstitialIndex -= 1;
interstitialScroll(parseInt(heights[index]) + (portHeight * interstitialIndex));
} else {
interstitialIndex = 1;
interstitialScroll(parseInt(heights[index]));
}
}
}
},
init: function () {
if (document.addEventListener && settings.touchScroll) {
var eventListenerOptions = {
passive: false
};
document.addEventListener('touchstart', swipeScroll.touchHandler, eventListenerOptions);
document.addEventListener('touchmove', swipeScroll.touchHandler, eventListenerOptions);
document.addEventListener('touchend', swipeScroll.touchHandler, eventListenerOptions);
}
}
};
util = {
refresh: function (withCallback, scroll) {
clearTimeout(timeoutId2);
timeoutId2 = setTimeout(function () {
//retain position
sizePanels(true);
//scroll, firstLoad
calculatePositions(scroll, false);
if (withCallback) {
settings.afterResize();
}
}, 400);
},
handleUpdate: function () {
//callbacks, scroll
//changed from false,true to false,false
util.refresh(false, false);
},
handleResize: function () {
//callbacks, scroll
util.refresh(true, false);
},
handleOrientation: function () {
//callbacks, scroll
util.refresh(true, true);
}
};
settings = $.extend(settings, options);
//retain position
sizePanels(false);
calculatePositions(false, true);
if (true === hasLocation) {
//index, instant, callbacks, toTop
animateScroll(index, false, true, true);
} else {
setTimeout(function () {
//instant,callbacks
manualScroll.calculateNearest(true, false);
}, 200);
}
if (heights.length) {
manualScroll.init();
swipeScroll.init();
$window.on("resize", util.handleResize);
if (document.addEventListener) {
window.addEventListener("orientationchange", util.handleOrientation, false);
}
}
function interstitialScroll(pos) {
if ($().velocity) {
$(settings.target).stop().velocity('scroll', {
duration: settings.scrollSpeed,
easing: settings.easing,
offset: pos,
mobileHA: false
});
} else {
$(settings.target).stop().animate({
scrollTop: pos
}, settings.scrollSpeed, settings.easing);
}
}
function sizePanels(keepPosition) {
if (keepPosition) {
top = $window.scrollTop();
}
var selector = settings.section;
overflow = [];
if (settings.interstitialSection.length) {
selector += "," + settings.interstitialSection;
}
if (settings.scrollbars === false) {
settings.overflowScroll = false;
}
portHeight = getportHeight();
$(selector).each(function (i) {
var $this = $(this);
if (settings.setHeights) {
if ($this.is(settings.interstitialSection)) {
overflow[i] = false;
} else {
if (($this.css("height", "auto").outerHeight() < portHeight) || $this.css("overflow") === "hidden") {
$this.css({"height": portHeight});
overflow[i] = false;
} else {
$this.css({"height": $this.outerHeight()});
if (settings.overflowScroll) {
overflow[i] = true;
} else {
overflow[i] = false;
}
}
}
} else {
if (($this.outerHeight() < portHeight) || (settings.overflowScroll === false)) {
overflow[i] = false;
} else {
overflow[i] = true;
}
}
});
if (keepPosition) {
$window.scrollTop(top);
}
}
function calculatePositions(scroll, firstLoad) {
var selector = settings.section;
if (settings.interstitialSection.length) {
selector += "," + settings.interstitialSection;
}
heights = [];
names = [];
elements = [];
$(selector).each(function (i) {
var $this = $(this);
if (i > 0) {
heights[i] = parseInt($this.offset().top) + settings.offset;
} else {
heights[i] = parseInt($this.offset().top);
}
if (settings.sectionName && $this.data(settings.sectionName)) {
names[i] = "#" + $this.data(settings.sectionName).toString().replace(/ /g, "-");
} else {
if ($this.is(settings.interstitialSection) === false) {
names[i] = "#" + (i + 1);
} else {
names[i] = "#";
if (i === $(selector).length - 1 && i > 1) {
heights[i] = heights[i - 1] + (parseInt($($(selector)[i - 1]).outerHeight()) - parseInt($(window).height())) + parseInt($this.outerHeight());
}
}
}
elements[i] = $this;
try {
if ($(names[i]).length && window.console) {
console.warn("Scrollify warning: Section names can't match IDs - this will cause the browser to anchor.");
}
} catch (e) {
}
if (window.location.hash === names[i]) {
index = i;
hasLocation = true;
}
});
if (true === scroll) {
//index, instant, callbacks, toTop
animateScroll(index, false, false, false);
}
}
function atTop() {
if (!overflow[index]) {
return true;
}
top = $window.scrollTop();
if (top > parseInt(heights[index])) {
return false;
} else {
return true;
}
}
function atBottom() {
if (!overflow[index]) {
return true;
}
top = $window.scrollTop();
portHeight = getportHeight();
if (top < parseInt(heights[index]) + (elements[index].outerHeight() - portHeight) - 28) {
return false;
} else {
return true;
}
}
}
function move(panel, instant) {
var z = names.length;
for (; z >= 0; z--) {
if (typeof panel === 'string') {
if (names[z] === panel) {
index = z;
//index, instant, callbacks, toTop
animateScroll(z, instant, true, true);
}
} else {
if (z === panel) {
index = z;
//index, instant, callbacks, toTop
animateScroll(z, instant, true, true);
}
}
}
}
scrollify.settings = settings;
scrollify.move = function (panel) {
if (panel === undefined) {
return false;
}
if (panel.originalEvent) {
panel = $(this).attr("href");
}
move(panel, false);
};
scrollify.instantMove = function (panel) {
if (panel === undefined) {
return false;
}
move(panel, true);
};
scrollify.next = function () {
if (index < names.length) {
index += 1;
//index, instant, callbacks, toTop
animateScroll(index, false, true, true);
}
};
scrollify.previous = function () {
if (index > 0) {
index -= 1;
//index, instant, callbacks, toTop
animateScroll(index, false, true, true);
}
};
scrollify.instantNext = function () {
if (index < names.length) {
index += 1;
//index, instant, callbacks, toTop
animateScroll(index, true, true, true);
}
};
scrollify.instantPrevious = function () {
if (index > 0) {
index -= 1;
//index, instant, callbacks, toTop
animateScroll(index, true, true, true);
}
};
scrollify.destroy = function () {
if (!initialised) {
return false;
}
if (settings.setHeights) {
$(settings.section).each(function () {
$(this).css("height", "auto");
});
}
$window.off("resize", util.handleResize);
if (settings.scrollbars) {
$window.off('mousedown', manualScroll.handleMousedown);
$window.off('mouseup', manualScroll.handleMouseup);
$window.off('scroll', manualScroll.handleScroll);
}
// $window.off(wheelEvent,manualScroll.wheelHandler);
$window.get(0).removeEventListener(wheelEvent, manualScroll.wheelHandler, {
capture: true,
passive: false
});
$window.off('keydown', manualScroll.keyHandler);
if (document.addEventListener && settings.touchScroll) {
document.removeEventListener('touchstart', swipeScroll.touchHandler, false);
document.removeEventListener('touchmove', swipeScroll.touchHandler, false);
document.removeEventListener('touchend', swipeScroll.touchHandler, false);
}
names = [];
elements = [];
overflow = [];
index = 0;
currentIndex = 0;
interstitialIndex = 1;
hasLocation = false;
timeoutId = false;
timeoutId2 = false;
$window = $(window);
portHeight = false;
top = $window.scrollTop();
scrollable = false;
locked = false;
scrolled = false;
manualScroll = false;
swipeScroll = false;
util = false;
disabled = false;
scrollSamples = [];
scrollTime = new Date().getTime();
firstLoad = true;
initialised = false;
destination = 0;
};
scrollify.update = function () {
if (!initialised) {
return false;
}
util.handleUpdate();
};
scrollify.current = function () {
return elements[index];
};
scrollify.currentIndex = function () {
return index;
};
scrollify.disable = function () {
disabled = true;
};
scrollify.enable = function () {
disabled = false;
if (initialised) {
//instant,callbacks
manualScroll.calculateNearest(false, false);
}
};
scrollify.isDisabled = function () {
return disabled;
};
scrollify.setOptions = function (updatedOptions) {
if (!initialised) {
return false;
}
if (typeof updatedOptions === "object") {
settings = $.extend(settings, updatedOptions);
util.handleUpdate();
} else if (window.console) {
console.warn("Scrollify warning: setOptions expects an object.");
}
};
$.scrollify = scrollify;
return scrollify;
}));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment