import customEvent from '../utils/customEvent';
/**
* Displays 'Back to Top' button with circle progress bar
*
* @module ReadProgress
* @prop {String} buttonDivSelector - Class reference for Back to Top button container
* @prop {String} buttonSelector - Class reference for Back to Top button
* @prop {String} circleSelector - Class reference for circle element
* @prop {String} hiddenClass - Hiding class
* @prop {String} mainSelector - Class reference for the main element
* @prop {String} summarySelector - Class reference for the summary section
* (used on Results template)
* @prop {String} interactiveSelector - Class reference for the interactive section
* (used on Results template)
* @prop {String} articleSelector - Class reference for the article wrapper
* @prop {String} mainResultsClass - Results template class
* @prop {Object} circle - DOM reference to the circle element
* @prop {Integer} topLocation - Measurable content start location from top of the document
* @prop {Integer} bottomLocation - Measurable content end location from top of the document
* @prop {Integer} measurableHeight - Measurable content height
* @prop {Object} $buttonDiv - DOM reference to Back to Top button
*/
const ReadProgress = {
buttonDivSelector: '.c-readProgress',
buttonSelector: '.c-readProgress__button',
circleSelector: '.c-readProgress__circle',
stickyOffsetClass: 'c-readProgress--stickyOffset',
hiddenClass: 'is-hidden',
mainSelector: '.l-main',
summarySelector: '.l-main__summary',
interactiveSelector: '.l-main__interactive',
articleSelector: '.l-main__article',
mainResultsClass: 'l-main--results',
circle: false,
topLocation: 0,
bottomLocation: 0,
measurableHeight: 0,
$buttonDiv: null,
/**
* Initializes 'Back To Top' button on article pages
*
* @method init
*/
init() {
this.$buttonDiv = document.querySelector( this.buttonDivSelector );
// Make sure there is a button container on the page
if ( ! this.$buttonDiv ) {
return;
}
this.circle = document.querySelector( this.circleSelector );
// Bind click event to the button
const $button = document.querySelector( this.buttonSelector );
if ( $button ) {
// markup has is-hidden applied to avoid big gap rendering on page load.
$button.classList.remove( this.hiddenClass );
$button.addEventListener( 'click', () => {
this.handleReadProgressClick();
});
}
window.addEventListener( customEvent.SCROLLED_TO_TOP, () => this.updatePercentage() );
window.addEventListener( customEvent.SCROLLED, () => this.updatePercentage() );
// Adds an offset to the ReadProgress button when sticky video is visible
window.addEventListener( customEvent.STICKY_ON, () => {
this.$buttonDiv.classList.add( this.stickyOffsetClass );
});
// Removes an offset from ReadProgress button when sticky video is not visible
window.addEventListener( customEvent.STICKY_OFF, () => {
this.$buttonDiv.classList.remove( this.stickyOffsetClass );
});
// We include only the 'article' element into the article reading length on most templates
// Measure the article height, and it's position from the top of the document
const article = document.querySelector( this.articleSelector );
this.topLocation = article.offsetTop;
this.bottomLocation = this.topLocation + article.offsetHeight;
this.measurableHeight = article.offsetHeight;
const $main = document.querySelector( this.mainSelector );
// On results template we include the 'summary', 'interactive' and 'article' elements
// into the article reading length due to different markup
if ( $main.classList.contains( this.mainResultsClass ) ) {
const summarySection = document.querySelector( this.summarySelector );
this.topLocation = summarySection.offsetTop;
const summaryHeight = summarySection.offsetHeight;
const interactiveSection = document.querySelector( this.interactiveSelector );
let interactiveHeight = 0;
if ( interactiveSection ) {
interactiveHeight = interactiveSection.offsetHeight;
}
this.bottomLocation = this.topLocation + summaryHeight + interactiveHeight + article.offsetHeight; // eslint-disable-line max-len
this.measurableHeight = summaryHeight + interactiveHeight + article.offsetHeight;
}
},
/**
* Updates the percentage bar in the circle
*
* @method updatePercentage
*/
updatePercentage() {
// Find current scroll position from the top of the document
// to the bottom of the visible screen
const currentPosition = window.pageYOffset + window.innerHeight;
let readPercentage = 0;
if ( currentPosition > this.bottomLocation ) {
readPercentage = 100;
} else if ( currentPosition < this.topLocation ) {
readPercentage = 0;
} else {
const positionInArticle = currentPosition - this.topLocation;
readPercentage = Math.round( ( positionInArticle / this.measurableHeight ) * 100 );
}
// Convert percentage into stroke-dashoffset value
// Note: use the reverse percentage value
// Ex. if you are 25% into the article, use the remaing 75% to calculate stroke-dashoffset
const dashOffset = ( 195 / 100 ) * ( 100 - readPercentage );
this.circle.style.strokeDashoffset = dashOffset;
},
/**
* Handles click on 'Back To Top' button
*
* @method handleReadProgressClick
*/
handleReadProgressClick() {
window.scroll({ top: 0, behavior: 'smooth' });
},
};
export default ReadProgress;