忘れないように記録しとこ

カバの樹

Vue.jsとjQueryでフィルター付きのMasonryレイアウトを実装する

はじめに

高さ可変のMasonryを作る事になりました。
加えてカテゴリーでフィルターできるようにして欲しいとのことで、目ぼしいMasonryライブラリが見つからず、やむを得ず一部自作することにしました。

フィルター付きのリスト部分をVue.jsで作り、MasonryをjQueryに担当させることにしました。
全部Vue.jsにしたかったのですが、高さが可変という条件があるので、Vue.jsのみで対応ができませんでした。
Vue.jsは事前に高さを取得することができませんからね。

そこでjQueryを使って、Vue.jsのマウント後にMasonryレイアウトすることにしました。

 

【動画サイズ:1.8MB】

 

環境

この記事は、以下の管理人の検証環境にて記事にしています。

vue.js 2.6.10
jQuery 1.11.0
waterfall-ligh 1.3.1

 

導入

管理人が行った、動作確認サンプルを実装するために、以下の手順でソースコードを導入していきます。
このサンプルでは、フィルター付きのMasonryレイアウトを実装します。

 

step.1 ライブラリの呼び出し

今回はサンプルのデザイン用にBootstrapを使用します。
Masonryレイアウトは、waterfall-light というjQueryのライブラリを使用します。

<!-- Bootstrap -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
<!-- Vue.js-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<!-- jQuery -->
<script src="https://code.jquery.com/jquery-1.11.0.min.js"></script>
<script src="https://code.jquery.com/jquery-migrate-1.2.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/waterfall-light@1.3.1/waterfall-light.min.js"></script>

 

step.2 JavaScript

mountedupdated時にMasonryレイアウトを行わせます。

mountedは、マウントしてすぐのタイミングでMasonryレイアウトにします。
updatedは、リストの更新をした時に、再度Masonryレイアウトさせます。

new Vue({
  el: '#app',
  data: {
    isShow: false,
    checked: [],
    category: [
      {no:1, title:'category:1'},
      {no:2, title:'category:2'},
      {no:3, title:'category:3'},
      {no:4, title:'category:4'},
      {no:5, title:'category:5'},
    ],
    items: [
      {id:1, category:1, text:'Some quick example text to bue bulk of the cards content.'},
      {id:2, category:5, text:'Some quick example text to build on the card title and make up tntent.'},
      {id:3, category:2, text:'Some quick example text to build on the card title and mak title and make up title and make up title and make up e up the bulk of the cards content.'},
      {id:4, category:4, text:'Some quick example text to build on the card title and makthe cards content.'},
      {id:5, category:2, text:'Some quick example text ulk of the cards content.'},
      {id:6, category:3, text:'Some quick example text to build on the card title and make up the bulk of the c title and make up title and make up title and make up ards content.'},
      {id:7, category:4, text:'Some quick example text to build on the card title and make u title and make up title and make up p the bulk of the cards content.'},
      {id:8, category:3, text:'Some quick example text to build on the card title and make up the bulk of the cards content.'},
      {id:9, category:1, text:'Some quick example text to build on the card title and make up the bulk of the cards content.'},
      {id:10, category:4, text:'Some quick example text to build on the card title and make up the bulk of th title and make up e cards content.'},
      {id:11, category:3, text:'Some quick example text to build on the card title and make up the bulk of the cards content.'},
      {id:12, category:2, text:'Some quick example text to build on the card title and make up th title and make up title and make up title and make up e bulk of the cards content.'},
    ]
  },
  computed: {
    getItems: function(){
      let self = this;
      return this.items.filter(function(item){
        return (self.checked.length === 0 || self.checked.indexOf(item.category) !== -1);
      });
    }
  },
  mounted:function() { 
    let self = this;
    $('#box').waterfall({
      gap: 10,
      gridWidth: [0,400,600,800,1200],
      refresh: 0
   });
   setTimeout(function(){
     self.isShow = true;
   },0);
  },
  updated:function(){
    $('#box').waterfall({
      gap: 10,
      gridWidth: [0,400,600,800,1200],
      refresh: 500
    });
  }
});

 

step.3 HTML

BootstrapベースのClassを設定していきます。
v-show="isShow" を設定して、jQueryのMasonryが起動するまでの、一瞬だけ映るぐちゃぐちゃなレイアウトを隠しておきます。

<main id="app" class="flex-shrink-0">
  <div class="container">
    <div class="row mt-4 mb-4 justify-content-lg-center bg-light p-2 rounded">
      <div class="text-center p-4">
        <div class="form-check form-check-inline" v-for="item in category">
          <input class="form-check-input" type="checkbox" :value="item.no" v-model="checked" >
          <label class="form-check-label" for="flexCheckDefault"> {{item.title}}</label>
        </div>
      </div>
    </div>
    <div id="box" v-show="isShow">
      <div v-for="(item, index) in getItems" class="card" ref="waterfall" style="width: 180px;">
        <svg class="bd-placeholder-img card-img-top" width="100%" height="180" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#868e96"></rect><text x="50%" y="50%" fill="#dee2e6" dy=".3em">Image cap {{item.id}}</text></svg>
        <div class="card-body">
          <h5 class="card-title">Card title {{item.id}}</h5>
          <p class="card-text">category:{{item.category}}</p>
          <p class="card-text">{{item.text}}</p>
        </div>
      </div>
    </div>
  </div>
</main>

 

デモ

 

  • B!