Search code examples
javascriptcssgrid-layoutcss-grid

Masonry gallery with grid layout


I have been searching for a while a Masonry gallery with grid layout, I didn't found it so I decided to do it for myself. I use a customElement with grid layout but I get blocked when I assign dynamically the grid rows.
I would like your feedback and help to improve it.

Some Error that I have detected are:

  • Need to run 2 times to works
  • Blank spaces when image/container height is not multiple of 100

HTML

<masonry-gallery></masonry-gallery>

JS

 class MasonryGallery extends HTMLElement {

    items = [
    { image:'https://unsplash.it/200/100/' },
    { image:'https://unsplash.it/200/200/' },
    { image:'https://unsplash.it/200/300/' },
    { image:'https://unsplash.it/200/400/' },
    { image:'https://unsplash.it/200/300/' },
    { image:'https://unsplash.it/200/200/' },
    { image:'https://unsplash.it/200/100/' },
    { image:'https://unsplash.it/200/300/' },
    { image:'https://unsplash.it/200/700/' },
    { image:'https://unsplash.it/200/300/' },
    { image:'https://unsplash.it/200/200/' },
    { image:'https://unsplash.it/200/600/' },
    { image:'https://unsplash.it/200/100/' }
  ]

  constructor() {
    super()
    this.attachShadow({ mode: 'open'})
    this.create()
    this.append()
  }

  create() {
    this.style.display = 'grid'
    this.style.gridTemplateColumns = 'repeat(auto-fill, 200px)'
    this.style.gridTemplateRows = 'repeat(auto-fill, 1fr)'
    this.style.gridGap = '1rem'
    this.style.gridAutoFlow = 'row dense'
  }

  append() {

    this.items.map(item => {

        const div = document.createElement('DIV');
      const image = document.createElement('IMG')

      image.src = item.image
      div.appendChild(image)

      this.shadowRoot.appendChild(div)

      div.style.gridRow = 'auto / span ' + [...String(image.height)][0]
    })

  }

}

customElements.define('masonry-gallery', MasonryGallery)

FIDDLE https://jsfiddle.net/znhybgb6/6/


Solution

  • Your "bugs" have the following reasons:

    1. You try to calculate the height of the image just after it is attached to the component, but its height is unknown at that point, it gets known only after the image has loaded.
    2. You have 1rem (16px) gaps between grid rows, so each 100px-tall image adds 116px to the grid height.

    This behavior can be fixed, e.g., by the following edit of your append method:

    append() {
    
        let gap = parseInt(getComputedStyle(this).gridRowGap)
    
        this.items.map(item => {
    
            const div = document.createElement('DIV');
          const image = document.createElement('IMG')
          image.style.display = 'block';
    
          image.src = item.image
          div.appendChild(image)
          image.onload = function() {
              this.parentNode.style.gridRow = 'auto / span ' + ((this.height + gap)/(100 + gap));
          }
    
          this.shadowRoot.appendChild(div)
    
        })
    
      }
    

    and replacing gridRows with gridAutoRows = '100px' for making the vertical rhythm uniform, and adjusting the heights of the images accordingly.

    You can see the result in the edited Fiddle.