<div class="masonry" style="--item-width:200px">
  <div class="box" style="--span:2">
    1. Lorem ipsum dolor sit amet consectetur adipisicing elit.
    <img src="https://picsum.photos/1200/800" alt="" />
  </div>
  <div class="box">
    2. Lorem ipsum dolor sit amet consectetur adipisicing elit. Maxime
    impedit praesentium voluptatibus? Vero, dicta dolore distinctio nulla
  </div>
  <div class="box" style="--span:2">
    3. Lorem ipsum dolor sit amet consectetur adipisicing elit. Maxime
    impedit praesentium voluptatibus? Vero, dicta dolore distinctio nulla
    amet consequatur ab, voluptatibus omnis recusandae commodi eius nisi fat
    non quos? Obcaecati?
  </div>
  <div class="box">
    4. Lorem ipsum dolor sit amet consectetur adipisicing elit.at non quos?
    Obcaecati?
  </div>
  <div class="box" >
    5. Lorem ipsum dolor sit amet consectetur adipisicing elit. Maxime
    impedit praesentium volunon quos? Obcaecati?
  </div>
  <div class="box">6. Lorem</div>
  <div class="box"  >
    7. Loa amet consequatur ab, voluptatibus omnis recusandae commodi eius
    nisi fat non quos? Obcaecati?
  </div>
  <div class="box">
    8. Lorem ipsum dolor sit amet consectetur adipisicing us omnis
    recusandae commodi eius nisi fat non quos? Obcaecati?
  </div>
  <div class="box">
    9. Lorem ipsum dolor sit amet consectetur adipisicing elit. Mro, dicta
    dolore distinctio nulla amet consequatur ab, voluptatibus omnis
    recusandae commodi eius nisi fat non quos? Obcaecati?
  </div>
  <div class="box">
    10. Lorem ipsum dolor sit amet consectetur adipisicing elit. Maxime
    impedit praesentium vol quos? Obcaecati?
  </div>
  <div class="box">
    11. Lorem ipsum dolor sit amet consectetur adipisicing elit. Mta dolore
    distinctio nulla amet consequatur ab, voluptatibus omnis recusandae
    commodi eius nisi fat non quos? Obcaecati?
  </div>
  <div class="box">
    12. Lorem ipsum dolor sit amet consectetur adipisicing elit. Maxime
    impedit praesentVero, dicta dolore distinctio nulla amet consequatur ab,
    voluptatibus omnis recusandae commodi eius nisi fat non quos? Obcaecati?
  </div>
  <div class="box">
    13. Lorem ipsum dolor sit amet consectetur adipisicing elit. Maxime
    impedit praesentium omnis recusandae commodi eius nisi fat non quos?
    Obcaecati?
  </div>
  <div class="box">
    14. Lorem ipsum dolor sit amet consectetur adipisicing elit. Maxime
    impedit praesentium voero, dicta dolore distinctio nulla amet
    consequatur ab, voluptatibus omnis recusandae commodi eius nisi fat non
    quos? Obcaecati?
  </div>
  <div class="box">
    15. Lorem ipsum dolor sit amet consectetur adipisicing elit. Maxime
    impedit praesentium voluptatibus? Vero, dicta dolore distinctio nulla
    amet consequatur ab, voluptatibus omnis recusandae commodi eius nisi fat
    non quos? Obcaecati?
  </div>
  <div class="box">
    16. Lorem ipsum dolor sit amet consectetur adipisicing elit. Maxime
    impedit praesentium voluptatibus? Vero, dicta dolore distinctio nulla
    amet consequatur ab, voluptatibus omnis recusandae commodi eius nisi fat
    non quos? Obcaecati?
  </div>
  <div class="box">
    17. Lorem ipsum dolor sit amet consectetur adipisicing elit. Maxime
    impedit praesentiums omnis recusandae commodi eius nisi fat non quos?
    Obcaecati?
  </div>
  <div class="box">
    18. Lorem ipsum dolor sit amet consectetur adipisics recusandae commodi
    eius nisi fat non quos? Obcaecati?
  </div>
  <div class="box">
    19. Lorem ipsum dolor sit amet consectetur adipisicing elit. Maxime
    impedit praesentium voluptatibus? Vero, dicta dolore distinctio null
    consequatur ab, voluptatibus omnis recusandae commodi eius nisi fat non
    quos? Obcaecati?
  </div>
  <div class="box">
    20. Lorem ipsum dolor sit amet consectetur adipisicing elit. Maxime
    impedit praesentibus omnis recusandae commodi eius nisi fat non quos?
    Obcaecati?
  </div>
</div>
.masonry {
  display: grid;
  grid-template-columns: repeat(
    auto-fit,
    minmax(min(var(--item-width, 200px), 100%), 1fr)
  );
  grid-template-rows: masonry;
  gap: 1rem;
  grid-auto-flow: dense;

  > *,
  > astro-slot > * {
    align-self: start;
    grid-column-end: span var(--span, 1);
  }
}

/* Stylistic Purposes */
html {
  font-family: system-ui, sans-serif;
}

body {
  margin: 1rem;
}

.box {
  display: flex; 
  flex-flow: column;
  gap: 1rem;
  border: 2px solid black; 
  padding: 1rem;
  border-radius: 0.5rem; 
  background: white;
}

img {
  display: block; 
  max-width: 100%;
}

const masonryLayouts = document.querySelectorAll('.masonry')

masonryLayouts.forEach(async container => {
  if (isMasonrySupported(container)) return

  const colGap = parseFloat(getComputedStyle(container).columnGap)
  const items = getChildren(container)

  container.style.gridAutoRows = '0px'
  container.style.setProperty('row-gap', '1px', 'important')
  
  try {
    await Promise.all([areImagesLoaded(container), areVideosLoaded(container)])
  } catch(e) {}
  
  layout({colGap, items})
  
  
  const observer = new ResizeObserver(observerFn)
  observer.observe(container)

  function observerFn(entries) {
    for (const entry of entries) {
      layout({colGap, items})
    }
  }
})

function isMasonrySupported(container) {
  if (typeof window === 'undefined') return
  return getComputedStyle(container).gridTemplateRows === 'masonry'
}

function getChildren(container) {
  let children = container.children

  // Compensate for Astro Slots
  if (children[0]?.nodeName === 'ASTRO-SLOT') children = children[0].children
  return Array.from(children)
}

async function areImagesLoaded(container) {
  const images = Array.from(container.querySelectorAll('img'))
  const promises = images.map(img => {
    return new Promise((resolve, reject) => {
      if (img.complete) return resolve()
      img.onload = resolve
      img.onerror = reject
    })
  })
  return Promise.all(promises)
}

function areVideosLoaded(container) {
  const videos = Array.from(container.querySelectorAll('video'))
  const promises = videos.map(video => {
    return new Promise((resolve, reject) => {
      if (video.readyState === 4) return resolve() // HAVE_ENOUGH_DATA
      video.onloadedmetadata = resolve
      video.onerror = reject
    })
  })
  return Promise.all(promises)
}

async function layout({colGap, items}) {
  items.forEach(item => {
    const ib = item.getBoundingClientRect()
    item.style.gridRowEnd = `span ${Math.round(ib.height + colGap)}`
  })
}

Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.