/** * @name Reload-in-place v2.0 * @desc A simple script to add to your pagedjs project. On reload, it will make the web browser scroll to the place it was before reload. * Useful when styling or proof correcting your book. Multi docs compatible and doesn't wait for complete compilation to go. * @author Nicolas Taffin * @author Sameh Chafik * @author (adapted by) Benjamin G. * @license MIT * @see { @link https://gitlab.com/csspageweaver/plugins/reloadInPlace } */ import { Handler } from '/csspageweaver/lib/paged.esm.js'; export default class reloadInPlace extends Handler { constructor(chunker, polisher, caller) { super(chunker, polisher, caller); this.parameters = cssPageWeaver.features.reloadInPlace.parameters || {} this.isScrollBlured = this.parameters.blur || true; this.scrollBehavior = this.parameters.scrollBehavior || 'instant'; // set a "unique" filename based on title element, in case several books are opened this.fileTitle = document.getElementsByTagName("title")[0].text.replace(/ /g, ""); } beforeParsed() { // separate human / machine scroll this.parameters.machineScroll = false; // check pagedJS ended compilation this.parameters.cssPageWeaverEnd = false; // Make it blur if needed this.isBlur() // Start saving scroll position early (don't wait for render to complete) let _ = this; var slowSave = this.debounce(() => { if(!_.parameters.machineScroll) { _.saveAmountScrolled(); } }, 100); window.addEventListener('scroll', slowSave); } afterParsed() { this.moveFast(); } afterRendered(pages) { this.parameters.cssPageWeaverEnd = true; } getDocHeight() { var D = document; return Math.max( D.body.scrollHeight, D.documentElement.scrollHeight, D.body.offsetHeight, D.documentElement.offsetHeight, D.body.clientHeight, D.documentElement.clientHeight ) } saveAmountScrolled(){ var scrollArray = []; var scrollTop = window.pageYOffset || (document.documentElement || document.body.parentNode || document.body).scrollTop if (!this.parameters.machineScroll) { var scrollLeft = window.pageXOffset || (document.documentElement || document.body.parentNode || document.body).scrollLeft scrollArray.push({ X: Math.round(scrollLeft), Y: Math.round(scrollTop) }); //console.log("Saved ", scrollArray); localStorage['reloadInPlace-' + this.fileTitle] = JSON.stringify(scrollArray); } } isBlur(){ // Apply a blur effect if scroll blurring is enabled if (this.isScrollBlured) { var styleEl = document.createElement('style'); styleEl.setAttribute("data-reload-in-place", true) document.head.appendChild(styleEl); this.styleSheet = styleEl.sheet; this.styleSheet.insertRule('.pagedjs_pages { filter: blur(3px); }', 0); } } retrievePosition(){ // Retrieve saved scroll data from localStorage var savedData = localStorage.getItem('reloadInPlace-' + this.fileTitle); if (savedData) { // Parse the saved data to get the scroll positions var scrollArray = JSON.parse(savedData); this.scrollTop = scrollArray[0].Y; this.scrollLeft = scrollArray[0].X; } else { // Default scroll positions if no saved data is found this.scrollTop = 0; this.scrollLeft = 0; } } /** * Adjusts the scroll position of the window based on saved data. * This function handles scrolling behavior when the document height changes, * ensuring the view returns to a previously saved scroll position or the bottom of the document. * * @param {Object} _ - ReloadInPlace scope */ moveFast() { // Set the machine scroll flag to true this.parameters.machineScroll = true; this.retrievePosition() // Get the window height var winheight = window.innerHeight || (document.documentElement || document.body).clientHeight; let _ = this // Track last scroll position to detect user scroll let lastScrollTop = window.pageYOffset || document.documentElement.scrollTop; // Detect user manual scroll and stop auto-scroll const userScrollHandler = () => { if (!_.parameters.machineScroll) return; const currentScroll = window.pageYOffset || document.documentElement.scrollTop; const expectedScroll = _.scrollTop > _.docheight - winheight ? _.docheight : _.scrollTop; // If scroll position differs significantly from expected, user is scrolling if (Math.abs(currentScroll - expectedScroll) > 100 && Math.abs(currentScroll - lastScrollTop) > 50) { _.stopAutoScroll(); } lastScrollTop = currentScroll; }; window.addEventListener('wheel', () => _.stopAutoScroll(), { once: true }); window.addEventListener('touchstart', () => _.stopAutoScroll(), { once: true }); // Set up an interval to adjust the scroll position _.currentInterval = setInterval(() => { // Get the current document height _.docheight = _.getDocHeight(); // Check if the saved scroll position is beyond the current document height if ( _.scrollTop > 0 && _.scrollTop > _.docheight - winheight && !_.parameters.cssPageWeaverEnd) { // Scroll to the bottom of the document window.scrollTo({ left: _.scrollLeft, top: _.docheight, behavior: _.scrollBehavior }); } else { // Scroll to the saved position window.scrollTo({ left: _.scrollLeft, top: _.scrollTop, behavior: _.scrollBehavior }); // Clear interval clearInterval(_.currentInterval); // set a timeout to finalize the scroll position setTimeout(function () { window.scrollTo({ left: _.scrollLeft, top: _.scrollTop, behavior: _.scrollBehavior }); // Reset the machine scroll flag _.parameters.machineScroll = false; // Remove the blur effect if it was applied if (_.isScrollBlured) { _.styleSheet.deleteRule(0); } }, 50); // Delay to start } }, 50); // Refresh frequency } stopAutoScroll() { if (this.currentInterval) { clearInterval(this.currentInterval); this.currentInterval = null; } this.parameters.machineScroll = false; // Remove blur if applied if (this.isScrollBlured && this.styleSheet && this.styleSheet.cssRules.length > 0) { this.styleSheet.deleteRule(0); } } debounce(func, wait, immediate) { var timeout; return function() { var context = this, args = arguments; var later = function() { timeout = null; if (!immediate) func.apply(context, args); }; var callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) func.apply(context, args); }; }; }