Issue with Masonry.js implementation causing layout to not display correctly

Currently, I am working on a project using Laravel, VueJS, and the Masonry.js library to develop a dynamic gallery. However, I have encountered a peculiar issue.

Here is a snippet of 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>

Here is how I retrieve the 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 issue at hand is that each image appears below the previous one.

In contrast, the following code snippet functions correctly:

<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>

However, as you can see, I am using placeholder dummy images instead of the ones I intended to use. Therefore, it does not suit my requirements. Despite using the same logic, it seems that I cannot resolve this issue.

Answer №1

Main point: Remember to retrigger Masonry after inserting a new image.

Masonry functions by fixing the position, width, and other attributes of elements. Therefore, every time an element is added or removed, you should retrigger Masonry to recalculate and reset these attributes again.

For example, check out this example in 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>

Make sure to call triggerMasonry on mounted, as well as after adding a new image.

Additionally, use nextTick to ensure that triggerMasonry is applied only after the new image has been inserted into the DOM.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Are there any jQuery Context Menu plugins clever enough to handle window borders seamlessly?

After reviewing UIkit, as well as some other jQuery Context Menu plugins, I have noticed that they all tend to exhibit a similar behavior: The actual menu div renders outside the window, causing valuable content to be hidden from view. Is there a way to ...

Animating a background image to slide in from the bottom to the top using CSS transition

Here is a link to a codepen example: https://codepen.io/jon424/pen/XWzGNLe The current effect in this example involves covering an image with a white square, moving from top to bottom when the "toggle" button is clicked. I am interested in reversing this ...

Retrieving Twitter posts using the screen_name parameter in a Node.js environment

I am looking to create a website that allows users to enter the Twitter screen name of any celebrity. When the user clicks on the "show tweet" button, the latest tweet from that screen name will be displayed. I am interested in implementing this feature ...

What is the best way to store an image file using html/angularjs?

I'm facing a challenge in my app where I need to save an image on one page and then retrieve it on another. So far, I have explored three different methods but none of them seem to be working for me: First, I attempted to use 'Parse.File' w ...

Lack of code completion in Nuxt options API when using typescript

After setting up Nuxtjs with typescript, I noticed that there are no code completions in the template and script as expected based on the title. Here is the code: <script lang="ts"> import Vue from 'vue'; import { FeaturedJobs } ...

Restrict the number of dynamic form elements to a maximum of 10 entries

I am working on a feature where users can refer their friends and the data will be saved in a database. <html> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <script type='text/javascript' sr ...

Upload file via AJAX immediately after downloading it (no need for storage)

Currently, I am in the process of developing a JavaScript script to safeguard an old game development sandbox website from being discarded by its owners. In order to prevent the loss of all the games on the site, I have managed to create a script that allo ...

Mapping API for JavaScript, converting coordinates into precise locations

I have two coordinates: 54.674705589 and 25.289369548. I want to place these coordinates on a map when the button is clicked, similar to this example. However, the example provided is for addresses, not coordinates. Is it possible to modify this example t ...

The Autocomplete feature in Material UI React includes both a "Select All" and a "Select

How can I add Select All and Select None buttons to an Autocomplete component in Material UI React? The goal is for all the options to be checked when Select All is clicked, and for all options to be unchecked when Select None is clicked. <Autocomple ...

unable to make a request to the express server with axios

I am in the process of developing a chat application similar to whatsapp. One of the key features I'm working on is that when a user clicks on another person's name, their chats will be displayed. However, currently, I'm facing an issue wher ...

When invoking a function, a React Component utilizes the props from the first element instead of its own

Whenever I try to invoke a function of a component, it seems to be replacing the parameters and passing the props of the first array element instead of the selected one. To illustrate this issue, let's take a look at some code: Firstly, here is how ...

Using jQuery to loop through a collection

I have a page that displays a list of posts. When a user clicks on the show comments button for a particular post, the comments associated with that post become visible. This functionality is achieved by using this and then searching based on the click loc ...

Click the "Add" button to dynamically generate textboxes and copy the contents from each

I am working on a project where I have an Add button and 6 columns. Clicking on the Add button generates rows dynamically, which can also be deleted. My challenge is to copy the content of one textbox into another in 2 of the columns. This copying function ...

Retrieve the file name of the webpage from the URL bar

Is there a way to retrieve the page name from the address bar using jquery or javascript instead of PHP? I am working on an HTML website and would prefer not to use PHP for this specific task. For example, if the address is www.mywebsite.com/hello.htm, ho ...

Tips for utilizing Jquery to manage a dropdown designed in button form

<div class="btn-group btn-grp-uk col-xs-12 "> <button id="colorList" type="button" class="btn-phn btn btn-dropdown-white- uk dropdown-toggle col-xs-12" data-toggle="dropdown">Red </button> <ul id="colordrop" class="dr ...

ReactJS and Redux: setting input value using properties

I am utilizing a controlled text field to monitor value changes and enforce case sensitivity for the input. In order to achieve this, I need to access the value property of the component's state. The challenge arises when I try to update this field ...

Obtaining the current ID from a URL in an API using Axios with Vue.js

When the user clicks the button, I want them to be able to block another user. This means that the URL should have a dynamic value for the user's id: http://example/user/:id. This is my template: <template> <div class> <div ...

TransitionGroup with CssTransition fails to execute exit transition

After making the switch from the outdated CSSTransitionGroup to the newer react-transition-group library for CSSTransition and TransitionGroup, I encountered an interesting challenge. I've been tinkering with creating an overlay loader, aiming to add ...

Hide HTML div on click

Why does the button disappear when I click on it, but the status refreshes? Javascript $( ".refreshstatus" ).click(function(){ $( ".navplayers" ).load('stats.php'); }); CSS .refreshstatus{ font-family:'Noto Sans'; font-w ...

What could be causing Jquery to not function properly in an Angular controller?

Can someone please help me understand why my code is not working in JSFiddle? Any assistance would be appreciated! UPDATE: I have to admit, the negative feedback is disheartening. I may not know everything, but asking questions is how we learn. What' ...