javascriptlaravelvue.jsmasonry

Masonry.js not applaying layout


I am using Laravel, VueJS and Masonry.js library to create a dynamic gallery and i'm in front of a weird problem.

I have this in my VueJS template:

<template lang="html">
  <div id="uploads-grid">
    <div class="grid" ref="grid">

      <!-- GRID START -->

      <div class="grid-item white-outline" v-for="(bg, index) in bgs" :style="'width:' + wd + 'px;height:' + hg[index] + 'px;'">
        <img :src="'/storage/users/1/photos/1/' + bg.photo" :width="wd" :height="hg[index]">
      </div>

      <!-- GRID END -->

    </div>
  </div>
</template>

And this is how i get my images:

<script>

import Masonry from 'masonry-layout';

export default {
  data() {
    return {
      bgs: [],
      wd: '300',
      hg: []
    }
  },
  methods: {
    getPhotos() {
      var url = '/photos/get'
      axios.get(url).then(r => {
        this.bgs = r.data
        for (var i = 0; i < r.data.length; i++) {
          var factor = r.data[i].width / 300
          var resizeHeight = Math.floor(r.data[i].height / factor)
          this.hg.push(resizeHeight)
        }
      });
    }
  },
  mounted() {
    let $masonry = new Masonry(this.$refs.grid, {
      itemSelector: '.grid-item',
      columnWidth: 300,
      isFitWidth: true
    });
  },
  created() {
    this.getPhotos()
  }
}
</script>

The problem is that every image appears bellow the last one.

Either way, this other piece of code works just fine:

<template lang="html">
  <div id="uploads-grid">
    <div class="grid" ref="grid">

      <!-- GRID START -->

      <div class="grid-item white-outline" v-for="(bg, index) in bgs" :style="'width:' + wd[index] + 'px;height:' + hg[index] + 'px;'">
        <img :src="bg">
      </div>

      <!-- GRID END -->

    </div>
  </div>
</template>

<script>

import Masonry from 'masonry-layout';

export default {
  data() {
    return {
      bgs: [],
      wd: [],
      hg: []
    }
  },
  methods: {
    rndBg() {
      for (var i = 0; i < 20; i++) {
        var wd = 300
        var hg = Math.floor(Math.random() * 350) + 150
        var bgsrc = 'https://placehold.it/' + wd + 'x' + hg
        this.bgs.push(bgsrc)
        this.wd.push(wd)
        this.hg.push(hg)
      }
    }
  },
  mounted() {
    let $masonry = new Masonry(this.$refs.grid, {
      itemSelector: '.grid-item',
      columnWidth: 300,
      isFitWidth: true
    });
  },
  created() {
    this.rndBg()
  }
}
</script>

The problem is that i'm using placeholdit dummy images and not the ones i want so... I does not works for me. I'm using the same logic but... yes, i can't get it to work.


Solution

  • Main point: You gotta trigger Masonry again after adding new image.

    Masonry works by setting fixed position, width, etc... to the elements. So everytime something is added/removed, you should trigger Masonry to recalculate and set again the fixed position, width, etc...

    e.g. https://codesandbox.io/s/4xw830q1r4 components/Hello.vue

    <template>
        <div class="hello">
            <button @click="addImg"> add img </button>
            <br/><br/>
            <div class="grid">
                <div class="grid-item"  v-for="img in imgs">
                    <img :src="img">
                </div>
            </div>
        </div>
    </template>
    
    <script>
        import Masonry from 'masonry-layout';
    
        export default {
      name: 'hello',
      data () {
        return {
                imgs:[
                    `https://unsplash.it/200/300/?random=${Math.random()}`,
                    `https://unsplash.it/200/100/?random=${Math.random()}`,
                    `https://unsplash.it/200/400/?random=${Math.random()}`,
                    `https://unsplash.it/200/250/?random=${Math.random()}`
                ]
        }
      },
        mounted() {
            this.triggerMasonry();
        },
        methods:{
            triggerMasonry() {
                console.log('triggerMasonry');
                var grid = document.querySelector('.grid');
                var msnry = new Masonry( grid, {
                    itemSelector: '.grid-item',
                    columnWidth: 200
                });
            },
            addImg(){
                this.imgs.push(`https://unsplash.it/200/300/?random=${Math.random()}`);
                this.$nextTick(()=>{
                    this.triggerMasonry();
                });
            }
        }
    }
    
    </script>
    
    <!-- Add "scoped" attribute to limit CSS to this component only -->
    <style scoped lang="scss">
    .grid {
        width: 100%;
        img {
            width: 100%;
        }
    }
    </style>
    

    triggerMasonry on mounted, and also triggerMasonry after a new image is added.

    You will also need to use nextTick to triggerMasonry only after the image has been added to DOM.