]> git.ardis.lu - ardislu.dev/commitdiff
Manually call setGridRowEnd on first page load
authorArdis Lu <ardisglu@gmail.com>
Mon, 9 Sep 2024 07:50:44 +0000 (00:50 -0700)
committerArdis Lu <ardisglu@gmail.com>
Mon, 9 Sep 2024 07:50:44 +0000 (00:50 -0700)
After debouncing ResizeObserver, the initial call on first page load needs to be manual otherwise there will be a visible delay.

script.js

index 024dce4d15fffcb7d3c707051dfda2ad175af9fe..c5f1e1ba9dd40538b1f5460c28fa6576956ae0b0 100644 (file)
--- a/script.js
+++ b/script.js
@@ -69,15 +69,38 @@ function debounce(fn, wait) {
   }
 }
 
-// Sets grid-row-end for each card when the homepage is resized. Required to implement masonry layout.
-const observer = new ResizeObserver(debounce(entries => {
-  for (const card of entries[0].target.children) {
+// Sets grid-row-end for each card based on card content. Required to implement masonry layout.
+function setGridRowEnd(home) {
+  const writes = [];
+  for (const card of home.children) {
     const contentHeight = Array.from(card.children).reduce((acc, cur) => acc + cur.clientHeight, 0);
     const cardHeight = contentHeight + 128; // 6 * 16px gaps + 2 * 16px padding
-    card.style.gridRowEnd = `span ${Math.ceil(cardHeight / 16)}`; // 16px grid gap
-    card.style.maxBlockSize = `${cardHeight}px`;
+
+    // IMPORTANT: do NOT set the styles inside this loop, otherwise there will be a forced CSS reflow on each loop (layout thrashing)
+    // because .clientHeight requires the elements to be positioned. Instead, gather the writes and set them all as a batch
+    // AFTER all .clientHeight values have been read.
+    writes.push({
+      card,
+      gridRowEnd: `span ${Math.ceil(cardHeight / 16)}`, // 16px grid gap
+      maxBlockSize: `${cardHeight}px`
+    });
+  }
+
+  // Set all styles as a batch, separated from the .clientHeight reads
+  for (const { card, gridRowEnd, maxBlockSize } of writes) {
+    card.style.gridRowEnd = gridRowEnd;
+    card.style.maxBlockSize = maxBlockSize;
   }
-}, 200));
+}
+
+// Recalculate grid-row-end only if inline size of the home component has changed.
+let previousInline = 0;
+const observer = new ResizeObserver(debounce(entries => {
+  if (previousInline !== entries[0].borderBoxSize[0].inlineSize) {
+    previousInline = entries[0].borderBoxSize[0].inlineSize;
+    setGridRowEnd(entries[0].target);
+  };
+}, 100));
 
 /* Constructs the <main> element used on the homepage from the CMS metadata Map object. */
 function buildHomeComponent(metadata) {
@@ -202,12 +225,15 @@ globalThis.search = search;
 
 function showSearch(query) {
   if (query === '') {
-    document.querySelector('main').replaceWith(globalThis.components.get('home'));
+    const home = globalThis.components.get('home');
+    document.querySelector('main').replaceWith(home);
+    setTimeout(() => setGridRowEnd(home), 0);
     return;
   }
 
   const filteredMetadata = search(query);
   const filteredHome = buildHomeComponent(filteredMetadata);
+  setTimeout(() => setGridRowEnd(filteredHome), 0);
 
   // Highlight instances of query in the text nodes of filteredHome
   const walker = document.createTreeWalker(filteredHome, NodeFilter.SHOW_TEXT);
@@ -289,7 +315,9 @@ async function showPage(path) {
       editUrl: 'https://docs.google.com/spreadsheets/d/1pfGF8yBu3D0GPTezygLuzu3Cif8SkjhtG98nL-czlhc/edit'
     });
 
-    document.querySelector('main').replaceWith(globalThis.components.get('home'));
+    const home = globalThis.components.get('home');
+    document.querySelector('main').replaceWith(home);
+    setTimeout(() => setGridRowEnd(home), 0); // Schedule initial grid-row-end on first page load
   }
   else if (path === '/search') {
     setPageMetadata({
This page took 0.038235 seconds and 4 git commands to generate.