github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/ui/app/components/flex-masonry.js (about) 1 import Component from '@glimmer/component'; 2 import { tracked } from '@glimmer/tracking'; 3 import { run } from '@ember/runloop'; 4 import { action } from '@ember/object'; 5 import { minIndex, max } from 'd3-array'; 6 7 export default class FlexMasonry extends Component { 8 @tracked element = null; 9 10 @action 11 captureElement(element) { 12 this.element = element; 13 } 14 15 @action 16 reflow() { 17 run.next(() => { 18 // There's nothing to do if there is no element 19 if (!this.element) return; 20 21 const items = this.element.querySelectorAll(':scope > .flex-masonry-item'); 22 23 // Clear out specified order and flex-basis values in case this was once a multi-column layout 24 if (this.args.columns === 1 || !this.args.columns) { 25 for (let item of items) { 26 item.style.flexBasis = null; 27 item.style.order = null; 28 } 29 this.element.style.maxHeight = null; 30 return; 31 } 32 33 const columns = new Array(this.args.columns).fill(null).map(() => ({ 34 height: 0, 35 elements: [], 36 })); 37 38 // First pass: assign each element to a column based on the running heights of each column 39 for (let item of items) { 40 const styles = window.getComputedStyle(item); 41 const marginTop = parseFloat(styles.marginTop); 42 const marginBottom = parseFloat(styles.marginBottom); 43 const height = item.clientHeight; 44 45 // Pick the shortest column accounting for margins 46 const column = columns[minIndex(columns, c => c.height)]; 47 48 // Add the new element's height to the column height 49 column.height += marginTop + height + marginBottom; 50 column.elements.push(item); 51 } 52 53 // Second pass: assign an order to each element based on their column and position in the column 54 columns 55 .mapBy('elements') 56 .flat() 57 .forEach((dc, index) => { 58 dc.style.order = index; 59 }); 60 61 // Guarantee column wrapping as predicted (if the first item of a column is shorter than the difference 62 // beteen the height of the column and the previous column, then flexbox will naturally place the first 63 // item at the end of the previous column). 64 columns.forEach((column, index) => { 65 const nextHeight = index < columns.length - 1 ? columns[index + 1].height : 0; 66 const item = column.elements.lastObject; 67 if (item) { 68 item.style.flexBasis = item.clientHeight + Math.max(0, nextHeight - column.height) + 'px'; 69 } 70 }); 71 72 // Set the max height of the container to the height of the tallest column 73 this.element.style.maxHeight = max(columns.mapBy('height')) + 1 + 'px'; 74 }); 75 } 76 }