Bläddra i källkod

Add masonry layout functionality

Svilen Markov 8 månader sedan
förälder
incheckning
ab0b11cc92

+ 52 - 0
internal/assets/static/js/masonry.js

@@ -0,0 +1,52 @@
+import { clamp } from "./utils.js";
+
+export function setupMasonries(options = {}, selector = ".masonry") {
+    options = {
+        minColumnWidth: 300,
+        maxColumns: 6,
+         ...options
+    };
+
+    const masonryContainers = document.querySelectorAll(selector);
+
+    for (let i = 0; i < masonryContainers.length; i++) {
+        const container = masonryContainers[i];
+        const items = Array.from(container.children);
+        let previousColumnsCount = 0;
+
+        const render = function() {
+            const columnsCount = clamp(
+                Math.floor(container.offsetWidth / options.minColumnWidth),
+                1,
+                Math.min(options.maxColumns, items.length)
+            );
+
+            if (columnsCount === previousColumnsCount) {
+                return;
+            } else {
+                container.textContent = "";
+                previousColumnsCount = columnsCount;
+            }
+
+            const columnsFragment = document.createDocumentFragment();
+
+            for (let i = 0; i < columnsCount; i++) {
+                const column = document.createElement("div");
+                column.className = "masonry-column";
+                columnsFragment.append(column);
+            }
+
+            // poor man's masonry
+            // TODO: add an option that allows placing items in the
+            // shortest column instead of iterating the columns in order
+            for (let i = 0; i < items.length; i++) {
+                columnsFragment.children[i % columnsCount].appendChild(items[i]);
+            }
+
+            container.append(columnsFragment);
+        };
+
+        const observer = new ResizeObserver(() => requestAnimationFrame(render));
+        observer.observe(container);
+    }
+}

+ 4 - 0
internal/assets/static/js/utils.js

@@ -23,3 +23,7 @@ export function throttledDebounce(callback, maxDebounceTimes, debounceDelay) {
 export function isElementVisible(element) {
 export function isElementVisible(element) {
     return !!(element.offsetWidth || element.offsetHeight || element.getClientRects().length);
     return !!(element.offsetWidth || element.offsetHeight || element.getClientRects().length);
 }
 }
+
+export function clamp(value, min, max) {
+    return Math.min(Math.max(value, min), max);
+}

+ 11 - 0
internal/assets/static/main.css

@@ -440,6 +440,17 @@ kbd:active {
     box-shadow: 0 0 0 0 var(--color-widget-background-highlight);
     box-shadow: 0 0 0 0 var(--color-widget-background-highlight);
 }
 }
 
 
+.masonry {
+    display: flex;
+    gap: var(--widget-gap);
+}
+
+.masonry-column {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+}
+
 .popover-container, [data-popover-html] {
 .popover-container, [data-popover-html] {
     display: none;
     display: none;
 }
 }