0

I am trying to use CSS column to replicate a masonry grid and it works perfectly except for the order. To make this work I want to take my array of items and re order the items so to make them read from left to right.

I know how many columns there are column count(may change from 2- to 4) and I have an array of data but I am not sure on the best way to shift this index's of each item to the correct new index example shown below. I thought I could user filter but I cant get the logic to work.

 const countArray = Array.from(Array(columnCount).keys());

   for(let i= 0; i < countArray.length; i++) {
     returnArray = returnArray.concat(
       dataList.filter((value, index,) =>{
         return (index % i === i - 1)
       })
     );
   }

DEFAULT

1 | 4

2 | 5

3 | 6

NEW ORDER

1 | 2

3 | 4

5 | 6

After attempting to fix it I get the following ordering issues on different column counts enter image description here enter image description here

enter image description here

1 Answer 1

2

I think the best way to do this is to break the data into "buckets" to order them based on the number of columns you have, then flatten those buckets back into a single-dimensional array for output/display purposes. The below solution will handle any data length and any number of columns and return a re-ordered array based on the number of columns you have. Hope this helps.

let data = [1, 2, 3, 4, 5, 6];

function dataColumnReorder (data, numColumns) {
  // generate a list of "buckets" to order the items
  let buckets = [];
  for (let i = 0; i < numColumns; i++) {
    buckets.push([]);
  }
  // fill the buckets, then flatten so all the data ends up in 1 flattened array
  let reordered = data.reduce((res, curr, index) => {
    // determine which bucket the item goes into based on index
    let bucketIndex = index % numColumns;
      res[bucketIndex].push(curr);
      return res;
  }, buckets).flat();
  
  return reordered;
}


console.log(dataColumnReorder(data, 1));
console.log(dataColumnReorder(data, 2));
console.log(dataColumnReorder(data, 3));
console.log(dataColumnReorder(data, 4));

You can do this purely with CSS using CSS Grid to create a masonry-style layout, but keep in mind, this will not always preserve the order of the items depending on the height variance. If one item spans multiple rows, it will use the next items to fill the gap in space of the empty rows on other columns, meaning it won't always read left-to-right in order. If you want them to always be in order, set a fixed height of your rows and make each item 1 row, or make each item the same height. If you want them to be a masonry-style layout, it won't always preserve order. Here's an example with minimal effort inspired by this article. (Note: to change the # of columns, adjust the grid-template-columns property in css. You can have different classes to support the different column sizes):

function resizeGridItem(item){
  grid = document.getElementsByClassName("grid")[0];
  rowHeight = parseInt(window.getComputedStyle(grid).getPropertyValue('grid-auto-rows'));
  rowGap = parseInt(window.getComputedStyle(grid).getPropertyValue('grid-row-gap'));
  rowSpan = Math.ceil((item.querySelector('.content').getBoundingClientRect().height+rowGap)/(rowHeight+rowGap));
    item.style.gridRowEnd = "span "+rowSpan;
}

function resizeAllGridItems(){
  allItems = document.getElementsByClassName("item");
  for(x=0;x<allItems.length;x++){
    resizeGridItem(allItems[x]);
  }
}

function resizeInstance(instance){
    item = instance.elements[0];
  resizeGridItem(item);
}

let data = Array.from(Array(30)).map((_, i) => i+1);
let grid$ = document.querySelector('.grid');
for (item of data) {
  let item$ = document.createElement('div');
  item$.classList.add('item');
  let content$ = document.createElement('div');
  content$.classList.add('content');
  content$.style.height = `${Math.floor(Math.random() * 100) + 160}px`;
  content$.innerText = item;
  item$.append(content$);
  grid$.append(item$);
}

resizeAllGridItems();
window.addEventListener("resize", resizeAllGridItems);
.grid {
  display: grid;
  grid-gap: 10px;
  grid-template-columns: repeat(3, minmax(250px,1fr));
  grid-auto-rows: 20px;
}

/* Non-grid specific CSS */  
body {
  margin: 10px;
    color: #374046;
    background-color: #374046;
    font-family: 'Open Sans Condensed', sans-serif;
    font-size: 1.15em;
    text-rendering: optimizeLegibility;
    -webkit-font-smoothing: antialiased;
}

.item {
  background-color: #ffffff;
}
<div class="grid">
  
</div>

Sign up to request clarification or add additional context in comments.

12 Comments

But the indexes are overlapped so I am not sure how your snippet would work to fix the problem once I concat them together.
@RossRawlins You said you have them displaying in order, don't you? If not, leave them in the buckets and display each sub-array in a column
Thanks, do you think this is scailable for a list of say 500-1000 items?
@RossRawlins Yes. Displaying that number of elements in the DOM will be more of a strain than reordering them. The time complexity of this algorithm is O(n) so it would scale linearly with the number of elements. The only place you'd slightly lose efficiency is in the creation of the sub-arrays, which takes up space in memory temporarily, but honestly I think you'd need millions of items before you'd really see a strain on system resources - depending on what your items look like. If they are very large objects, the constraints would be lower.
thanks for this I was trying to make it happen a different way and I guess I just needed a look from outside the box.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.