[object Object]

← 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

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 →