Maybe someone can figure out how to use https://developer.mozilla.org/en-US/docs/Web/API/History/scrollRestoration .. but my basic idea is to remember the scrollTop for each page.
This means we have to include a value for each URL, upon returning to a page we then need load the lazy items that establish the proper scrollTop.
I have used this MDN example and this debounce script as base and will only provide the necessary code changes - I included a number of images on "first_page" and "second_page". I used to provide link to a working example, but this was taken offline.
js/lazyload.js
'use strict';
// supports older browsers - no arrow functions, spread syntax or such *sigh*
var pLAZY = ( function(){
var version = "1.1.1"
, loadOffset = 512
, animatable = [ "IMG", "IFRAME" ] // tags that may/should be animated, e.g. *not* SOURCE
, loadingClasses = [] // [ 'animated', 'rubberBand' ] // change/disable animation style here
, lazyItems = {}
, lazyHandler_scroll = null
, lazyHandler_resize = null
, forceLoad_timer = null
;
function _debug(){ if( arguments.length ){ debug = parseInt( arguments[0] ); } return debug; }
function _version(){ return version; }
function docScroll(){ return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; } // cross-browser
function winHeight(){ return window.innerHeight || document.documentElement.clientHeight; }
function findLazyItems(){
lazyItems = {};
var candidates = document.querySelectorAll( '.lazyload,img[data-src],iframe[data-src],source[data-srcset]' );
for( var idx = 0; idx < candidates.length; idx++ ){
var domEl = candidates[idx]
, curTop = domEl.offsetTop // if you fail to ensure lazy loaded items do not force a reflow lower items may be loaded sooner
;
if( lazyItems.hasOwnProperty( curTop ) ){
lazyItems[curTop].push( domEl );
}else{
lazyItems[curTop] = [ domEl ];
}
}
}
function loadLazy(item){
var dsrc = item.getAttribute( 'data-src' )
, dset = item.getAttribute( 'data-srcset' )
, changed = false
, worked = false
;
if( dsrc && dsrc.length ){
item.setAttribute( 'src', dsrc );
item.removeAttribute( 'data-src' );
changed = true;
}
if( dset && dset.length ){
item.setAttribute( 'srcset', dset );
item.removeAttribute( 'data-srcset' );
changed = true;
}
if( changed ){
worked = true;
if( animatable.includes( item.nodeName ) ){
for( var lcx = 0; lcx < loadingClasses.length; lcx++ ){
item.classList.add( loadingClasses[lcx] );
}
}
}
return worked;
}
function checkShowItems(){
var curVisBar = docScroll() + winHeight() + loadOffset // winHeight, not docHeight!!
, worked = false
, str_pagePos = "0"
;
for( str_pagePos in lazyItems ){ // pagePos is a DOM.position().top value
var pagePos = parseInt( str_pagePos );
var lazy = lazyItems[ pagePos ]; // the items are constantly refreshed, double-effort is considered negligible
if( pagePos <= curVisBar ){
for( var idx = 0; idx < lazy.length; idx++ ){
if( loadLazy( lazy[idx] ) ) worked = true;
}
}
}
if( worked ){
findLazyItems();
if( Object.keys( lazyItems ).length == 0 ){
window.removeEventListener( 'scroll', lazyHandler_scroll );
window.removeEventListener( 'resize', lazyHandler_resize ); // this is not required anymore either
}
}
}
function waitForLoad(index,callback){
let count = 0
, done = 0
, str_pagePos = "0"
, worked = false
;
for( str_pagePos in lazyItems ){
var lazy = lazyItems[ parseInt( str_pagePos ) ];
for( let lx = 0; lx < lazy.length; lx++ ){
if( count <= index ){
if( lazy[lx].complete ) done++;
count++;
}
}
}
if( done == index ){
window.clearInterval( forceLoad_timer );
forceLoad_timer = null;
checkShowItems();
if(callback) callback();
}//else{ console.log( "%d/%d images done.", done, index ); }
}
function forceLoad(index,callback){
let count = 0
, str_pagePos = "0"
, worked = false
;
for( str_pagePos in lazyItems ){
var lazy = lazyItems[ parseInt( str_pagePos ) ];
for( let lx = 0; lx < lazy.length; lx++ ){
if( count <= index ){
if( loadLazy( lazy[lx] ) ) worked = true;
count++;
}
}
}
forceLoad_timer = window.setInterval( function(){ waitForLoad(index,callback); }, 50 );
}
function lowestSeenImage(){
var curVisBar = docScroll() + winHeight() + loadOffset // winHeight, not docHeight!!
, str_pagePos = "0"
, count = 0
;
for( str_pagePos in lazyItems ){
var pagePos = parseInt( str_pagePos );
var lazy = lazyItems[ pagePos ];
if( pagePos <= curVisBar ){
count += lazy.length;
}
}
return count;
}
function duringResizing(){
findLazyItems();
checkShowItems();
}
function hookListener( evtName, callback ){
var throttleTimer = arguments.length > 2 ? parseInt( arguments[2] ) : 250;
if( "Cowboy" in window ){
window.addEventListener( evtName, Cowboy.throttle( throttleTimer, callback ) );
}else{
//console.log( "without the Ben 'Cowboy' Almann throttle plugin we may choke the responsiveness of the user interface" );
window.addEventListener( evtName, callback );
}
}
function initialise(){
findLazyItems();
var seeTBjquery = ( ( "jQuery" in window ) && $.isFunction( $.throttle ) )
, seeTBplain = ( ( "Cowboy" in window ) && ( "throttle" in window.Cowboy ) )
;
if( seeTBjquery ){
lazyHandler_scroll = $.throttle( 250, checkShowItems );
lazyHandler_resize = $.throttle( 250, duringResizing );
}else{
if( seeTBplain ){
lazyHandler_scroll = Cowboy.throttle( 250, checkShowItems );
lazyHandler_resize = Cowboy.throttle( 250, duringResizing );
}else{
lazyHandler_scroll = checkShowItems;
lazyHandler_resize = duringResizing;
}
}
window.addEventListener( 'scroll', lazyHandler_scroll );
window.addEventListener( 'resize', lazyHandler_resize );
checkShowItems();
}
if( ! window.hasOwnProperty( "_pLAZY_disable_autoInit" ) ){
document.addEventListener( "DOMContentLoaded", function(){
initialise();
} );
}
return {
"debug": _debug
, "version": _version
, "findLazyItems": findLazyItems
, "checkShowItems": checkShowItems
, "forceLoad": forceLoad
, "lowestSeenImage": lowestSeenImage
, "duringResizing": duringResizing
, "hookListener": hookListener
, "initialize": initialise // american spelling
, "initialise": initialise
};
} )();
js/ajax_nav.js
"use strict";
const ajaxRequest = new (function () {
function closeReq () {
oLoadingBox.parentNode && document.body.removeChild(oLoadingBox);
bIsLoading = false;
}
function abortReq () {
if (!bIsLoading) { return; }
oReq.abort();
closeReq();
}
function ajaxError () {
alert("Unknown error.");
}
// NOT PART OF MDN EXAMPLE::
function restoreScroll(){
if( oPageInfo.url in oPageInfo.scrollTop ){
window.scrollTo( 0, oPageInfo.scrollTop[oPageInfo.url] ); // always left-most
}
}
function loadAndScroll(){
if( "pLAZY" in window ) pLAZY.forceLoad( oPageInfo.lowestSeenImage[oPageInfo.url], restoreScroll );
}
// ::NOT PART OF MDN EXAMPLE
function ajaxLoad () {
var vMsg, nStatus = this.status;
switch (nStatus) {
case 200:
vMsg = JSON.parse(this.responseText);
document.title = oPageInfo.title = vMsg.page;
document.getElementById(sTargetId).innerHTML = vMsg.content;
if (bUpdateURL) {
history.pushState(oPageInfo, oPageInfo.title, oPageInfo.url);
bUpdateURL = false;
// NOT PART OF MDN EXAMPLE::
if( "pLAZY" in window ){
window.pLAZY.initialise();
if( oPageInfo.lowestSeenImage[oPageInfo.url] > -1 ){
loadAndScroll();
}else{
restoreScroll();
}
}//else console.log("without pLAZY no scrollPos restore possible");
ajaxRequest.rebuildLinks();
// ::NOT PART OF MDN EXAMPLE
}
break;
default:
vMsg = nStatus + ": " + (oHTTPStatus[nStatus] || "Unknown");
switch (Math.floor(nStatus / 100)) {
/*
case 1:
// Informational 1xx
console.log("Information code " + vMsg);
break;
case 2:
// Successful 2xx
console.log("Successful code " + vMsg);
break;
case 3:
// Redirection 3xx
console.log("Redirection code " + vMsg);
break;
*/
case 4:
/* Client Error 4xx */
alert("Client Error #" + vMsg);
break;
case 5:
/* Server Error 5xx */
alert("Server Error #" + vMsg);
break;
default:
/* Unknown status */
ajaxError();
}
}
closeReq();
}
function filterURL (sURL, sViewMode) {
return sURL.replace(rSearch, "") + ("?" + sURL.replace(rHost, "&").replace(rView, sViewMode ? "&" + sViewKey + "=" + sViewMode : "").slice(1)).replace(rEndQstMark, "");
}
function getPage (sPage) {
if (bIsLoading) { return; }
oReq = new XMLHttpRequest();
bIsLoading = true;
oReq.onload = ajaxLoad;
oReq.onerror = ajaxError;
// NOT IN MDN EXAMPLE ::
oPageInfo.scrollTop[oPageInfo.url] = document.documentElement.scrollTop;
oPageInfo.lowestSeenImage[oPageInfo.url] = ( "pLAZY" in window ) ? pLAZY.lowestSeenImage() : 0;
// :: NOT IN MDN EXAMPLE
if (sPage) { oPageInfo.url = filterURL(sPage, null); }
oReq.open("get", filterURL(oPageInfo.url, "json"), true);
oReq.send();
oLoadingBox.parentNode || document.body.appendChild(oLoadingBox);
}
function requestPage (sURL) {
if (history.pushState) {
bUpdateURL = true;
getPage(sURL);
} else {
/* Ajax navigation is not supported */
location.assign(sURL);
}
}
function processLink () {
if (this.className === sAjaxClass) {
requestPage(this.href);
return false;
}
return true;
}
function init () {
oPageInfo.title = document.title;
for (var oLink, nIdx = 0, nLen = document.links.length; nIdx < nLen; document.links[nIdx++].onclick = processLink);
}
const
/* customizable constants */
sTargetId = "ajax-content", sViewKey = "view_as", sAjaxClass = "ajax-nav",
/* not customizable constants */
rSearch = /\?.*$/, rHost = /^[^\?]*\?*&*/, rView = new RegExp("&" + sViewKey + "\\=[^&]*|&*$", "i"), rEndQstMark = /\?$/,
oLoadingBox = document.createElement("div"), oCover = document.createElement("div"), oLoadingImg = new Image(),
oPageInfo = {
title: null,
scrollTop: {}, // NOT PART OF MDN EXAMPLE
lowestSeenImage: {}, // NOT PART OF MDN EXAMPLE
url: location.href
}, oHTTPStatus = /* http://www.iana.org/assignments/http-status-codes/http-status-codes.xml */ {
100: "Continue",
101: "Switching Protocols",
102: "Processing",
200: "OK",
201: "Created",
202: "Accepted",
203: "Non-Authoritative Information",
204: "No Content",
205: "Reset Content",
206: "Partial Content",
207: "Multi-Status",
208: "Already Reported",
226: "IM Used",
300: "Multiple Choices",
301: "Moved Permanently",
302: "Found",
303: "See Other",
304: "Not Modified",
305: "Use Proxy",
306: "Reserved",
307: "Temporary Redirect",
308: "Permanent Redirect",
400: "Bad Request",
401: "Unauthorized",
402: "Payment Required",
403: "Forbidden",
404: "Not Found",
405: "Method Not Allowed",
406: "Not Acceptable",
407: "Proxy Authentication Required",
408: "Request Timeout",
409: "Conflict",
410: "Gone",
411: "Length Required",
412: "Precondition Failed",
413: "Request Entity Too Large",
414: "Request-URI Too Long",
415: "Unsupported Media Type",
416: "Requested Range Not Satisfiable",
417: "Expectation Failed",
422: "Unprocessable Entity",
423: "Locked",
424: "Failed Dependency",
425: "Unassigned",
426: "Upgrade Required",
427: "Unassigned",
428: "Precondition Required",
429: "Too Many Requests",
430: "Unassigned",
431: "Request Header Fields Too Large",
500: "Internal Server Error",
501: "Not Implemented",
502: "Bad Gateway",
503: "Service Unavailable",
504: "Gateway Timeout",
505: "HTTP Version Not Supported",
506: "Variant Also Negotiates (Experimental)",
507: "Insufficient Storage",
508: "Loop Detected",
509: "Unassigned",
510: "Not Extended",
511: "Network Authentication Required"
};
var
oReq, bIsLoading = false, bUpdateURL = false;
oLoadingBox.id = "ajax-loader";
oCover.onclick = abortReq;
oLoadingImg.src = "data:image/gif;base64,R0lGODlhEAAQAPIAAP///wAAAMLCwkJCQgAAAGJiYoKCgpKSkiH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQJCgAAACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkECQoAAAAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkECQoAAAAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkECQoAAAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQJCgAAACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQJCgAAACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAkKAAAALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA==";
oCover.appendChild(oLoadingImg);
oLoadingBox.appendChild(oCover);
onpopstate = function (oEvent) {
bUpdateURL = false;
oPageInfo.title = oEvent.state.title;
oPageInfo.scrollTop = oEvent.state.scrollTop; // NOT PART OF MDN EXAMPLE
oPageInfo.lowestSeenImage = oEvent.state.lowestSeenImage; // NOT PART OF MDN EXAMPLE
oPageInfo.url = oEvent.state.url;
getPage();
};
window.addEventListener ? addEventListener("load", init, false) : window.attachEvent ? attachEvent("onload", init) : (onload = init);
// Public methods
this.open = requestPage;
this.stop = abortReq;
this.rebuildLinks = init;
})();