Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

scrollama

Package Overview
Dependencies
Maintainers
1
Versions
50
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

scrollama - npm Package Compare versions

Comparing version 0.5.4 to 0.6.0

docs/scrollama.min.js

271

build/scrollama.js

@@ -10,21 +10,9 @@ (function (global, factory) {

*
* 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
* Licensed under the W3C SOFTWARE AND DOCUMENT NOTICE AND LICENSE.
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
*
*/
(function(window, document) {
'use strict';
// Exits early if all IntersectionObserver and IntersectionObserverEntry
// features are natively supported.
if ('IntersectionObserver' in window &&

@@ -35,3 +23,3 @@ 'IntersectionObserverEntry' in window &&

// Minimal polyfill for Edge 15's lack of `isIntersecting`
// See: https://github.com/WICG/IntersectionObserver/issues/211
// See: https://github.com/w3c/IntersectionObserver/issues/211
if (!('isIntersecting' in window.IntersectionObserverEntry.prototype)) {

@@ -60,3 +48,3 @@ Object.defineProperty(window.IntersectionObserverEntry.prototype,

* Creates the global IntersectionObserverEntry constructor.
* https://wicg.github.io/IntersectionObserver/#intersection-observer-entry
* https://w3c.github.io/IntersectionObserver/#intersection-observer-entry
* @param {Object} entry A dictionary of instance properties.

@@ -91,3 +79,3 @@ * @constructor

* Creates the global IntersectionObserver constructor.
* https://wicg.github.io/IntersectionObserver/#intersection-observer-interface
* https://w3c.github.io/IntersectionObserver/#intersection-observer-interface
* @param {Function} callback The function to be invoked after intersection

@@ -370,3 +358,3 @@ * changes have queued. The function is not invoked if the queue has

* TODO(philipwalton): at this time clip-path is not considered.
* https://wicg.github.io/IntersectionObserver/#calculate-intersection-rect-algo
* https://w3c.github.io/IntersectionObserver/#calculate-intersection-rect-algo
* @param {Element} target The target DOM element

@@ -664,3 +652,3 @@ * @param {Object} rootRect The bounding rect of the root after being

// Ignore Windows 7 IE11 "Unspecified error"
// https://github.com/WICG/IntersectionObserver/pull/205
// https://github.com/w3c/IntersectionObserver/pull/205
}

@@ -788,22 +776,48 @@

var stepStates = null;
var previousYOffset = 0;
var direction = 'down';
function getIndex(element) {
return +element.getAttribute('data-scrollama-index');
}
function updateDirection() {
if (window.pageYOffset > previousYOffset) { direction = 'down'; }
else if (window.pageYOffset < previousYOffset) { direction = 'up'; }
previousYOffset = window.pageYOffset;
}
// NOTIFY CALLBACKS
function notifyStepEnter(element, direction) {
var index = +element.getAttribute('data-scrollama-index');
function notifyStepEnter(element) {
var index = getIndex(element);
var resp = { element: element, index: index, direction: direction };
// store most recent trigger
stepStates[index].direction = direction;
stepStates[index].state = 'enter';
if (callback.stepEnter && typeof callback.stepEnter === 'function')
{ callback.stepEnter(resp); }
if (progressMode) {
if (direction === 'up') { notifyStepProgress(element, 1); }
else { notifyStepProgress(element, 0); }
if (direction === 'down') { notifyStepProgress(element, 0); }
else { notifyStepProgress(element, 1); }
}
}
function notifyStepExit(element, direction) {
var index = +element.getAttribute('data-scrollama-index');
function notifyStepExit(element) {
var index = getIndex(element);
var resp = { element: element, index: index, direction: direction };
// store most recent trigger
stepStates[index].direction = direction;
stepStates[index].state = 'exit';
if (callback.stepExit && typeof callback.stepExit === 'function')
{ callback.stepExit(resp); }
if (progressMode) {
if (direction === 'up') { notifyStepProgress(element, 0); }
else { notifyStepProgress(element, 1); }
if (direction === 'down') { notifyStepProgress(element, 1); }
else { notifyStepProgress(element, 0); }
}

@@ -813,3 +827,3 @@ }

function notifyStepProgress(element, progress) {
var index = +element.getAttribute('data-scrollama-index');
var index = getIndex(element);
var resp = { element: element, index: index, progress: progress };

@@ -820,3 +834,3 @@ if (callback.stepProgress && typeof callback.stepProgress === 'function')

function notifyContainerEnter(direction) {
function notifyContainerEnter() {
var resp = { direction: direction };

@@ -830,3 +844,3 @@ if (

function notifyContainerExit(direction) {
function notifyContainerExit() {
var resp = { direction: direction };

@@ -841,13 +855,23 @@ if (callback.containerExit && typeof callback.containerExit === 'function')

// bottom must be > 0 which means it is on "screen" (shifted by offset)
function intersectStepTop(entries) {
function intersectStepAbove(entries) {
updateDirection();
entries.forEach(function (entry) {
var isIntersecting = entry.isIntersecting;
var intersectionRatio = entry.intersectionRatio;
var boundingClientRect = entry.boundingClientRect;
var target = entry.target;
// bottom is how far bottom edge of el is from top of viewport
var bottom = boundingClientRect.bottom;
var bottomAdjusted = bottom - offsetMargin;
var index = getIndex(target);
if (bottomAdjusted >= -ZERO_MOE) {
var direction = isIntersecting ? 'down' : 'up';
if (isIntersecting) { notifyStepEnter(target, direction); }
else { notifyStepExit(target, direction); }
if (isIntersecting && direction === 'down')
{ notifyStepEnter(target, direction); }
else if (direction === 'up') {
// we went from exit to exit, must have skipped an enter
if (stepStates[index].state === 'exit')
{ notifyStepEnter(target, direction); }
notifyStepExit(target, direction);
}
}

@@ -857,5 +881,7 @@ });

function intersectStepBottom(entries) {
function intersectStepBelow(entries) {
updateDirection();
entries.forEach(function (entry) {
var isIntersecting = entry.isIntersecting;
var intersectionRatio = entry.intersectionRatio;
var boundingClientRect = entry.boundingClientRect;

@@ -866,12 +892,19 @@ var target = entry.target;

var bottomAdjusted = bottom - offsetMargin;
var index = getIndex(target);
if (
bottomAdjusted >= -ZERO_MOE &&
bottomAdjusted < height &&
isIntersecting
isIntersecting &&
direction === 'up'
) {
var direction = 'up';
notifyStepEnter(target, direction);
} else if (bottomAdjusted <= ZERO_MOE && !isIntersecting) {
var direction$1 = 'down';
notifyStepExit(target, direction$1);
} else if (
bottomAdjusted <= ZERO_MOE &&
!isIntersecting &&
direction === 'down'
) {
if (stepStates[index].state === 'exit')
{ notifyStepEnter(target, direction); }
notifyStepExit(target, direction);
}

@@ -881,3 +914,49 @@ });

// if there is a scroll even that skips the entire enter/exit of a step,
// fallback to trigger the enter/exit if element lands in viewport
function intersectViewportAbove(entries) {
updateDirection();
entries.forEach(function (entry) {
var isIntersecting = entry.isIntersecting;
var intersectionRatio = entry.intersectionRatio;
var boundingClientRect = entry.boundingClientRect;
var target = entry.target;
var index = getIndex(target);
if (isIntersecting && direction === 'down') {
if (
stepStates[index].state === 'exit' &&
stepStates[index].direction === 'up'
) {
notifyStepEnter(target, 'down');
notifyStepExit(target, 'down');
}
}
});
}
function intersectViewportBelow(entries) {
updateDirection();
entries.forEach(function (entry) {
var isIntersecting = entry.isIntersecting;
var intersectionRatio = entry.intersectionRatio;
var boundingClientRect = entry.boundingClientRect;
var target = entry.target;
var index = getIndex(target);
if (isIntersecting && direction === 'up') {
if (
stepStates[index].state === 'exit' &&
stepStates[index].direction === 'down'
) {
notifyStepEnter(target, 'up');
notifyStepExit(target, 'up');
}
}
});
}
function intersectStepProgress(entries) {
updateDirection();
entries.forEach(function (entry) {

@@ -898,8 +977,9 @@ var isIntersecting = entry.isIntersecting;

function intersectTop(entries) {
updateDirection();
var ref = entries[0];
var isIntersecting = ref.isIntersecting;
var boundingClientRect = ref.boundingClientRect;
var top = boundingClientRect.top;
var bottom = boundingClientRect.bottom;
if (bottom > -ZERO_MOE) {
var direction = isIntersecting ? 'down' : 'up';
if (isIntersecting) { notifyContainerEnter(direction); }

@@ -911,2 +991,3 @@ else { notifyContainerExit(direction); }

function intersectBottom(entries) {
updateDirection();
var ref = entries[0];

@@ -917,3 +998,2 @@ var isIntersecting = ref.isIntersecting;

if (top < ZERO_MOE) {
var direction = isIntersecting ? 'up' : 'down';
if (isIntersecting) { notifyContainerEnter(direction); }

@@ -951,9 +1031,10 @@ else { notifyContainerExit(direction); }

// top edge
function updateStepTopIO() {
if (io.stepTop) { io.stepTop.forEach(function (d) { return d.disconnect(); }); }
function updateStepAboveIO() {
if (io.stepAbove) { io.stepAbove.forEach(function (d) { return d.disconnect(); }); }
io.stepTop = stepEl.map(function (el, i) {
io.stepAbove = stepEl.map(function (el, i) {
var marginTop = stepHeights[i] - offsetMargin;
var marginBottom = -vh + offsetMargin;
var rootMargin = marginTop + "px 0px " + marginBottom + "px 0px";
// addDebugStep({ label: 'above', edge: 'orange', marginTop, marginBottom });

@@ -966,3 +1047,3 @@ var options = {

var obs = new IntersectionObserver(intersectStepTop, options);
var obs = new IntersectionObserver(intersectStepAbove, options);
obs.observe(el);

@@ -974,10 +1055,30 @@ return obs;

// bottom edge
function updateStepBottomIO() {
if (io.stepBottom) { io.stepBottom.forEach(function (d) { return d.disconnect(); }); }
function updateStepBelowIO() {
if (io.stepBelow) { io.stepBelow.forEach(function (d) { return d.disconnect(); }); }
io.stepBottom = stepEl.map(function (el, i) {
io.stepBelow = stepEl.map(function (el, i) {
var marginTop = -offsetMargin;
var marginBottom = -vh + stepHeights[i] + offsetMargin;
var rootMargin = marginTop + "px 0px " + marginBottom + "px 0px";
// addDebugStep({ label: 'below', edge: 'blue', marginTop, marginBottom });
var options = {
root: null,
rootMargin: rootMargin,
threshold: 0
};
var obs = new IntersectionObserver(intersectStepBelow, options);
obs.observe(el);
return obs;
});
}
// jump into viewport
function updateViewportAboveIO() {
if (io.viewportAbove) { io.viewportAbove.forEach(function (d) { return d.disconnect(); }); }
io.viewportAbove = stepEl.map(function (el, i) {
var marginTop = 0;
var marginBottom = -(vh - offsetMargin + stepHeights[i]);
var rootMargin = marginTop + "px 0px " + marginBottom + "px 0px";
addDebugStep({ label: 'above', edge: 'purple', marginTop: marginTop, marginBottom: marginBottom });
var options = {

@@ -989,3 +1090,3 @@ root: null,

var obs = new IntersectionObserver(intersectStepBottom, options);
var obs = new IntersectionObserver(intersectViewportAbove, options);
obs.observe(el);

@@ -996,2 +1097,21 @@ return obs;

function updateViewportBelowIO() {
if (io.viewportBelow) { io.viewportBelow.forEach(function (d) { return d.disconnect(); }); }
io.viewportBelow = stepEl.map(function (el, i) {
var marginTop = -(offsetMargin + stepHeights[i]);
var marginBottom = 0;
var rootMargin = marginTop + "px 0px " + marginBottom + "px 0px";
addDebugStep({ label: 'above', edge: 'purple', marginTop: marginTop, marginBottom: marginBottom });
var options = {
root: null,
rootMargin: rootMargin,
threshold: 0
};
var obs = new IntersectionObserver(intersectViewportBelow, options);
obs.observe(el);
return obs;
});
}
// progress progress tracker

@@ -1019,4 +1139,6 @@ function updateStepProgressIO() {

function updateIO() {
updateStepTopIO();
updateStepBottomIO();
updateStepAboveIO();
updateStepBelowIO();
updateViewportAboveIO();
updateViewportBelowIO();

@@ -1057,5 +1179,7 @@ if (progressMode) { updateStepProgressIO(); }

if (io.bottom) { io.bottom.disconnect(); }
if (io.stepTop) { io.stepTop.forEach(function (d) { return d.disconnect(); }); }
if (io.stepBottom) { io.stepBottom.forEach(function (d) { return d.disconnect(); }); }
if (io.stepAbove) { io.stepAbove.forEach(function (d) { return d.disconnect(); }); }
if (io.stepBelow) { io.stepBelow.forEach(function (d) { return d.disconnect(); }); }
if (io.stepProgress) { io.stepProgress.forEach(function (d) { return d.disconnect(); }); }
if (io.viewportAbove) { io.viewportAbove.forEach(function (d) { return d.disconnect(); }); }
if (io.viewportBelow) { io.viewportBelow.forEach(function (d) { return d.disconnect(); }); }
isEnabled = false;

@@ -1069,2 +1193,10 @@ }

function setupStepStates() {
stepStates = stepEl.map(function () { return ({
direction: null,
state: null,
bottom: -1
}); });
}
function addDebug() {

@@ -1094,2 +1226,30 @@ if (debugMode) {

function addDebugStep(ref) {
var label = ref.label;
var edge = ref.edge;
var marginTop = ref.marginTop;
var marginBottom = ref.marginBottom;
var el1 = document.createElement('div');
el1.setAttribute('id', ("scrollama__debug--" + edge + "-a-" + id));
el1.setAttribute('class', 'scrollama__debug--offset');
el1.style.position = 'fixed';
el1.style.top = (-marginTop) + "px";
el1.style.left = '0';
el1.style.width = '100%';
el1.style.height = '1px';
el1.style.borderBottom = "1px dashed " + edge;
document.body.appendChild(el1);
var el2 = document.createElement('div');
el2.setAttribute('id', ("scrollama__debug--" + edge + "-b-" + id));
el2.setAttribute('class', 'scrollama__debug--offset');
el2.style.position = 'fixed';
el2.style.bottom = (-marginBottom) + "px";
el2.style.left = '0';
el2.style.width = '100%';
el2.style.height = '1px';
el2.style.borderBottom = "1px dashed " + edge;
document.body.appendChild(el2);
}
function setThreshold() {

@@ -1125,2 +1285,3 @@ var count = 100;

indexSteps();
setupStepStates();
if (progressMode) { setThreshold(); }

@@ -1127,0 +1288,0 @@ handleResize();

@@ -1,1 +0,1 @@

!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.scrollama=e()}(this,function(){"use strict";function t(t){for(var e=t.length,n=[],o=0;o<e;o+=1)n.push(t[o]);return n}function e(t){return document.querySelector(t)}function n(e,n){return void 0===n&&(n=document),t(n.querySelectorAll(e))}return function(t,e){function n(t){this.time=t.time,this.target=t.target,this.rootBounds=t.rootBounds,this.boundingClientRect=t.boundingClientRect,this.intersectionRect=t.intersectionRect||{top:0,bottom:0,left:0,right:0,width:0,height:0},this.isIntersecting=!!t.intersectionRect;var e=this.boundingClientRect,n=e.width*e.height,o=this.intersectionRect,r=o.width*o.height;this.intersectionRatio=n?r/n:this.isIntersecting?1:0}function o(t,e){var n=e||{};if("function"!=typeof t)throw new Error("callback must be a function");if(n.root&&1!=n.root.nodeType)throw new Error("root must be an Element");this._checkForIntersections=i(this._checkForIntersections.bind(this),this.THROTTLE_TIMEOUT),this._callback=t,this._observationTargets=[],this._queuedEntries=[],this._rootMarginValues=this._parseRootMargin(n.rootMargin),this.thresholds=this._initThresholds(n.threshold),this.root=n.root||null,this.rootMargin=this._rootMarginValues.map(function(t){return t.value+t.unit}).join(" ")}function r(){return t.performance&&performance.now&&performance.now()}function i(t,e){var n=null;return function(){n||(n=setTimeout(function(){t(),n=null},e))}}function s(t,e,n,o){"function"==typeof t.addEventListener?t.addEventListener(e,n,o||!1):"function"==typeof t.attachEvent&&t.attachEvent("on"+e,n)}function c(t,e,n,o){"function"==typeof t.removeEventListener?t.removeEventListener(e,n,o||!1):"function"==typeof t.detatchEvent&&t.detatchEvent("on"+e,n)}function u(t,e){var n=Math.max(t.top,e.top),o=Math.min(t.bottom,e.bottom),r=Math.max(t.left,e.left),i=Math.min(t.right,e.right),s=i-r,c=o-n;return s>=0&&c>=0&&{top:n,bottom:o,left:r,right:i,width:s,height:c}}function a(t){var e;try{e=t.getBoundingClientRect()}catch(t){}return e?(e.width&&e.height||(e={top:e.top,right:e.right,bottom:e.bottom,left:e.left,width:e.right-e.left,height:e.bottom-e.top}),e):{top:0,bottom:0,left:0,right:0,width:0,height:0}}function h(t,e){for(var n=e;n;){if(n==t)return!0;n=p(n)}return!1}function p(t){var e=t.parentNode;return e&&11==e.nodeType&&e.host?e.host:e}if("IntersectionObserver"in t&&"IntersectionObserverEntry"in t&&"intersectionRatio"in t.IntersectionObserverEntry.prototype)"isIntersecting"in t.IntersectionObserverEntry.prototype||Object.defineProperty(t.IntersectionObserverEntry.prototype,"isIntersecting",{get:function(){return this.intersectionRatio>0}});else{var l=[];o.prototype.THROTTLE_TIMEOUT=100,o.prototype.POLL_INTERVAL=null,o.prototype.observe=function(t){if(!this._observationTargets.some(function(e){return e.element==t})){if(!t||1!=t.nodeType)throw new Error("target must be an Element");this._registerInstance(),this._observationTargets.push({element:t,entry:null}),this._monitorIntersections(),this._checkForIntersections()}},o.prototype.unobserve=function(t){this._observationTargets=this._observationTargets.filter(function(e){return e.element!=t}),this._observationTargets.length||(this._unmonitorIntersections(),this._unregisterInstance())},o.prototype.disconnect=function(){this._observationTargets=[],this._unmonitorIntersections(),this._unregisterInstance()},o.prototype.takeRecords=function(){var t=this._queuedEntries.slice();return this._queuedEntries=[],t},o.prototype._initThresholds=function(t){var e=t||[0];return Array.isArray(e)||(e=[e]),e.sort().filter(function(t,e,n){if("number"!=typeof t||isNaN(t)||t<0||t>1)throw new Error("threshold must be a number between 0 and 1 inclusively");return t!==n[e-1]})},o.prototype._parseRootMargin=function(t){var e=(t||"0px").split(/\s+/).map(function(t){var e=/^(-?\d*\.?\d+)(px|%)$/.exec(t);if(!e)throw new Error("rootMargin must be specified in pixels or percent");return{value:parseFloat(e[1]),unit:e[2]}});return e[1]=e[1]||e[0],e[2]=e[2]||e[0],e[3]=e[3]||e[1],e},o.prototype._monitorIntersections=function(){this._monitoringIntersections||(this._monitoringIntersections=!0,this.POLL_INTERVAL?this._monitoringInterval=setInterval(this._checkForIntersections,this.POLL_INTERVAL):(s(t,"resize",this._checkForIntersections,!0),s(e,"scroll",this._checkForIntersections,!0),"MutationObserver"in t&&(this._domObserver=new MutationObserver(this._checkForIntersections),this._domObserver.observe(e,{attributes:!0,childList:!0,characterData:!0,subtree:!0}))))},o.prototype._unmonitorIntersections=function(){this._monitoringIntersections&&(this._monitoringIntersections=!1,clearInterval(this._monitoringInterval),this._monitoringInterval=null,c(t,"resize",this._checkForIntersections,!0),c(e,"scroll",this._checkForIntersections,!0),this._domObserver&&(this._domObserver.disconnect(),this._domObserver=null))},o.prototype._checkForIntersections=function(){var t=this._rootIsInDom(),e=t?this._getRootRect():{top:0,bottom:0,left:0,right:0,width:0,height:0};this._observationTargets.forEach(function(o){var i=o.element,s=a(i),c=this._rootContainsTarget(i),u=o.entry,h=t&&c&&this._computeTargetAndRootIntersection(i,e),p=o.entry=new n({time:r(),target:i,boundingClientRect:s,rootBounds:e,intersectionRect:h});u?t&&c?this._hasCrossedThreshold(u,p)&&this._queuedEntries.push(p):u&&u.isIntersecting&&this._queuedEntries.push(p):this._queuedEntries.push(p)},this),this._queuedEntries.length&&this._callback(this.takeRecords(),this)},o.prototype._computeTargetAndRootIntersection=function(n,o){var r=this;if("none"!=t.getComputedStyle(n).display){for(var i=a(n),s=p(n),c=!1;!c;){var h=null,l=1==s.nodeType?t.getComputedStyle(s):{};if("none"==l.display)return;if(s==r.root||s==e?(c=!0,h=o):s!=e.body&&s!=e.documentElement&&"visible"!=l.overflow&&(h=a(s)),h&&!(i=u(h,i)))break;s=p(s)}return i}},o.prototype._getRootRect=function(){var t;if(this.root)t=a(this.root);else{var n=e.documentElement,o=e.body;t={top:0,left:0,right:n.clientWidth||o.clientWidth,width:n.clientWidth||o.clientWidth,bottom:n.clientHeight||o.clientHeight,height:n.clientHeight||o.clientHeight}}return this._expandRectByRootMargin(t)},o.prototype._expandRectByRootMargin=function(t){var e=this._rootMarginValues.map(function(e,n){return"px"==e.unit?e.value:e.value*(n%2?t.width:t.height)/100}),n={top:t.top-e[0],right:t.right+e[1],bottom:t.bottom+e[2],left:t.left-e[3]};return n.width=n.right-n.left,n.height=n.bottom-n.top,n},o.prototype._hasCrossedThreshold=function(t,e){var n=this,o=t&&t.isIntersecting?t.intersectionRatio||0:-1,r=e.isIntersecting?e.intersectionRatio||0:-1;if(o!==r)for(var i=0;i<this.thresholds.length;i++){var s=n.thresholds[i];if(s==o||s==r||s<o!=s<r)return!0}},o.prototype._rootIsInDom=function(){return!this.root||h(e,this.root)},o.prototype._rootContainsTarget=function(t){return h(this.root||e,t)},o.prototype._registerInstance=function(){l.indexOf(this)<0&&l.push(this)},o.prototype._unregisterInstance=function(){var t=l.indexOf(this);-1!=t&&l.splice(t,1)},t.IntersectionObserver=o,t.IntersectionObserverEntry=n}}(window,document),function(){function t(t,e){var n={element:t,index:+t.getAttribute("data-scrollama-index"),direction:e};T.stepEnter&&"function"==typeof T.stepEnter&&T.stepEnter(n),H&&("up"===e?r(t,1):r(t,0))}function o(t,e){var n={element:t,index:+t.getAttribute("data-scrollama-index"),direction:e};T.stepExit&&"function"==typeof T.stepExit&&T.stepExit(n),H&&("up"===e?r(t,0):r(t,1))}function r(t,e){var n={element:t,index:+t.getAttribute("data-scrollama-index"),progress:e};T.stepProgress&&"function"==typeof T.stepProgress&&T.stepProgress(n)}function i(t){var e={direction:t};T.containerEnter&&"function"==typeof T.containerEnter&&T.containerEnter(e)}function s(t){var e={direction:t};T.containerExit&&"function"==typeof T.containerExit&&T.containerExit(e)}function c(e){e.forEach(function(e){var n=e.isIntersecting,r=e.boundingClientRect,i=e.target;if(r.bottom-L>=-R){var s=n?"down":"up";n?t(i,s):o(i,s)}})}function u(e){e.forEach(function(e){var n=e.isIntersecting,r=e.boundingClientRect,i=e.target,s=r.bottom,c=r.height,u=s-L;u>=-R&&u<c&&n?t(i,"up"):u<=R&&!n&&o(i,"down")})}function a(t){t.forEach(function(t){var e=t.isIntersecting,n=t.intersectionRatio,o=t.boundingClientRect,i=t.target,s=o.bottom-L;e&&s>=-R&&r(i,+n.toFixed(3))})}function h(t){var e=t[0],n=e.isIntersecting;if(e.boundingClientRect.bottom>-R){var o=n?"down":"up";n?i(o):s(o)}}function p(t){var e=t[0],n=e.isIntersecting;if(e.boundingClientRect.top<R){var o=n?"up":"down";n?i(o):s(o)}}function l(){w.top&&w.top.unobserve(O);var t={root:null,rootMargin:A+"px 0px -"+A+"px 0px",threshold:0};w.top=new IntersectionObserver(h,t),w.top.observe(O)}function f(){w.bottom&&w.bottom.unobserve(O);var t={root:null,rootMargin:"-"+P.height+"px 0px "+P.height+"px 0px",threshold:0};w.bottom=new IntersectionObserver(p,t),w.bottom.observe(O)}function d(){w.stepTop&&w.stepTop.forEach(function(t){return t.disconnect()}),w.stepTop=C.map(function(t,e){var n={root:null,rootMargin:B[e]-L+"px 0px "+(-A+L)+"px 0px",threshold:0},o=new IntersectionObserver(c,n);return o.observe(t),o})}function g(){w.stepBottom&&w.stepBottom.forEach(function(t){return t.disconnect()}),w.stepBottom=C.map(function(t,e){var n={root:null,rootMargin:-L+"px 0px "+(-A+B[e]+L)+"px 0px",threshold:0},o=new IntersectionObserver(u,n);return o.observe(t),o})}function m(){w.stepProgress&&w.stepProgress.forEach(function(t){return t.disconnect()}),w.stepProgress=C.map(function(t,e){var n={root:null,rootMargin:B[e]-L+"px 0px "+(-A+L)+"px 0px",threshold:F},o=new IntersectionObserver(a,n);return o.observe(t),o})}function v(){d(),g(),H&&m(),O&&M&&(l(),f())}function b(){A=window.innerHeight,P=M?M.getBoundingClientRect():null,L=k*A,B=C?C.map(function(t){return t.getBoundingClientRect().height}):[],S&&q&&v(),N&&(document.querySelector("#scrollama__debug--offset-"+x).style.top=L+"px")}function _(t){t&&!S?(q&&v(),S=!0):t||(w.top&&w.top.disconnect(),w.bottom&&w.bottom.disconnect(),w.stepTop&&w.stepTop.forEach(function(t){return t.disconnect()}),w.stepBottom&&w.stepBottom.forEach(function(t){return t.disconnect()}),w.stepProgress&&w.stepProgress.forEach(function(t){return t.disconnect()}),S=!1)}function y(){C.forEach(function(t,e){return t.setAttribute("data-scrollama-index",e)})}function E(){if(N){var t=document.createElement("div");t.setAttribute("id","scrollama__debug--offset-"+x),t.setAttribute("class","scrollama__debug--offset"),t.style.position="fixed",t.style.top="0",t.style.left="0",t.style.width="100%",t.style.height="1px",t.style.borderBottom="1px dashed red";var e=document.createElement("p"),n=C[0].getAttribute("class");e.innerText='".'+n+'" trigger: '+k,e.style.fontSize="12px",e.style.fontFamily="monospace",e.style.color="red",e.style.margin="0",e.style.padding="6px",t.appendChild(e),document.body.appendChild(t)}}function I(){F=[];for(var t=0;t<100;t++)F.push(.01*t)}var x=Math.floor(1e5*Math.random()),R=1,T={},w={},O=null,M=null,C=null,k=0,L=0,A=0,B=null,P=null,F=0,q=!1,S=!1,N=!1,H=!1,V={};return V.setup=function(t){var o=t.container,r=t.graphic,i=t.step,s=t.offset;void 0===s&&(s=.5);var c=t.progress;void 0===c&&(c=!1);var u=t.debug;return void 0===u&&(u=!1),i?(C=n(i),O=o?e(o):null,M=r?e(r):null,V.offsetTrigger(s),N=u,H=c,q=!0,E(),y(),H&&I(),b(),_(!0)):console.error("scrollama error: missing step element"),V},V.resize=function(){return b(),V},V.enable=function(){return _(!0),V},V.disable=function(){return _(!1),V},V.offsetTrigger=function(t){return t&&(isNaN(t),!0)?(k=Math.min(Math.max(0,t),1),V):k},V.onStepEnter=function(t){return T.stepEnter=t,V},V.onStepExit=function(t){return T.stepExit=t,V},V.onStepProgress=function(t){return T.stepProgress=t,V},V.onContainerEnter=function(t){return T.containerEnter=t,V},V.onContainerExit=function(t){return T.containerExit=t,V},V}});
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.scrollama=e()}(this,function(){"use strict";function t(t){return document.querySelector(t)}return function(t,e){function n(t){this.time=t.time,this.target=t.target,this.rootBounds=t.rootBounds,this.boundingClientRect=t.boundingClientRect,this.intersectionRect=t.intersectionRect||{top:0,bottom:0,left:0,right:0,width:0,height:0},this.isIntersecting=!!t.intersectionRect;var e=this.boundingClientRect,n=e.width*e.height,o=this.intersectionRect,r=o.width*o.height;this.intersectionRatio=n?r/n:this.isIntersecting?1:0}function o(t,e){var n=e||{};if("function"!=typeof t)throw new Error("callback must be a function");if(n.root&&1!=n.root.nodeType)throw new Error("root must be an Element");this._checkForIntersections=function(t,e){var n=null;return function(){n||(n=setTimeout(function(){t(),n=null},e))}}(this._checkForIntersections.bind(this),this.THROTTLE_TIMEOUT),this._callback=t,this._observationTargets=[],this._queuedEntries=[],this._rootMarginValues=this._parseRootMargin(n.rootMargin),this.thresholds=this._initThresholds(n.threshold),this.root=n.root||null,this.rootMargin=this._rootMarginValues.map(function(t){return t.value+t.unit}).join(" ")}function r(t,e,n,o){"function"==typeof t.addEventListener?t.addEventListener(e,n,o||!1):"function"==typeof t.attachEvent&&t.attachEvent("on"+e,n)}function i(t,e,n,o){"function"==typeof t.removeEventListener?t.removeEventListener(e,n,o||!1):"function"==typeof t.detatchEvent&&t.detatchEvent("on"+e,n)}function s(t){var e;try{e=t.getBoundingClientRect()}catch(t){}return e?(e.width&&e.height||(e={top:e.top,right:e.right,bottom:e.bottom,left:e.left,width:e.right-e.left,height:e.bottom-e.top}),e):{top:0,bottom:0,left:0,right:0,width:0,height:0}}function c(t,e){for(var n=e;n;){if(n==t)return!0;n=u(n)}return!1}function u(t){var e=t.parentNode;return e&&11==e.nodeType&&e.host?e.host:e}if("IntersectionObserver"in t&&"IntersectionObserverEntry"in t&&"intersectionRatio"in t.IntersectionObserverEntry.prototype)"isIntersecting"in t.IntersectionObserverEntry.prototype||Object.defineProperty(t.IntersectionObserverEntry.prototype,"isIntersecting",{get:function(){return this.intersectionRatio>0}});else{var a=[];o.prototype.THROTTLE_TIMEOUT=100,o.prototype.POLL_INTERVAL=null,o.prototype.observe=function(t){if(!this._observationTargets.some(function(e){return e.element==t})){if(!t||1!=t.nodeType)throw new Error("target must be an Element");this._registerInstance(),this._observationTargets.push({element:t,entry:null}),this._monitorIntersections(),this._checkForIntersections()}},o.prototype.unobserve=function(t){this._observationTargets=this._observationTargets.filter(function(e){return e.element!=t}),this._observationTargets.length||(this._unmonitorIntersections(),this._unregisterInstance())},o.prototype.disconnect=function(){this._observationTargets=[],this._unmonitorIntersections(),this._unregisterInstance()},o.prototype.takeRecords=function(){var t=this._queuedEntries.slice();return this._queuedEntries=[],t},o.prototype._initThresholds=function(t){var e=t||[0];return Array.isArray(e)||(e=[e]),e.sort().filter(function(t,e,n){if("number"!=typeof t||isNaN(t)||t<0||t>1)throw new Error("threshold must be a number between 0 and 1 inclusively");return t!==n[e-1]})},o.prototype._parseRootMargin=function(t){var e=(t||"0px").split(/\s+/).map(function(t){var e=/^(-?\d*\.?\d+)(px|%)$/.exec(t);if(!e)throw new Error("rootMargin must be specified in pixels or percent");return{value:parseFloat(e[1]),unit:e[2]}});return e[1]=e[1]||e[0],e[2]=e[2]||e[0],e[3]=e[3]||e[1],e},o.prototype._monitorIntersections=function(){this._monitoringIntersections||(this._monitoringIntersections=!0,this.POLL_INTERVAL?this._monitoringInterval=setInterval(this._checkForIntersections,this.POLL_INTERVAL):(r(t,"resize",this._checkForIntersections,!0),r(e,"scroll",this._checkForIntersections,!0),"MutationObserver"in t&&(this._domObserver=new MutationObserver(this._checkForIntersections),this._domObserver.observe(e,{attributes:!0,childList:!0,characterData:!0,subtree:!0}))))},o.prototype._unmonitorIntersections=function(){this._monitoringIntersections&&(this._monitoringIntersections=!1,clearInterval(this._monitoringInterval),this._monitoringInterval=null,i(t,"resize",this._checkForIntersections,!0),i(e,"scroll",this._checkForIntersections,!0),this._domObserver&&(this._domObserver.disconnect(),this._domObserver=null))},o.prototype._checkForIntersections=function(){var e=this._rootIsInDom(),o=e?this._getRootRect():{top:0,bottom:0,left:0,right:0,width:0,height:0};this._observationTargets.forEach(function(r){var i=r.element,c=s(i),u=this._rootContainsTarget(i),a=r.entry,h=e&&u&&this._computeTargetAndRootIntersection(i,o),p=r.entry=new n({time:t.performance&&performance.now&&performance.now(),target:i,boundingClientRect:c,rootBounds:o,intersectionRect:h});a?e&&u?this._hasCrossedThreshold(a,p)&&this._queuedEntries.push(p):a&&a.isIntersecting&&this._queuedEntries.push(p):this._queuedEntries.push(p)},this),this._queuedEntries.length&&this._callback(this.takeRecords(),this)},o.prototype._computeTargetAndRootIntersection=function(n,o){if("none"!=t.getComputedStyle(n).display){for(var r=s(n),i=u(n),c=!1;!c;){var a=null,h=1==i.nodeType?t.getComputedStyle(i):{};if("none"==h.display)return;if(i==this.root||i==e?(c=!0,a=o):i!=e.body&&i!=e.documentElement&&"visible"!=h.overflow&&(a=s(i)),a&&!(r=function(t,e){var n=Math.max(t.top,e.top),o=Math.min(t.bottom,e.bottom),r=Math.max(t.left,e.left),i=Math.min(t.right,e.right),s=i-r,c=o-n;return s>=0&&c>=0&&{top:n,bottom:o,left:r,right:i,width:s,height:c}}(a,r)))break;i=u(i)}return r}},o.prototype._getRootRect=function(){var t;if(this.root)t=s(this.root);else{var n=e.documentElement,o=e.body;t={top:0,left:0,right:n.clientWidth||o.clientWidth,width:n.clientWidth||o.clientWidth,bottom:n.clientHeight||o.clientHeight,height:n.clientHeight||o.clientHeight}}return this._expandRectByRootMargin(t)},o.prototype._expandRectByRootMargin=function(t){var e=this._rootMarginValues.map(function(e,n){return"px"==e.unit?e.value:e.value*(n%2?t.width:t.height)/100}),n={top:t.top-e[0],right:t.right+e[1],bottom:t.bottom+e[2],left:t.left-e[3]};return n.width=n.right-n.left,n.height=n.bottom-n.top,n},o.prototype._hasCrossedThreshold=function(t,e){var n=t&&t.isIntersecting?t.intersectionRatio||0:-1,o=e.isIntersecting?e.intersectionRatio||0:-1;if(n!==o)for(var r=0;r<this.thresholds.length;r++){var i=this.thresholds[r];if(i==n||i==o||i<n!=i<o)return!0}},o.prototype._rootIsInDom=function(){return!this.root||c(e,this.root)},o.prototype._rootContainsTarget=function(t){return c(this.root||e,t)},o.prototype._registerInstance=function(){a.indexOf(this)<0&&a.push(this)},o.prototype._unregisterInstance=function(){var t=a.indexOf(this);-1!=t&&a.splice(t,1)},t.IntersectionObserver=o,t.IntersectionObserverEntry=n}}(window,document),function(){function e(t){return+t.getAttribute("data-scrollama-index")}function n(){window.pageYOffset>q?S="down":window.pageYOffset<q&&(S="up"),q=window.pageYOffset}function o(t){var n=e(t),o={element:t,index:n,direction:S};F[n].direction=S,F[n].state="enter",y.stepEnter&&"function"==typeof y.stepEnter&&y.stepEnter(o),P&&("down"===S?i(t,0):i(t,1))}function r(t){var n=e(t),o={element:t,index:n,direction:S};F[n].direction=S,F[n].state="exit",y.stepExit&&"function"==typeof y.stepExit&&y.stepExit(o),P&&("down"===S?i(t,1):i(t,0))}function i(t,n){var o={element:t,index:e(t),progress:n};y.stepProgress&&"function"==typeof y.stepProgress&&y.stepProgress(o)}function s(){var t={direction:S};y.containerEnter&&"function"==typeof y.containerEnter&&y.containerEnter(t)}function c(){var t={direction:S};y.containerExit&&"function"==typeof y.containerExit&&y.containerExit(t)}function u(t){n(),t.forEach(function(t){var n=t.isIntersecting,i=(t.intersectionRatio,t.boundingClientRect),s=t.target,c=i.bottom-T,u=e(s);c>=-_&&(n&&"down"===S?o(s):"up"===S&&("exit"===F[u].state&&o(s),r(s)))})}function a(t){n(),t.forEach(function(t){var n=t.isIntersecting,i=(t.intersectionRatio,t.boundingClientRect),s=t.target,c=i.bottom,u=i.height,a=c-T,h=e(s);a>=-_&&a<u&&n&&"up"===S?o(s):a<=_&&!n&&"down"===S&&("exit"===F[h].state&&o(s),r(s))})}function h(t){n(),t.forEach(function(t){var n=t.isIntersecting,i=(t.intersectionRatio,t.boundingClientRect,t.target),s=e(i);n&&"down"===S&&"exit"===F[s].state&&"up"===F[s].direction&&(o(i),r(i))})}function p(t){n(),t.forEach(function(t){var n=t.isIntersecting,i=(t.intersectionRatio,t.boundingClientRect,t.target),s=e(i);n&&"up"===S&&"exit"===F[s].state&&"down"===F[s].direction&&(o(i),r(i))})}function l(t){n(),t.forEach(function(t){var e=t.isIntersecting,n=t.intersectionRatio,o=t.boundingClientRect,r=t.target,s=o.bottom;e&&s-T>=-_&&i(r,+n.toFixed(3))})}function f(t){n();var e=t[0],o=e.isIntersecting,r=e.boundingClientRect;r.top,r.bottom>-_&&(o?s():c())}function d(t){n();var e=t[0],o=e.isIntersecting;e.boundingClientRect.top<_&&(o?s():c())}function g(){E.stepAbove&&E.stepAbove.forEach(function(t){return t.disconnect()}),E.stepAbove=I.map(function(t,e){var n={root:null,rootMargin:M[e]-T+"px 0px "+(-O+T)+"px 0px",threshold:0},o=new IntersectionObserver(u,n);return o.observe(t),o}),E.stepBelow&&E.stepBelow.forEach(function(t){return t.disconnect()}),E.stepBelow=I.map(function(t,e){var n={root:null,rootMargin:-T+"px 0px "+(-O+M[e]+T)+"px 0px",threshold:0},o=new IntersectionObserver(a,n);return o.observe(t),o}),E.viewportAbove&&E.viewportAbove.forEach(function(t){return t.disconnect()}),E.viewportAbove=I.map(function(t,e){var n={root:null,rootMargin:"0px 0px "+-(O-T+M[e])+"px 0px",threshold:0},o=new IntersectionObserver(h,n);return o.observe(t),o}),E.viewportBelow&&E.viewportBelow.forEach(function(t){return t.disconnect()}),E.viewportBelow=I.map(function(t,e){var n={root:null,rootMargin:-(T+M[e])+"px 0px 0px 0px",threshold:0},o=new IntersectionObserver(p,n);return o.observe(t),o}),P&&(E.stepProgress&&E.stepProgress.forEach(function(t){return t.disconnect()}),E.stepProgress=I.map(function(t,e){var n={root:null,rootMargin:M[e]-T+"px 0px "+(-O+T)+"px 0px",threshold:A},o=new IntersectionObserver(l,n);return o.observe(t),o})),w&&x&&(function(){E.top&&E.top.unobserve(w);var t={root:null,rootMargin:O+"px 0px -"+O+"px 0px",threshold:0};E.top=new IntersectionObserver(f,t),E.top.observe(w)}(),function(){E.bottom&&E.bottom.unobserve(w);var t={root:null,rootMargin:"-"+C.height+"px 0px "+C.height+"px 0px",threshold:0};E.bottom=new IntersectionObserver(d,t),E.bottom.observe(w)}())}function v(){O=window.innerHeight,C=x?x.getBoundingClientRect():null,T=R*O,M=I?I.map(function(t){return t.getBoundingClientRect().height}):[],k&&B&&g(),L&&(document.querySelector("#scrollama__debug--offset-"+m).style.top=T+"px")}function b(t){t&&!k?(B&&g(),k=!0):t||(E.top&&E.top.disconnect(),E.bottom&&E.bottom.disconnect(),E.stepAbove&&E.stepAbove.forEach(function(t){return t.disconnect()}),E.stepBelow&&E.stepBelow.forEach(function(t){return t.disconnect()}),E.stepProgress&&E.stepProgress.forEach(function(t){return t.disconnect()}),E.viewportAbove&&E.viewportAbove.forEach(function(t){return t.disconnect()}),E.viewportBelow&&E.viewportBelow.forEach(function(t){return t.disconnect()}),k=!1)}var m=Math.floor(1e5*Math.random()),_=1,y={},E={},w=null,x=null,I=null,R=0,T=0,O=0,M=null,C=null,A=0,B=!1,k=!1,L=!1,P=!1,F=null,q=0,S="up",N={};return N.setup=function(e){var n=e.container,o=e.graphic,r=e.step,i=e.offset;void 0===i&&(i=.5);var s=e.progress;void 0===s&&(s=!1);var c=e.debug;return void 0===c&&(c=!1),r?(I=function(t,e){return void 0===e&&(e=document),function(t){for(var e=t.length,n=[],o=0;o<e;o+=1)n.push(t[o]);return n}(e.querySelectorAll(t))}(r),w=n?t(n):null,x=o?t(o):null,N.offsetTrigger(i),L=c,P=s,B=!0,function(){if(L){var t=document.createElement("div");t.setAttribute("id","scrollama__debug--offset-"+m),t.setAttribute("class","scrollama__debug--offset"),t.style.position="fixed",t.style.top="0",t.style.left="0",t.style.width="100%",t.style.height="1px",t.style.borderBottom="1px dashed red";var e=document.createElement("p"),n=I[0].getAttribute("class");e.innerText='".'+n+'" trigger: '+R,e.style.fontSize="12px",e.style.fontFamily="monospace",e.style.color="red",e.style.margin="0",e.style.padding="6px",t.appendChild(e),document.body.appendChild(t)}}(),I.forEach(function(t,e){return t.setAttribute("data-scrollama-index",e)}),F=I.map(function(){return{direction:null,state:null,bottom:-1}}),P&&function(){A=[];for(var t=0;t<100;t++)A.push(.01*t)}(),v(),b(!0)):console.error("scrollama error: missing step element"),N},N.resize=function(){return v(),N},N.enable=function(){return b(!0),N},N.disable=function(){return b(!1),N},N.offsetTrigger=function(t){return t&&(isNaN(t),!0)?(R=Math.min(Math.max(0,t),1),N):R},N.onStepEnter=function(t){return y.stepEnter=t,N},N.onStepExit=function(t){return y.stepExit=t,N},N.onStepProgress=function(t){return y.stepProgress=t,N},N.onContainerEnter=function(t){return y.containerEnter=t,N},N.onContainerExit=function(t){return y.containerExit=t,N},N}});
{
"name": "scrollama",
"version": "0.5.4",
"version": "0.6.0",
"description": "Lightweight scrollytelling library using IntersectionObserver",

@@ -8,6 +8,6 @@ "main": "build/scrollama.js",

"scripts": {
"dev": "rollup -w --config rollup.config.dev.js",
"pretest": "rm -rf build && mkdir build && rollup --config rollup.config.js",
"build": "npm run pretest && uglifyjs -m -c -- build/scrollama.js > build/scrollama.min.js",
"docs": "cp build/scrollama.js docs"
"dev": "cross-env NODE_ENV=development rollup -w -c",
"build": "cross-env NODE_ENV=production rollup -c && npm run docs",
"pretest": "npm run dev",
"docs": "cp build/scrollama.min.js docs"
},

@@ -34,12 +34,14 @@ "module": "index",

"devDependencies": {
"rollup": "^0.50.0",
"cross-env": "^5.1.1",
"rollup": "^0.51.8",
"rollup-plugin-babel": "^3.0.2",
"rollup-plugin-buble": "^0.16.0",
"rollup-plugin-commonjs": "^8.2.4",
"rollup-plugin-buble": "^0.18.0",
"rollup-plugin-commonjs": "^8.2.6",
"rollup-plugin-filesize": "^1.5.0",
"rollup-plugin-node-resolve": "^3.0.0",
"uglifyjs": "^2.4.11"
"rollup-plugin-uglify": "^2.0.1"
},
"dependencies": {
"intersection-observer": "^0.4.2"
"intersection-observer": "^0.4.3"
}
}
###### scrollama.js
<img src="https://russellgoldenberg.github.io/scrollama/logo.png" width="160" alt="scrollama.js"/>
**Scrollama** is a modern & lightweight JavaScript library for scrollytelling using [IntersectionObserver](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) in favor of scroll events.
**Scrollama** is a modern & lightweight JavaScript library for scrollytelling
using
[IntersectionObserver](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)
in favor of scroll events.
As seen on [The Pudding](https://pudding.cool/):
* [What is a Superteam in the NBA?](https://pudding.cool/2017/10/superteams/)
* [What City is the Microbrew Capital of the US?](https://pudding.cool/2017/04/beer/)
<!-- * [2017-18 NBA Top Plays](https://russellgoldenberg.github.io/reddit-nba-top-plays) -->
<!-- * [2017-18 NBA Top Plays](https://russellgoldenberg.github.io/reddit-nba-top-plays) -->

@@ -15,3 +20,11 @@ [Jump to examples.](https://github.com/russellgoldenberg/scrollama#examples)

Scrollytelling can be complicated to implement and difficult to make performant. The goal of this library is to provide a simple interface for creating scroll-driven interactives. Scrollama is focused on perfomance by using [IntersectionObserver](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) to handle element position detection. It offers an opinionated (but popular) scrollytelling pattern to reduce more involved DOM calculations. The sticky graphic pattern (enter-stick-exit) can be seen below. Check out my [blog post](https://pudding.cool/process/introducing-scrollama) for a deeper introduction.
Scrollytelling can be complicated to implement and difficult to make performant.
The goal of this library is to provide a simple interface for creating
scroll-driven interactives. Scrollama is focused on perfomance by using
[IntersectionObserver](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)
to handle element position detection. It offers an opinionated (but popular)
scrollytelling pattern to reduce more involved DOM calculations. The sticky
graphic pattern (enter-stick-exit) can be seen below. Check out my
[blog post](https://pudding.cool/process/introducing-scrollama) for a deeper
introduction.

@@ -25,3 +38,3 @@ [![scrollytelling pattern](https://thumbs.gfycat.com/FearfulHotArabianoryx-size_restricted.gif)](https://pudding.cool/process/how-to-implement-scrollytelling)

```html
<script src='scrollama.min.js'></script>
<script src='https://unpkg.com/scrollama'></script>
```

@@ -38,3 +51,3 @@

```js
import scrollama from 'scrollama' // or...
import scrollama from 'scrollama'; // or...
const scrollama = require('scrollama');

@@ -47,5 +60,10 @@ ```

You can use this library to simply trigger steps, similar to something like [Waypoints](http://imakewebthings.com/waypoints/). This is useful if you need more control over your interactive, or you don't want to follow the sticky scrollytelling pattern.
You can use this library to simply trigger steps, similar to something like
[Waypoints](http://imakewebthings.com/waypoints/). This is useful if you need
more control over your interactive, or you don't want to follow the sticky
scrollytelling pattern.
You can use any id/class naming conventions you want. The HTML structure should look like:
You can use any id/class naming conventions you want. The HTML structure should
look like:
```html

@@ -60,17 +78,20 @@ <!--you don't need the "data-step" attr, but can be useful for storing instructions for JS -->

// instantiate the scrollama
const scroller = Scrollama()
const scroller = Scrollama();
// setup the instance, pass callback functions
scroller.setup({
step: '.step', // required
offset: 0.5, // optional, default = 0.5
debug: false, // optional, default = false
})
.onStepEnter(handleStepEnter)
.onStepExit(handleStepExit)
scroller
.setup({
step: '.step', // required
offset: 0.5, // optional, default = 0.5
debug: false // optional, default = false
})
.onStepEnter(handleStepEnter)
.onStepExit(handleStepExit);
```
#### Sticky Graphic
To implement the sticky graphic scrollytelling pattern, you need the following three elements (container, graphic, steps). The structure should look like:
To implement the sticky graphic scrollytelling pattern, you need the following
three elements (container, graphic, steps). The structure should look like:
```html

@@ -94,16 +115,17 @@ <!-- container = ".scroll" -->

// instantiate the scrollama
const scroller = Scrollama()
const scroller = Scrollama();
// setup the instance, pass callback functions
scroller.setup({
step: '.scroll__text .step', // required
container: '.scroll', // required (for sticky)
graphic: '.scroll__graphic', // required (for sticky)
offset: 0.5, // optional, default = 0.5
debug: false, // optional, default = false
})
.onStepEnter(handleStepEnter)
.onStepExit(handleStepExit)
.onContainerEnter(handleContainerEnter)
.onContainerExit(handleContainerExit)
scroller
.setup({
step: '.scroll__text .step', // required
container: '.scroll', // required (for sticky)
graphic: '.scroll__graphic', // required (for sticky)
offset: 0.5, // optional, default = 0.5
debug: false // optional, default = false
})
.onStepEnter(handleStepEnter)
.onStepExit(handleStepExit)
.onContainerEnter(handleContainerEnter)
.onContainerExit(handleContainerExit);
```

@@ -115,16 +137,24 @@

*options:*
* `step` (string): Selector for the step elements that will trigger changes. **required**
* `container` (string): Selector for the element that contains everything for the scroller. **optional**
* `graphic` (string): Selector for the graphic element that will become fixed. **optional**
* `offset` (number, 0 - 1): How far from the top of the viewport to trigger a step. **(default: 0.5)**
* `progress` (boolean): Whether to fire incremental step progress updates or not. **(default: false)**
* `debug` (boolean): Whether to show visual debugging tools or not. **(default: false)**
_options:_
* `step` (string): Selector for the step elements that will trigger changes.
**required**
* `container` (string): Selector for the element that contains everything for
the scroller. **optional**
* `graphic` (string): Selector for the graphic element that will become fixed.
**optional**
* `offset` (number, 0 - 1): How far from the top of the viewport to trigger a
step. **(default: 0.5)**
* `progress` (boolean): Whether to fire incremental step progress updates or
not. **(default: false)**
* `debug` (boolean): Whether to show visual debugging tools or not. **(default:
false)**
#### scrollama.onStepEnter(callback)
Callback that fires when the top or bottom edge of a step element enters the offset threshold.
Callback that fires when the top or bottom edge of a step element enters the
offset threshold.
The argument of the callback is an object:
`{ element: DOMElement, index: number, direction: string }`
The argument of the callback is an object: `{ element: DOMElement, index:
number, direction: string }`

@@ -139,6 +169,7 @@ `element`: The step element that triggered

Callback that fires when the top or bottom edge of a step element exits the offset threshold.
Callback that fires when the top or bottom edge of a step element exits the
offset threshold.
The argument of the callback is an object:
`{ element: DOMElement, index: number, direction: string }`
The argument of the callback is an object: `{ element: DOMElement, index:
number, direction: string }`

@@ -155,4 +186,4 @@ `element`: The step element that triggered

The argument of the callback is an object:
`{ element: DOMElement, index: number, progress: number }`
The argument of the callback is an object: `{ element: DOMElement, index:
number, progress: number }`

@@ -167,6 +198,6 @@ `element`: The step element that triggered

Callback that fires when the top of container becomes flush with viewport *or* the graphic becomes fully in view coming from the bottom of the container.
Callback that fires when the top of container becomes flush with viewport _or_
the graphic becomes fully in view coming from the bottom of the container.
The argument of the callback is an object:
`{ direction: string }`
The argument of the callback is an object: `{ direction: string }`

@@ -177,6 +208,6 @@ `direction`: 'up' or 'down'

Callback that fires when the top of container goes below viewport *or* the graphic becomes not full in view leaving the bottom of the container.
Callback that fires when the top of container goes below viewport _or_ the
graphic becomes not full in view leaving the bottom of the container.
The argument of the callback is an object:
`{ direction: string }`
The argument of the callback is an object: `{ direction: string }`

@@ -191,7 +222,10 @@ `direction`: 'up' or 'down'

Tell scrollama to get latest dimensions the browser/DOM. It is best practice to throttle resize in your code, update the DOM elements, then call this function at the end.
Tell scrollama to get latest dimensions the browser/DOM. It is best practice to
throttle resize in your code, update the DOM elements, then call this function
at the end.
#### scrollama.enable()
Tell scrollama to resume observing for trigger changes. Only necessary to call if you have previously disabled.
Tell scrollama to resume observing for trigger changes. Only necessary to call
if you have previously disabled.

@@ -204,15 +238,27 @@ #### scrollama.disable()

*Note: most of these demos use D3 to keep the code concise, but this can be used with any library, or with no library at all.*
_Note: most of these demos use D3 to keep the code concise, but this can be used
with any library, or with no library at all._
* [Basic](https://russellgoldenberg.github.io/scrollama/basic) - just step triggers
* [Progress](https://russellgoldenberg.github.io/scrollama/progress) - incremental step progress callback
* [Sticky Graphic v1a (CSS, position sticky)](https://russellgoldenberg.github.io/scrollama/sticky-css) - using CSS vertically center chart, and position sticky (+ polyfill) for sticking.
* [Sticky Graphic v1b (JS, position sticky)](https://russellgoldenberg.github.io/scrollama/sticky-js) - using JS vertically center chart, and position sticky (+ polyfill) for sticking. Added bonus ability to start chart at top of steps then vertically.
* [Sticky Graphic v2a (CSS, position fixed)](https://russellgoldenberg.github.io/scrollama/fixed-css) - using CSS vertically center chart, and position fixed and absolute for sticking.
* [Sticky Graphic v2b (JS, position fixed)](https://russellgoldenberg.github.io/scrollama/fixed-js) - using read position fixed and absolute for sticking.
* [Basic](https://russellgoldenberg.github.io/scrollama/basic) - just step
triggers
* [Progress](https://russellgoldenberg.github.io/scrollama/progress) -
incremental step progress callback
* [Sticky Graphic v1a (CSS, position sticky)](https://russellgoldenberg.github.io/scrollama/sticky-css) -
using CSS vertically center chart, and position sticky (+ polyfill) for
sticking.
* [Sticky Graphic v1b (JS, position sticky)](https://russellgoldenberg.github.io/scrollama/sticky-js) -
using JS vertically center chart, and position sticky (+ polyfill) for
sticking. Added bonus ability to start chart at top of steps then vertically.
* [Sticky Graphic v2a (CSS, position fixed)](https://russellgoldenberg.github.io/scrollama/fixed-css) -
using CSS vertically center chart, and position fixed and absolute for
sticking.
* [Sticky Graphic v2b (JS, position fixed)](https://russellgoldenberg.github.io/scrollama/fixed-js) -
using read position fixed and absolute for sticking.
### Tips
* Always call `scrollama.resize()` after a window resize event to ensure scroll triggers update with new dimensions.
* Avoid using `viewport height` (vh) in your CSS because scrolling up and down constantly triggers vh to change, which will also trigger a window resize.
* Always call `scrollama.resize()` after a window resize event to ensure scroll
triggers update with new dimensions.
* Avoid using `viewport height` (vh) in your CSS because scrolling up and down
constantly triggers vh to change, which will also trigger a window resize.

@@ -231,2 +277,3 @@ ### To do

### Logo
Logo by the awesome [Elaina Natario](https://twitter.com/elainanatario)

@@ -240,6 +287,17 @@

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:
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 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.
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.
import commonjs from 'rollup-plugin-commonjs';
import resolve from 'rollup-plugin-node-resolve';
import buble from 'rollup-plugin-buble';
import uglify from 'rollup-plugin-uglify';
import filesize from 'rollup-plugin-filesize';
const isProd = process.env.NODE_ENV === 'production';
const file = `build/scrollama${isProd ? '.min' : ''}.js`;
const plugins = [
resolve({
jsnext: true,
main: true,
}),
commonjs({
sourceMap: false,
}),
buble(),
filesize(),
];
isProd && plugins.push(uglify());
export default {
input: 'index.js',
output: {
file: 'build/scrollama.js',
file,
format: 'umd',
},
name: 'scrollama',
plugins: [
resolve({
jsnext: true,
main: true,
}),
commonjs({
sourceMap: false,
}),
buble(),
],
plugins,
};

@@ -27,22 +27,48 @@ import { select, selectAll } from './dom';

let stepStates = null;
let previousYOffset = 0;
let direction = 'up';
function getIndex(element) {
return +element.getAttribute('data-scrollama-index');
}
function updateDirection() {
if (window.pageYOffset > previousYOffset) direction = 'down';
else if (window.pageYOffset < previousYOffset) direction = 'up';
previousYOffset = window.pageYOffset;
}
// NOTIFY CALLBACKS
function notifyStepEnter(element, direction) {
const index = +element.getAttribute('data-scrollama-index');
function notifyStepEnter(element) {
const index = getIndex(element);
const resp = { element, index, direction };
// store most recent trigger
stepStates[index].direction = direction;
stepStates[index].state = 'enter';
if (callback.stepEnter && typeof callback.stepEnter === 'function')
callback.stepEnter(resp);
if (progressMode) {
if (direction === 'up') notifyStepProgress(element, 1);
else notifyStepProgress(element, 0);
if (direction === 'down') notifyStepProgress(element, 0);
else notifyStepProgress(element, 1);
}
}
function notifyStepExit(element, direction) {
const index = +element.getAttribute('data-scrollama-index');
function notifyStepExit(element) {
const index = getIndex(element);
const resp = { element, index, direction };
// store most recent trigger
stepStates[index].direction = direction;
stepStates[index].state = 'exit';
if (callback.stepExit && typeof callback.stepExit === 'function')
callback.stepExit(resp);
if (progressMode) {
if (direction === 'up') notifyStepProgress(element, 0);
else notifyStepProgress(element, 1);
if (direction === 'down') notifyStepProgress(element, 1);
else notifyStepProgress(element, 0);
}

@@ -52,3 +78,3 @@ }

function notifyStepProgress(element, progress) {
const index = +element.getAttribute('data-scrollama-index');
const index = getIndex(element);
const resp = { element, index, progress };

@@ -59,3 +85,3 @@ if (callback.stepProgress && typeof callback.stepProgress === 'function')

function notifyContainerEnter(direction) {
function notifyContainerEnter() {
const resp = { direction };

@@ -69,3 +95,3 @@ if (

function notifyContainerExit(direction) {
function notifyContainerExit() {
const resp = { direction };

@@ -80,3 +106,4 @@ if (callback.containerExit && typeof callback.containerExit === 'function')

// bottom must be > 0 which means it is on "screen" (shifted by offset)
function intersectStepTop(entries) {
function intersectStepAbove(entries) {
updateDirection();
entries.forEach(entry => {

@@ -89,8 +116,16 @@ const {

} = entry;
// bottom is how far bottom edge of el is from top of viewport
const { bottom } = boundingClientRect;
const bottomAdjusted = bottom - offsetMargin;
const index = getIndex(target);
if (bottomAdjusted >= -ZERO_MOE) {
const direction = isIntersecting ? 'down' : 'up';
if (isIntersecting) notifyStepEnter(target, direction);
else notifyStepExit(target, direction);
if (isIntersecting && direction === 'down')
notifyStepEnter(target, direction);
else if (direction === 'up') {
// we went from exit to exit, must have skipped an enter
if (stepStates[index].state === 'exit')
notifyStepEnter(target, direction);
notifyStepExit(target, direction);
}
}

@@ -100,3 +135,4 @@ });

function intersectStepBottom(entries) {
function intersectStepBelow(entries) {
updateDirection();
entries.forEach(entry => {

@@ -111,11 +147,18 @@ const {

const bottomAdjusted = bottom - offsetMargin;
const index = getIndex(target);
if (
bottomAdjusted >= -ZERO_MOE &&
bottomAdjusted < height &&
isIntersecting
isIntersecting &&
direction === 'up'
) {
const direction = 'up';
notifyStepEnter(target, direction);
} else if (bottomAdjusted <= ZERO_MOE && !isIntersecting) {
const direction = 'down';
} else if (
bottomAdjusted <= ZERO_MOE &&
!isIntersecting &&
direction === 'down'
) {
if (stepStates[index].state === 'exit')
notifyStepEnter(target, direction);
notifyStepExit(target, direction);

@@ -126,3 +169,53 @@ }

// if there is a scroll even that skips the entire enter/exit of a step,
// fallback to trigger the enter/exit if element lands in viewport
function intersectViewportAbove(entries) {
updateDirection();
entries.forEach(entry => {
const {
isIntersecting,
intersectionRatio,
boundingClientRect,
target
} = entry;
const index = getIndex(target);
if (isIntersecting && direction === 'down') {
if (
stepStates[index].state === 'exit' &&
stepStates[index].direction === 'up'
) {
notifyStepEnter(target, 'down');
notifyStepExit(target, 'down');
}
}
});
}
function intersectViewportBelow(entries) {
updateDirection();
entries.forEach(entry => {
const {
isIntersecting,
intersectionRatio,
boundingClientRect,
target
} = entry;
const index = getIndex(target);
if (isIntersecting && direction === 'up') {
if (
stepStates[index].state === 'exit' &&
stepStates[index].direction === 'down'
) {
notifyStepEnter(target, 'up');
notifyStepExit(target, 'up');
}
}
});
}
function intersectStepProgress(entries) {
updateDirection();
entries.forEach(entry => {

@@ -145,6 +238,6 @@ const {

function intersectTop(entries) {
updateDirection();
const { isIntersecting, boundingClientRect } = entries[0];
const { top, bottom } = boundingClientRect;
if (bottom > -ZERO_MOE) {
const direction = isIntersecting ? 'down' : 'up';
if (isIntersecting) notifyContainerEnter(direction);

@@ -156,6 +249,6 @@ else notifyContainerExit(direction);

function intersectBottom(entries) {
updateDirection();
const { isIntersecting, boundingClientRect } = entries[0];
const { top } = boundingClientRect;
if (top < ZERO_MOE) {
const direction = isIntersecting ? 'up' : 'down';
if (isIntersecting) notifyContainerEnter(direction);

@@ -193,6 +286,6 @@ else notifyContainerExit(direction);

// top edge
function updateStepTopIO() {
if (io.stepTop) io.stepTop.forEach(d => d.disconnect());
function updateStepAboveIO() {
if (io.stepAbove) io.stepAbove.forEach(d => d.disconnect());
io.stepTop = stepEl.map((el, i) => {
io.stepAbove = stepEl.map((el, i) => {
const marginTop = stepHeights[i] - offsetMargin;

@@ -208,3 +301,3 @@ const marginBottom = -vh + offsetMargin;

const obs = new IntersectionObserver(intersectStepTop, options);
const obs = new IntersectionObserver(intersectStepAbove, options);
obs.observe(el);

@@ -216,6 +309,6 @@ return obs;

// bottom edge
function updateStepBottomIO() {
if (io.stepBottom) io.stepBottom.forEach(d => d.disconnect());
function updateStepBelowIO() {
if (io.stepBelow) io.stepBelow.forEach(d => d.disconnect());
io.stepBottom = stepEl.map((el, i) => {
io.stepBelow = stepEl.map((el, i) => {
const marginTop = -offsetMargin;

@@ -231,3 +324,3 @@ const marginBottom = -vh + stepHeights[i] + offsetMargin;

const obs = new IntersectionObserver(intersectStepBottom, options);
const obs = new IntersectionObserver(intersectStepBelow, options);
obs.observe(el);

@@ -238,2 +331,41 @@ return obs;

// jump into viewport
function updateViewportAboveIO() {
if (io.viewportAbove) io.viewportAbove.forEach(d => d.disconnect());
io.viewportAbove = stepEl.map((el, i) => {
const marginTop = 0;
const marginBottom = -(vh - offsetMargin + stepHeights[i]);
const rootMargin = `${marginTop}px 0px ${marginBottom}px 0px`;
const options = {
root: null,
rootMargin,
threshold: 0
};
const obs = new IntersectionObserver(intersectViewportAbove, options);
obs.observe(el);
return obs;
});
}
function updateViewportBelowIO() {
if (io.viewportBelow) io.viewportBelow.forEach(d => d.disconnect());
io.viewportBelow = stepEl.map((el, i) => {
const marginTop = -(offsetMargin + stepHeights[i]);
const marginBottom = 0;
const rootMargin = `${marginTop}px 0px ${marginBottom}px 0px`;
const options = {
root: null,
rootMargin,
threshold: 0
};
const obs = new IntersectionObserver(intersectViewportBelow, options);
obs.observe(el);
return obs;
});
}
// progress progress tracker

@@ -261,4 +393,6 @@ function updateStepProgressIO() {

function updateIO() {
updateStepTopIO();
updateStepBottomIO();
updateStepAboveIO();
updateStepBelowIO();
updateViewportAboveIO();
updateViewportBelowIO();

@@ -299,5 +433,7 @@ if (progressMode) updateStepProgressIO();

if (io.bottom) io.bottom.disconnect();
if (io.stepTop) io.stepTop.forEach(d => d.disconnect());
if (io.stepBottom) io.stepBottom.forEach(d => d.disconnect());
if (io.stepAbove) io.stepAbove.forEach(d => d.disconnect());
if (io.stepBelow) io.stepBelow.forEach(d => d.disconnect());
if (io.stepProgress) io.stepProgress.forEach(d => d.disconnect());
if (io.viewportAbove) io.viewportAbove.forEach(d => d.disconnect());
if (io.viewportBelow) io.viewportBelow.forEach(d => d.disconnect());
isEnabled = false;

@@ -311,2 +447,10 @@ }

function setupStepStates() {
stepStates = stepEl.map(() => ({
direction: null,
state: null,
bottom: -1
}));
}
function addDebug() {

@@ -366,2 +510,3 @@ if (debugMode) {

indexSteps();
setupStepStates();
if (progressMode) setThreshold();

@@ -368,0 +513,0 @@ handleResize();

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc