inflation-explorer/shared/js/scrollytellerProgress.js (95 lines of code) (raw):
import { sum, supportsSticky } from "./util.js"
// this module returns a number between 0 and 1 charting the progress of the whole scrolly and each individual section - see READ.me for details
class ScrollyTeller {
constructor(config) {
this.isMobile = window.innerWidth < 740;
this.triggerTop = (!this.isMobile) ? config.triggerTop : config.triggerTopMobile;
this.scrollInner = config.parent.querySelector(".scroll-inner");
this.scrollText = config.parent.querySelector(".scroll-text");
this.scrollWrapper = config.parent.querySelector(".scroll-wrapper");
this.lastScroll = null;
this.lastI = null;
this.triggerPoints = [];
this.textBoxes = [].slice.apply(this.scrollText.querySelectorAll(".scroll-text__inner"));
this.transparentUntilActive = config.transparentUntilActive;
this.scrollWrapper.style.height = this.textBoxes.map( el => el.getBoundingClientRect().height ).reduce(sum, 0) + 'px'
this.on = true
this.onOverallProgress = () => {}; // default here should be improved
if(this.transparentUntilActive) {
config.parent.classList.add("transparent-until-active");
}
}
gradual( f ) {
this.onGradualChange = f
}
overall (f) {
this.onOverallProgress = f
}
toggle () {
this.on = !this.on
}
checkScroll() {
if(this.on && this.lastScroll !== window.pageYOffset) {
const bbox = this.scrollText.getBoundingClientRect();
if(!supportsSticky) {
if(bbox.top <= 0 && bbox.bottom >= window.innerHeight) {
this.scrollInner.classed("fixed-top", true);
this.scrollInner.classed("absolute-bottom", false);
this.scrollInner.classed("absolute-top", false);
} else if(bbox.top <= 0) {
this.scrollInner.classed("fixed-top", false);
this.scrollInner.classed("absolute-bottom", true);
this.scrollInner.classed("absolute-top", false);
} else {
this.scrollInner.classed("fixed-top", false);
this.scrollInner.classed("absolute-bottom", false);
this.scrollInner.classed("absolute-top", true);
}
}
const scrollyStartPoint = window.innerHeight * this.triggerTop // point in window at which scrolly is triggered
if(bbox.top < scrollyStartPoint && bbox.bottom > scrollyStartPoint) {
let indx = 0
const neg = Math.floor(Math.abs(bbox.top - (window.innerHeight*(this.triggerTop))))
indx = this.textBoxes.findIndex( (el, j,arr) => {
const soFar = arr.slice(0, j).map( el => el.getBoundingClientRect().height ).reduce(sum, 0)
return soFar > neg
} ) - 1
const i = indx < 0 ? this.textBoxes.length -1 : indx; // default to the last box if box indx is not found
const overallP = (bbox.top - scrollyStartPoint)/bbox.height*(-1)
this.onOverallProgress(overallP)
const prevBox = this.textBoxes[i]
const abs = window.innerHeight*this.triggerTop - prevBox.getBoundingClientRect().top
const total = prevBox.getBoundingClientRect().height
const progressInBox = abs / total;
this.onGradualChange(progressInBox, i, abs, total)
if(i !== this.lastI) {
this.lastI = i;
this.doScrollAction(i);
if(this.transparentUntilActive) {
this.textBoxes.forEach((el, j) => {
if(j <= i) {
el.style.opacity = "1";
} else {
el.style.opacity = "0.25";
}
});
}
}
}
this.lastScroll = window.pageYOffset;
}
window.requestAnimationFrame(this.checkScroll.bind(this));
}
doScrollAction(i) {
const trigger = this.triggerPoints.find(d => d.num === i+1);
if(trigger) {
trigger.do();
}
}
watchScroll() {
window.requestAnimationFrame(this.checkScroll.bind(this));
}
addTrigger(t) {
this.triggerPoints.push(t);
}
}
export default ScrollyTeller