We were recently asked to source and install an ajax infinite scroll module on a client's Magento2 site. An ajax list/scroll module allows the user to see the next pages of products without loading a whole new page. There are lots of modules out there for this purpose, but unfortunately most are flawed in one way or another.

The main issue with all the ones we auditioned - including one from a Magento Gold partner (not naming names), was on mobile/tablet. A serious issue given upto 2/3 of traffic is from such devices these days. The issue is when you click to a product and then press back, the product is either not showing, or the 'load more products' dialogue never loads as it was stopped when you clicked to the product.

So, we made our own script for this purpose, which I'd like to share with you. There are 3 steps to make this work on your theme;

Step 1 - Add product block identifier

Open your theme's list.phtml template, usually located in;


/app/design/frontend/VENTOR/THEME/Magento_Catalog/templates/product/list.phtml


Within this file, find the foreach loop which iterates over the products. If you are using grid and list view, you will have 2 of these in the file.


<?php foreach ($_productCollection as $_product): ?>
<?php /* @escapeNotVerified */ echo($iterator++ == 1) ? '<li class="item product product-item">' : '</li><li class="item product product-item">' ?>


In order for this to be able to return directly back to the product which was clicked when the user presses back, we need to add the product ID to the container block, like this;


<?php foreach ($_productCollection as $_product): ?>
<?php /* @escapeNotVerified */ echo($iterator++ == 1) ? '<li class="item product product-item" id="pmpid_'.$_product->getId().'">' : '</li><li class="item product product-item" id="pmpid_'.$_product->getId().'">' ?>


Step 2 - Add javascript

At the end of the same file (list.phtml) add the following;



<script type="text/javascript">
require(['jquery'],function($){

$jq = jQuery.noConflict();

// Setup
pagination = false;
previouslink = false;
nextlink = false;

// Force go to product
$jq(document).ready(function() {
var hash = window.location.hash.substr(1);
if(typeof hash != 'undefined' && hash.length) {
$jq('html, body').animate({ scrollTop: $('#'+hash).offset().top}, 50);
}
});

// Load next button if applicable
var pagination = $jq(".pages").html();

if(typeof pagination != 'undefined' && pagination.length) {
var nextlink = $jq(".pages-item-next a").attr("href");
if(typeof nextlink != 'undefined' && nextlink.length) {
var pnum = getParameterByName("p", nextlink)
var nextHtml = '<div class="pagenum_'+pnum+' ajaxstaging"></div><div class="next-load pm-ajax-button" data-next="'+nextlink+'" data-pagenumber="'+pnum+'"><span>LOAD MORE PRODUCTS</span></div>';
$jq(".products-grid").append(nextHtml);
}
}

// Load previous button if applicable
if(typeof pagination != 'undefined' && pagination.length) {
var previouslink = $jq(".pages-item-previous a").attr("href");
if(typeof previouslink != 'undefined' && previouslink.length) {
var pnum = getParameterByName("p", previouslink)
var previousHtml = '<div class="pagenum_'+pnum+' ajaxstaging"></div><div class="previous-load pm-ajax-button" data-next="'+previouslink+'" data-pagenumber="'+pnum+'"><span>LOAD PREVIOUS PRODUCTS</span></div>';
$jq(".products-grid").prepend(previousHtml);
}
}

// Load next function
function loadNext() {
var nextLink = $jq("body .next-load").data("next");
var pnum = $jq("body .next-load").data("pagenumber");
if(nextLink.length) {
$jq.ajax({

url: nextLink,
cache: false,
success: function(response) {
nextlinknext = false;
pnumnext = false;
items = $jq(response).find(".product-items");
$jq(response).find(".pages-item-next a").each(function() {
if($jq(this).attr("href").length) {
nextlinknext = $jq(this).attr("href");
pnumnext = getParameterByName("p", nextlinknext);
}
});
addItems(".pagenum_"+pnum,items,nextLink,"append");
$jq(".next-load").remove();
if(nextlinknext.length) {
var nextHtml = '<div class="pagenum_'+pnumnext+' ajaxstaging"></div><div class="next-load pm-ajax-button" data-next="'+nextlinknext+'" data-pagenumber="'+pnumnext+'"><span>LOAD MORE PRODUCTS</span></div>';
$jq(".products-grid").append(nextHtml);
}

}
});
return true;
}
}

function loadPrevious() {
var nextLink = $jq("body .previous-load").data("next");
var pnum = $jq("body .previous-load").data("pagenumber");
if(nextLink.length) {
$jq.ajax({
url: nextLink,
cache: false,
success: function(response) {
nextlinknext = false;
pnumnext = false;
items = $jq(response).find(".product-items");
$jq(response).find(".pages-item-previous a").each(function() {
if($jq(this).attr("href").length) {
nextlinknext = $jq(this).attr("href");
pnumnext = getParameterByName("p", nextlinknext);
}
});
addItems(".pagenum_"+pnum,items,nextLink,'prepend');
$jq(".previous-load").remove();
if(nextlinknext.length) {
var nextHtml = '<div class="pagenum_'+pnumnext+' ajaxstaging"></div><div class="previous-load pm-ajax-button" data-next="'+nextlinknext+'" data-pagenumber="'+pnumnext+'"><span>LOAD PREVIOUS PRODUCTS</span></div>';
$jq(".products-grid").prepend(nextHtml);
}
}
});
return true;
}
}

$jq("body").on("click",".next-load span",function() {
$jq(this).parent().addClass("pm-ajax-running");
loadNext();
});

$jq("body").on("click",".previous-load span",function() {
$jq(this).parent().addClass("pm-ajax-running");
loadPrevious();
});

$jq("body").on("click",".product-item a",function(e) {

e.preventDefault();
returnUrl = false;
var returnUrl = $jq(this).data("returnurl");
var pid = $jq(this).closest("li").attr("id");
var productUrl = $jq(this).attr("href");
if(returnUrl) {
returnUrl = returnUrl.replace("html/?","html?");
} else {
var href = window.location.href;
if(href.indexOf("#") > 0){
href = href.split("#")[0];
returnUrl = href.replace("html/?","html?");;
} else {
returnUrl = href.replace("html/?","html?");
}

}
history.pushState('data', '', returnUrl+"#"+pid);
window.location.href = productUrl;

});


function addItems(target,html,returnUrl,dest) {
$jq(target).html(html);
$jq(target+" a").each(function() {
$jq(this).attr("data-returnurl",returnUrl);
});

newHtml = $jq(target+" ol").html();
if(dest == 'prepend') {
$jq(".product-items").prepend(newHtml);
} else {
$jq(".product-items").append(newHtml);
}
$jq(target).remove();

}

function getParameterByName(name, url) {
if (!url) {
url = window.location.href;
}
name = name.replace(/[\[\]]/g, "\\$&");
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, " "));
}


});



</script>

If you're looking for an animated loading gif, these guys are very good and free: http://loading.io/.

Enjoy.