Vuetify v-card fit like a puzzle (Masonry layout)

Viewed 3283

I have a page with 6 or more v-card.

Is there a way to fit them like a puzzle? I would like to remove white space between small v-card on first and second line.

Now is like this:

enter image description here

3 Answers

No way (Yet) to solve this by Vuetify API. Related Github Feature Req:

[Feature Request] Masonry Layout #4091

Solution by Masonry.js

Use Javascript plugin. For example masonry.js.

Codepen Demo: https://codepen.io/ezra_siton/pen/gOpZqKr

Masonry.js & vuetify grid - Code snippet

<!-- https://vuetifyjs.com/en/ -->
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@4.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css" rel="stylesheet">

<div id="app">
<v-app>
  <v-content>
    <v-container>
      <v-row class="masonry">
        <v-col class="child" cols="12" sm="6">
          <v-card class="pa-2" color="pink darken-1" dark>
            <v-card-title>Card 1</v-card-title>
            <v-card-text>
              The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections form, Rackham. One of three columns
              The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections form, Rackham.
              </v-card-text>
          </v-card>
        </v-col>
        <!-- card 2-->
        <v-col class="child" cols="12" sm="6">
          <v-card class="pa-2"  color="orange darken-3" dark>
            <v-card-title>Card 2</v-card-title>
            <v-card-text>
              One The standard chunk of Lorem Ipsum used since the 1500s is 
              </v-card-text>
          </v-card>
        </v-col>
        <!-- card 3 -->
        <v-col class="child" cols="12" sm="6">
          <v-card class="pa-2" color="#385F73" dark >
            <v-card-title>Card 3</v-card-title>
            <v-card-text>
              The chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections form, Rackham.
              </v-card-text>
          </v-card>
        </v-col>
        <!-- card 4 -->
        <v-col class="child" cols="12" sm="6">
          <v-card class="pa-2" color="blue darken-4" dark >
            <v-card-title>Card 4</v-card-title>
            <v-card-text>
              The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections form, Rackham.
              </v-card-text>
          </v-card>
        </v-col>
      </v-row>
    </v-container>
  </v-content>
</v-app>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js"></script>
<script src="https://unpkg.com/masonry-layout@4.2.2/dist/masonry.pkgd.min.js"></script>

<script>
 new Vue({
  el: '#app',
  vuetify: new Vuetify(),
  mounted: function () {
      // Code that will run only after the
      // entire view has been rendered
      var msnry = new Masonry( '.masonry', {
        // options
        itemSelector: "[class*='col-']",
      });
  }
})
</script>

How to (Less than 1 min)

Step 1/3: CDN before body

Link directly to Masonry files on unpkg.

<script src="https://unpkg.com/masonry-layout@4.2.2/dist/masonry.pkgd.min.js"></script>

-or- Install with npm: npm install masonry-layout and use import import Masonry from 'masonry-layout'

Step 2/3: HTML - Set container element

Masonry works on a container grid element with a group of child items.

Add a class (or id) to your flexbox grid (Set as container element). masonry in this example (Use any name you want).

<v-row class="masonry">
  <v-col class="child" cols="12" sm="6">
   <v-card class="pa-2" outlined >
..rest of the code

Step 3/3: Initialize with vanilla js

new Masonry( elem, options )

3.1: Use .masonry as a container element argument.

3.2: Inside options object - set itemSelector to: itemSelector: "[class*='col-']"

[class*='col-']: Wildcard selector (Select any class Contains col. For example: .col-6 -or- .col-md-2 ==> DRY // Clean solution)

I load the script inside vue mounted() lifecycle hook (After the components added to the DOM).

 new Vue({
  el: '#app',
  vuetify: new Vuetify(),
  mounted: function () {
      // Code that will run only after the
      // entire view has been rendered
      var msnry = new Masonry( '.masonry', {
        // options
        itemSelector: "[class*='col-']",
      });
  }
})

Docs:


Solution by custom CSS

One more option is to use flexbox/Grid and Custom CSS, in my opinion, this is too much code & ideas for such a simple task. Related article:

https://css-tricks.com/piecing-together-approaches-for-a-css-masonry-layout/

If you are using VueJs and Vuetify then try to use vue.js masonry. With vue-masony, all you need to do is v-row and v-col instead of div.

<v-row v-masonry transition-duration="0.3s" item-selector=".item">
    <v-col v-masonry-tile class="item" cols="12" v-for="(content_obj, index) in class_contents" key="`content${index}`" sm="3" >
            ...
    </v-col>
</v-row>

I found a simple solution (in this blog) with anything else than CSS. The same blog gives more examples of different grids and also some examples to make things prettier with JS.

The easiest way is to define a grid of columns the way you want. For example, I want on my proyect a 3 column grid about 400px each.

So we define a class masonry-with-columns, we put all the properties we want (margin, padding, column-gap, row-gap, etc)

.masonry-with-columns {
    columns: 3 400px;
    column-gap: 10px;
    max-width: 100%;
}

Each child must have 2 properties

.masonry-with-columns div {
    display: inline-block;
    width: 100%;
}
  • inline-block: prevents objects from being cut
  • width: 100%: When you do not put this property, at the time of loading it is displayed with a reduced width and then it expands, to avoid that, we must define a width

Example

<template>
    <v-container
        fill-height
        fluid
    >
        <div class="masonry-with-columns">
            <NoticeCard
                v-for="(notice, idx) in notices"
                :key="idx"
                v-bind.sync="notice"
            ></NoticeCard>
        </div>
    </v-container>
</template>
import NoticeCard from '@/components/common/Notices/NoticeCard';
export default {
    data() {
        return {
            notices: []
        }
    },
    components:{
        NoticeCard
    },
    methods: {
        getNotices() {
            axiosRequest.get('/get-cards').then((response) => {
                if ( response.status === 200 )
                    this.notices = response.data[0];
            }).catch(e => axiosResponseErrorHandler(this, e));
        },
    }
}
</script>
<style scoped>
    .masonry-with-columns {
        columns: 3 400px;
        column-gap: 10px;
        max-width: 100%;
    }
    .masonry-with-columns div {
        display: inline-block;
        width: 100%;
    }
</style>

Comments

  • Notices are filled with a server response
  • NoticeCard is your card

Results

enter image description here

enter image description here

Related