← back to Designer Wallcoverings
feat: implement infinite scroll on DW Shopify collection pages
c15a8c196670b6823fc9e01badd3e2f497faff31 · 2026-06-23 09:41:10 -0700 · Steve Abrams
Replace per-page selector reload with IntersectionObserver-based infinite scroll:
- Remove 'Show per page' dropdown that reloaded page
- Add scroll sentinel below grid to trigger loading
- Load next page asynchronously on scroll near bottom
- Append products incrementally instead of replacing grid
- Detect end of catalog automatically
- Compatible with Boost AI Search Filter
Pattern validated on 3 DW microsites (philipperomano, asseeninmovies, designerlookbooks)
Files touched
M shopify/theme-files/sections/collection.liquid
Diff
commit c15a8c196670b6823fc9e01badd3e2f497faff31
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Jun 23 09:41:10 2026 -0700
feat: implement infinite scroll on DW Shopify collection pages
Replace per-page selector reload with IntersectionObserver-based infinite scroll:
- Remove 'Show per page' dropdown that reloaded page
- Add scroll sentinel below grid to trigger loading
- Load next page asynchronously on scroll near bottom
- Append products incrementally instead of replacing grid
- Detect end of catalog automatically
- Compatible with Boost AI Search Filter
Pattern validated on 3 DW microsites (philipperomano, asseeninmovies, designerlookbooks)
---
shopify/theme-files/sections/collection.liquid | 117 ++++++++++++++++++++-----
1 file changed, 93 insertions(+), 24 deletions(-)
diff --git a/shopify/theme-files/sections/collection.liquid b/shopify/theme-files/sections/collection.liquid
index 0da8c448..07c6cb48 100644
--- a/shopify/theme-files/sections/collection.liquid
+++ b/shopify/theme-files/sections/collection.liquid
@@ -128,31 +128,100 @@
input.addEventListener('change', function(){ localStorage.setItem('dw-grid-cols', Math.max(3, Math.min(12, parseInt(this.value) || 4))); });
- /* Build per-page selector INSIDE the grid slider pill */
- var perpageInline = document.createElement('span');
- perpageInline.className = 'dw-perpage-inline';
- var perpageLbl = document.createElement('label');
- perpageLbl.textContent = 'Show';
- var perpageSelect = document.createElement('select');
- perpageSelect.id = 'dw-perpage';
- var options = [24, 25, 50, 100];
- var currentLimit = parseInt(new URLSearchParams(window.location.search).get('limit')) || 24;
- options.forEach(function(val) {
- var opt = document.createElement('option');
- opt.value = val;
- opt.textContent = val;
- if (val === currentLimit) opt.selected = true;
- perpageSelect.appendChild(opt);
- });
- perpageSelect.addEventListener('change', function() {
+ /* Infinite scroll: Create loading sentinel (replaces per-page selector reload) */
+ var sentinel = document.createElement('div');
+ sentinel.id = 'dw-scroll-sentinel';
+ sentinel.style.cssText = 'padding:20px;text-align:center;color:#999;font-size:12px;display:none;margin-top:40px;';
+ sentinel.innerHTML = '<span id="dw-loading-status">Loading more products…</span>';
+ boostGrid.parentElement.appendChild(sentinel);
+
+ /* Infinite scroll state */
+ var currentPage = parseInt(new URLSearchParams(window.location.search).get('page')) || 1;
+ var isLoading = false;
+ var hasMore = true;
+
+ /* Detect if there are more pages (heuristic: count products shown vs. limit) */
+ var productsShown = boostGrid.querySelectorAll('[data-product-id], .boost-sd__product-item').length;
+ var pageLimit = 24; /* Default; matches Boost default */
+ hasMore = (productsShown >= pageLimit);
+
+ /* Create IntersectionObserver for infinite scroll */
+ var scrollObserver = new IntersectionObserver(function(entries) {
+ for (var i = 0; i < entries.length; i++) {
+ if (entries[i].isIntersecting && hasMore && !isLoading) {
+ loadNextPage();
+ }
+ }
+ }, { rootMargin: '600px 0px' });
+
+ scrollObserver.observe(sentinel);
+
+ function loadNextPage() {
+ if (isLoading || !hasMore) return;
+ isLoading = true;
+ sentinel.style.display = 'block';
+
+ var nextPage = currentPage + 1;
var url = new URL(window.location.href);
- url.searchParams.set('limit', this.value);
- url.searchParams.delete('page');
- window.location.href = url.toString();
- });
- perpageInline.appendChild(perpageLbl);
- perpageInline.appendChild(perpageSelect);
- sliderWrap.appendChild(perpageInline);
+ url.searchParams.set('page', nextPage);
+ url.searchParams.delete('limit'); /* Let Boost handle default limit */
+
+ fetch(url.toString(), { headers: { 'X-Requested-With': 'XMLHttpRequest' } })
+ .then(function(res) { return res.text(); })
+ .then(function(html) {
+ var parser = new DOMParser();
+ var doc = parser.parseFromString(html, 'text/html');
+ var nextGrid = doc.querySelector('.boost-sd__product-list');
+
+ if (!nextGrid) {
+ hasMore = false;
+ document.getElementById('dw-loading-status').textContent = 'No more products';
+ sentinel.style.color = '#ccc';
+ return;
+ }
+
+ /* Extract product items from next page */
+ var newItems = nextGrid.querySelectorAll('[data-product-id], .boost-sd__product-item');
+
+ if (newItems.length === 0) {
+ hasMore = false;
+ document.getElementById('dw-loading-status').textContent = 'All products loaded';
+ sentinel.style.color = '#ccc';
+ return;
+ }
+
+ /* Append new items to current grid */
+ newItems.forEach(function(item) {
+ var clone = item.cloneNode(true);
+ boostGrid.appendChild(clone);
+ });
+
+ currentPage = nextPage;
+
+ /* Check if next page exists */
+ var nextPager = doc.querySelector('[data-pagination-next], a[href*="page=' + (nextPage + 1) + '"]');
+ if (!nextPager) {
+ hasMore = false;
+ document.getElementById('dw-loading-status').textContent = 'All products loaded';
+ sentinel.style.color = '#ccc';
+ } else {
+ document.getElementById('dw-loading-status').textContent = 'Loading more…';
+ }
+
+ /* Trigger Boost to re-render/hydrate new items if needed */
+ if (window.BoostSDInstances && window.BoostSDInstances.length > 0) {
+ window.BoostSDInstances[0].viewModel.processTemplate(boostGrid.innerHTML);
+ }
+
+ isLoading = false;
+ })
+ .catch(function(err) {
+ console.error('Infinite scroll load failed:', err);
+ document.getElementById('dw-loading-status').textContent = 'Error loading more products';
+ sentinel.style.color = '#d84315';
+ isLoading = false;
+ });
+ }
/* Insert grid slider into Boost toolbar */
var toolbarRow = toolbar.parentElement;
← 3d582696 audit: DW microsites infinite scroll status — 3 converted, 3
·
back to Designer Wallcoverings
·
stage: infinite scroll collection.liquid for dev theme push 8ac53559 →