と思いググっていたら、見つけました。
See the Pen
VueJS Category Filter CSS Animation by aoki_monpro (@suica)
on CodePen.
写真ごとにカテゴリーが設定されていて、
ARTやWORKSHOPSと書かれている箇所をクリックすると、カテゴリーごとに表示されて、
ALLを押すと全ての写真が表示されると。
動きもスムーズ。いい感じ。
という事でソースを読んでみることにしました。
目次
かっこいいフォトギャラリーのソースコードを読んでみる
ソースコードはこんな感じです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
<!DOCTYPE html> <meta charset="utf-8"> <head> <style> html,body { margin:0; font-family: 'Dawning of a New Day', cursive; } .title-container { display:flex; flex-direction:column; justify-content:center; align-items:center; } .title { font-family: 'Dawning of a New Day', cursive; font-size:30pt; font-weight:normal; } .project-title { font-size:16pt } .filter { font-family:arial; padding: 6px 6px; cursor:pointer; border-radius: 6px; transition: all 0.35s; } .filter.active { box-shadow:0px 1px 3px 0px #00000026; } .filter:hover { background:lightgray; } .projects { margin-top:25px; display:flex; flex-wrap:wrap; justify-content:center; } .projects-enter { transform: scale(0.5) translatey(-80px); opacity:0; } .projects-leave-to{ transform: translatey(30px); opacity:0; } .projects-leave-active { position: absolute; z-index:-1; } .circle { text-align:center; position:absolute; bottom:-38px; left:40px; width:100px; height:100px; border-radius:50px; /* border:1px solid black; */ display:flex; box-shadow: 0px -4px 3px 0px #494d3257; justify-content:center; align-items:center; background-color:#fff; /* box-shadow:0px -3px 3px #484848a6; */ } .project { transition: all .35s ease-in-out; margin:10px; box-shadow:0px 2px 8px lightgrey; border-radius:3px; width:180px; height:200px; display:flex; flex-direction:column; align-items:center; } .project-image-wrapper { position:relative; } .gradient-overlay { position:absolute; top:0; left:0; width:100%; height:150px; opacity:0.09; background: linear-gradient(to bottom, rgba(0,210,247,0.65) 0%,rgba(0,210,247,0.64) 1%,rgba(0,0,0,0) 100%), linear-gradient(to top, rgba(247,0,156,0.65) 0%,rgba(247,0,156,0.64) 1%,rgba(0,0,0,0) 100%); border-bottom-left-radius:10px; border-bottom-right-radius:10px; border-top-left-radius:3px; border-top-right-radius:3px; } .project-image { width:100%; height:150px; border-bottom-left-radius:5px; border-bottom-right-radius:5px; border-top-left-radius:3px; border-top-right-radius:3px; } </style> </head> <body> <div id="app"> <div class="title-container"> <div> <h3 class="title"> Our Projects </h3> </div> <div class="filters"> <span class="filter" v-bind:class="{ active: currentFilter === 'ALL' }" v-on:click="setFilter('ALL')">ALL</span> <span class="filter" v-bind:class="{ active: currentFilter === 'ALL' }" v-on:click="setFilter('ART')">ART</span> <span class="filter" v-bind:class="{ active: currentFilter === 'WORKSHOPS' }" v-on:click="setFilter('WORKSHOPS')">WORKSHOPS</span> <span class="filter" v-bind:class="{ active: currentFilter === 'FUN' }" v-on:click="setFilter('DOODLES')">DOODLES</span> </div> </div> <transition-group class="projects" name="projects" > <div class="project" v-if="currentFilter === project.category || currentFilter === 'ALL'" v-bind:key="project.title" v-for="project in projects"> <div class="project-image-wrapper"> <img class="project-image" v-bind:src="project.image"> <div class="gradient-overlay"></div> <div class="circle"> <span class="project-title">{{project.title}}</span> </div> </div> </div> </transition-group> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.22/dist/vue.js"></script> <script> new Vue({ el: '#app', data: { currentFilter: 'ALL', projects: [ {title: "Artwork", image: "https://picsum.photos/g/200?image=122", category: 'ART'}, {title: "Charcoal", image: "https://picsum.photos/g/200?image=116", category: 'ART'}, {title: "Sketching", image: "https://picsum.photos/g/200?image=121", category: 'DOODLES'}, {title: "Acrillic", image: "https://picsum.photos/g/200?image=133", category: 'WORKSHOPS'}, {title: "Pencil", image: "https://picsum.photos/g/200?image=134", category: 'DOODLES'}, {title: "Pen", image: "https://picsum.photos/g/200?image=115", category: 'ART'}, {title: "Inking", image: "https://picsum.photos/g/200", category: 'WORKSHOPS'}, ] }, methods: { setFilter: function(filter) { this.currentFilter = filter; } } }) </script> </body> </html> |
大きく分けると、
- ホームページを表示する『HTML』と、
- 見た目をいい感じにする『CSS』と、
- 最近人気急上昇中の『Vue.js(JavaScriptのフレームワーク)』
の3点セットでなりたっています。
説明の都合上、まずはscriptタグの中から見てみます。
かっこいいフォトギャラリー 『Vue.js』の読み込みなど
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.22/dist/vue.js"></script> <script> new Vue({ el: '#app', data: { currentFilter: 'ALL', projects: [ {title: "Artwork", image: "https://picsum.photos/g/200?image=122", category: 'ART'}, {title: "Charcoal", image: "https://picsum.photos/g/200?image=116", category: 'ART'}, {title: "Sketching", image: "https://picsum.photos/g/200?image=121", category: 'DOODLES'}, {title: "Acrillic", image: "https://picsum.photos/g/200?image=133", category: 'WORKSHOPS'}, {title: "Pencil", image: "https://picsum.photos/g/200?image=134", category: 'DOODLES'}, {title: "Pen", image: "https://picsum.photos/g/200?image=115", category: 'ART'}, {title: "Inking", image: "https://picsum.photos/g/200", category: 'WORKSHOPS'}, ] }, methods: { setFilter: function(filter) { this.currentFilter = filter; } } }) </script> |
まず最初に、
1 |
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.22/dist/vue.js"></script> |
という箇所で、『Vue.js』のファイルを読み込んでいます。
『Vue.js』を使うには大きく2つ方法がありますが、
- 『Vue.js』をインストールする方法
- インターネット上のファイルを読み込む方法
今回はインターネット上のファイルを読み込んでいます。
インターネット上のファイルを読み込む方法のことを、『CDN』と読んだりします。
『CDN』・・コンテンツデリバリーネットワーク
インターネット上から読み込んだ後、下記のコードが書かれています。
1 2 3 4 5 6 7 8 9 10 11 12 |
new Vue({ el: '#app', data: { currentFilter: 'ALL', projects: [] // 一旦カット }, methods: { setFilter: function(filter) { this.currentFilter = filter; } } }) |
new Vueとして、『Vue.js』を有効にして(インスタンス化)して、
el: の後に、『Vue.js』の適用範囲を決めます。
なんでもいいのですが慣例で #app とすることが多いです。
『HTML』側、bodyのすぐ下から #app で囲むことで、body全体を『Vue.js』が効くようにします。
かっこいいフォトギャラリー data編
次はdata。
文字通りデータを入れておく箱のことで、今回はdataの中に、
- currentFilter
- projects
の2つのデータの箱(変数)を用意しています。
初期値を入力できるので、
- currentFilter: ‘ALL’,
で、最初はALLが選択された状態にしています。
- projects
の中はオブジェクトの配列になっていて、
キー:値
の組み合わせでいくつかの情報が書かれています。
1 |
{title: "Artwork", image: "https://picsum.photos/g/200?image=122", category: 'ART'}, |
の場合だと、
- title が Artwork
- image が https://picsum.photos/g/200?image=122 (画像ファイル)
- category が ART
といった具合です。
今回は合計7つ、カテゴリーとしては、ARTとDOODLESとWORKSHOPSの3種類が書かれています。
配列なのでもちろんもっと増やすことも可能です。
1 2 3 4 5 6 7 8 9 |
projects: [ {title: "Artwork", image: "https://picsum.photos/g/200?image=122", category: 'ART'}, {title: "Charcoal", image: "https://picsum.photos/g/200?image=116", category: 'ART'}, {title: "Sketching", image: "https://picsum.photos/g/200?image=121", category: 'DOODLES'}, {title: "Acrillic", image: "https://picsum.photos/g/200?image=133", category: 'WORKSHOPS'}, {title: "Pencil", image: "https://picsum.photos/g/200?image=134", category: 'DOODLES'}, {title: "Pen", image: "https://picsum.photos/g/200?image=115", category: 'ART'}, {title: "Inking", image: "https://picsum.photos/g/200", category: 'WORKSHOPS'}, ] |
かっこいいフォトギャラリー methods編
次はmethods(メソッド)。
1 2 3 4 5 |
methods: { setFilter: function(filter) { this.currentFilter = filter; } } |
メソッドの名前はなんでもいいのですが、
読んで意味がわかるように『名詞』か『動詞+名詞』がいいとされています。
今回はフィルターをセットするということで 『setFilter』。
そのままですね。
メソッドの中身にまるっと関数をいれています。
引数に filter として、カテゴリーの値をとるようにして、
this.currentFilter というのは 先ほどのdata のcurrentFilter になります。
例えばカテゴリーがARTだったら、
dataのcurrentFilterも ART に変わる、という具合ですね。
かっこいいフォトギャラリー 『HTML』内に独特の書き方が満載
『Vue.js』の大きな特徴として、
『HTML』の中に、『Vue.js』独特の書き方を織り交ぜる事ができます。
『ディレクティブ』と呼ばれます。
最初はとっつきにくいかもですが、
『HTML』内のどの場所が変化するかというのがわかりやすいので、
慣れるとだいぶ読みやすいつくりになっています。
今回のフォトギャラリーの場合、合計6つの『Vue.js』独特の書き方(『ディレクティブ』)がありました。
- v-on:click ・・クリックした時に動作する(メソッドを指定する)
- v-bind:class ・・クラスを紐づける
- transition-group ・・いい感じに動かす
- v-if ・・ カテゴリーによって表示を振り分ける
- v-for ・・繰り返す
- v-bind:key ・・ v-forの時は必ずkeyをつける
- v-bind:src ・・ 画像のURL
上から順に解説してみます。
クリックしたら処理を実行する v-on:click
カテゴリーの箇所のコードは下記になっています。
1 2 3 4 5 6 |
<div class="filters"> <span class="filter" v-bind:class="{ active: currentFilter === 'ALL' }" v-on:click="setFilter('ALL')">ALL</span> <span class="filter" v-bind:class="{ active: currentFilter === 'ART' }" v-on:click="setFilter('ART')">ART</span> <span class="filter" v-bind:class="{ active: currentFilter === 'WORKSHOPS' }" v-on:click="setFilter('WORKSHOPS')">WORKSHOPS</span> <span class="filter" v-bind:class="{ active: currentFilter === 'FUN' }" v-on:click="setFilter('DOODLES')">DOODLES</span> </div> |
1 |
v-on:click="setFilter('ALL')" |
という箇所で、クリックした時にsetFilterメソッド(引数はALL)が実行されます。
(data.currentFilter の中身がALLになります。)
1 |
v-on:click="setFilter('ART')" |
をクリックしたら、 setFilterメソッド(引数はART)が実行ですね。
ちなみに、v-on:clickは @click と短く書くこともできます。
条件指定でクラスをつける v-bind:class
v-bindはちょっととっつきにくいかもですが、bindは英語で結びつけるという意味で、『CSS』のクラスとdataの値を結びつける事ができます。
1 |
v-bind:class="{ active: currentFilter === 'ALL' }" |
の書き方だと、
currentFilter が ALL なら class を active にする、という意味になります。
『CSS』側で、 .active のスタイルを書いています。
1 2 3 |
.filter.active { box-shadow:0px 1px 3px 0px #00000026; } |
box-shadowなのでうっすら影をつけるイメージでしょうか。
よくよくみると、確かにうっすら影が見えます。
いい感じに動かす transition-group
カテゴリーボタンを押した時に、
にゅ〜っと動いたり、
ふわっと消えたり、
なんというかこういい感じに動いているんですが、(語彙力・・)
それを実現する方法が、『transition-group』というディレクティブになります。
『transition-group』で囲った箇所がいい感じに動く対象の範囲になります。
先ほど v-bind:class で active というクラスをつけていましたが、
『transition-group』も同じように、動作によってクラスをつけたり消したりします。
『Vue.js』公式の画像を拝借すると、
- v-enter・・動作開始のタイミング(フェードインのタイミング)
- v-enter-active ・・動作中
- v-enter-to ・・動作が終わったタイミング
- v-leave・・消え始めるタイミング(フェードアウトのタイミング)
- v-leave-active ・・消えている間
- v-leave-to ・・消えたタイミング
の6つのクラスが瞬時についたり消えたりします。
今回の場合、transition-group タグで囲った中に、画像表示のコードが書かれているのですが(v-ifやv-forなど)、
これら全てに クラスがついたり消えたりします。
1 2 |
<transition-group class="projects" name="projects" > <div class="project" v-if="currentFilter === project.category || currentFilter === 'ALL'" v-bind:key="project.title" v-for="project in projects"> |
実際にクラスがついたり消えたりする動画がこちら。
一瞬なので、
な状態ですが、よくみると確かにクラスがついたり消えたりしているのがわかるんじゃないかなと思います。
(グーグルクロムで右クリック->検証 を押すと確認できます。)
『CSS』側を見ると、 projects というクラス以外に、
- projects-enter
- projects-leave-to
- projects-leave-active
というクラスも用意されていることがわかります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
.projects { margin-top:25px; display:flex; flex-wrap:wrap; justify-content:center; } .projects-enter { transform: scale(0.5) translatey(-80px); opacity:0; } .projects-leave-to{ transform: translatey(30px); opacity:0; } .projects-leave-active { position: absolute; z-index:-1; } |
これらのクラスが一瞬でついたり消えたりします。
『CSS』周りの解説は長くなるので別記事にて。
条件付きで繰り返す v-if v-for v-bind:key
transition-group で囲った中はこんなコードになっています。
1 2 3 |
<div class="project" v-if="currentFilter === project.category || currentFilter === 'ALL'" v-bind:key="project.title" v-for="project in projects"> 〜省略〜 </div> |
v-forは繰り返す構文で、下記のように書きます。
1 |
v-for="project in projects" |
v-for = 単数形 in 複数形
単数の箇所は実際はなんでもよくて、例えば
1 |
v-for = "test in projects" |
でも動くんですが、見た目がわかりづらいので、
v-for = 単数形 in 複数形
で書くのが慣例になっています。
また、
v-forで繰り返す場合は、v-bind:keyが必ず必要になります。(それぞれを識別できるようにするため)。
今回は dataの、projectの中の、title をキーにしています。
1 |
v-bind:key="project.title" |
ですね。
『v-if』は文字通り条件指定で、
1 |
v-if="currentFilter === project.category |
もし、 currentFilter が、 project.categoryだったら・・ (project.categoryというのはdataの中の、projectの中のcategory)、
- ARTならARTを表示して、
- DOODLEならDOODLEを、
- WORKSHOPS なら WORKSHOPSを表示、という意味になります。
1 |
v-if="currentFilter === project.category || currentFilter === 'ALL'" |
OR演算子(||) がついていて、 ALL だったら、という条件もあるので、
と思い v-ifを消してみたら見事に動かなくなったので、動くきっかけとしてv-ifは必要のようです。
最後に、 v-bind:src=”project.image” は見たまま、画像のアドレスを projectからもってきてます。
かっこいいフォトギャラリー のソースコードを読んでみて
なによりびっくりしたのは、
『transition-group』で囲うだけで、6つのクラスが瞬時についたり消えたりするとこですね。
つくクラス名も法則が決まっているので、
慣れるとかなりかっこいいフォトギャラリーがさくっとつくれるようになりそうな。
今回は解説していませんが、『CSS』の『Flexbox』という技術も使っているのでスマホでもばっちり表示されますし。
参考ページ Using Vuejs transitions to filter the projects by category
『Vue/Vuex/Nuxt』ではこんな記事も読まれています。
1. 【Vue.js】と【Firebase】で作るミニWebサービス を試してみた〜Googleログインまで〜2. 【Nuxt】入門 Vuexの状態管理を【図解】してみた【初心者向け】
3. 【Laravel5.5】Webアプリケーションを作るためのゆるめの環境構築編【Node.js】【npm】【Vue.js】【初心者向け】
4. 【Laravel】と【Vue.js】のサンプル動画を見ながらさらりと解説してみる
5. 【Vue.js】【SPA】の作り方をわかりやすくまとめてみた【初心者向け】
6. 【Vue.js】かっこいいフォトギャラリーを発見したのでソースを読んでみた。カテゴリーで選べてなめらかに動くんです
アオキのツイッターアカウント。
丁度Vueでのこのようなフィルター機能を探していました!
そこで質問なのですが、1つの作品に複数のカテゴリをつけたい場合(例えば、Artworkに”ART”と”WORKSHOP”)、どのようにcategoryを設定すれば良いのでしょうか?
また、それぞれに別ページのリンクをつけたいのですが(“/Artwork”の様に)、どの様にできますでしょうか?
Nuxtでやっているので、などとしてみましたが、上手くいきませんでした。。。
もしご回答いただけますと幸いです!
複数カテゴリ 配列かませばいけるだろうとしばらくググってみたのですが、確かにうまく動かないですね、わかり次第追記したいと思います。
リンクは category/:id などのようにカテゴリごとにページ作ってその下に番号ふる形でいいのではと思います。
vue-routerの動的ルートマッチングでいけそうな。
https://router.vuejs.org/ja/guide/essentials/dynamic-matching.html
v-if文とv-for文は推奨されないのでそのせいかもしれません。
とりあえずこんな感じにしたら動きました。
:
projects: [
{title: “Artwork”, image: “https://picsum.photos/g/200?image=122”, category: [‘ART’,’DOODLES’]},
{title: “Charcoal”, image: “https://picsum.photos/g/200?image=116”, category: ‘ART’},
{title: “Sketching”, image: “https://picsum.photos/g/200?image=121”, category: ‘DOODLES’},
{title: “Acrillic”, image: “https://picsum.photos/g/200?image=133”, category: [‘ART’,’WORKSHOPS’]},
:
Masaさん、検証ありがとうございます!
時間作って確かめてみます^^