サイトを公開した時からずっと作成したいと思っていたのですが、ついに作りました。

まえがき


もともとはScala + PlayFrameworkでゴリゴリやろうかと思って去年少しだけ作っていたのですがJWT認証実装した後にフロントエンドで挫折したままでした。今回はある程度妥協してこのサイトに組み込むことにしました。

つくったもの


実物はこちらにあります。が、後述する通り全てのボタンを押すと現時点で30MBくらいの通信量がかかります。

妥協点


下記を妥協しました。

  • 動的に管理
  • ユーザの通信量
  • JQueryの使用

まずデータベースなどで管理することをあきらめました。そちらの方が良いと思うのですが、そうなると自作するか出来合いのものを利用するかになります。これは手間というか時間も取れないので諦めることとしました。

次にユーザの通信量ですが、こういったものは画像をそのまま表示してしまうとユーザの通信量がエライことになるので、一覧表示ではリサイズしたサムネイルを使用することで通信量を抑えるといったことを考慮しないといけません。が、これはサムネイル画像を生成したりするのが手間だったので後述する方法で妥協することとしました。

最後にJQueryに依存させるつもりはなかったのですが、サムネイルの表示とかをCSSを書かずにいい感じにやってくれそうなライブラリが非JQuery依存のものでは存在しませんでした。まあ、別にそこまでこだわって脱JQueryする必要もないですし、既にこのサイトでも多用されてるのでええかな。という判断になりました。

使用ライブラリ


Vue.jsとlightgallery.jsはCDNを使ってます。Justified-Galleryは後述する問題で一部修正したものをセルフホスティングしてます。

実装


まずざっとコードの説明をします。

HTML部ですが下記のようにしました。それぞれのカテゴリーのボタン押下時にVue.jsのswithCategoryメソッドを呼び出します。呼び出すと後述するVue.jsで画像のパスを格納したデータが更新されるのでDOMが再描画されます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<div id="vueapp">
<div class="flex-container">
<a v-on:click="swithCategory('random')">
<div class="item flex-container-menu">Random<br>ランダム</div>
</a>
...
<!-- swithCategoryメソッドで画像のリストを更新する -->
<a v-on:click="swithCategory('monochrome')">
<div class="item flex-container-menu">Monochrome<br>モノクロ</div>
</a>
</div>
<div>
<!-- 画像リストの表示 -->
<div id="gallery">
<div v-bind:data-src="photo" v-for="photo in photos">
<a v-bind:href="photo">
<img v-bind:src="photo"/>
</a>
</div>
</div>
</div>
</div>

次にJavaScript側です。基本の流れは下記のようになります。

  • justifiedGalleryのインスタンスを破棄
  • 写真のリストを生成
  • Vue.jsでDOMを再描画
  • justifiedGalleryのインスタンス生成
  • lightgallery.js再構築

あらかじめカテゴリーごとに写真のリスト(URL)の配列を作成しておきます。それらをVue.jsで切り替えて更新しています。デフォルト画面は全画像からランダムで抽出した15個の画像を表示するようにしています。残りの説明はコードに書いてるのでコード見てください。

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
//カテゴリーごとの写真のリスト
const sceneryPhotos = [
'https://yoshinorin.net/assets/photos/scenery/1.jpg',
...
'https://yoshinorin.net/assets/photos/scenery/38.jpg'
];

...

//デフォルト表示用に全カテゴリーの配列を結合したものも作っておく
const allPhotos = sceneryPhotos.concat(animalPhotos.concat...

//justifiedGalleryがJQueryに依存しているので描画完了した後に実行する
window.onload = function() {
const app = new Vue({
el: '#vueapp',
data: {
photos: []
},
mounted: function() {
//デフォルトの画像リストセット
this.photos = this.reSize(this.photoshuffle(allPhotos));
Vue.nextTick(function () {
//Vue.jsによる再描画を待つ
$("#gallery").justifiedGallery({
rowHeight: 100,
maxRowHeight: 100,
lastRow: 'justify'
});
lightGallery(document.getElementById('gallery'), {
download: false
});
});
},
methods: {
//カテゴリーごとに切り替え
swithCategory: function(type) {
this.photos = [];
//justifiedGalleryのインスタンスを破棄
$('#gallery').justifiedGallery('destroy');
switch (type) {
case "scenery":
this.photos = this.photoshuffle(sceneryPhotos);
break;
...
default:
this.photos = this.reSize(this.photoshuffle(allPhotos));
break;
}
//Vue.jsによる再描画を待つ
Vue.nextTick(function () {
$("#gallery").justifiedGallery({
rowHeight: 100,
maxRowHeight: 100,
lastRow: 'justify'
});
lightGallery(document.getElementById('gallery'), {
download: false
});
});
},
//justifiedGalleryのランダムだとlightGalleryのインデックスとずれてしまうので自前でシャッフルする
photoshuffle: function(photos) {
for(i = photos.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var tmp = photos[i];
photos[i] = photos[j];
photos[j] = tmp;
}
return photos;
},
//デフォルト表示用に15画像抽出
reSize: function(photos) {
let p = [];
for(i = 0; i < 16; i++) {
p.push(photos[i]);
}
return p;
}
}
});
}

工夫した点や詰まった点


Vue.jsによる更新 & 通信量の低減

フォトギャラリーをデータベースを使わずにカテゴリー別に楽に管理できるかということを考えたときにやはりリストから自動で描画できると良いな。と考えました。これをVue.jsでやれば良いと気づいたのは我ながらナイスだったと思います。

また、上記の通り各カテゴリーのボタンを押したタイミングでそれぞれのカテゴリーに属する画像を取りに行くので、全ボタンを押すか(ランダムボタンを押しまくって全画像が表示されるか)までは全画像は取得されません。つまり、間接的にユーザの通信量も抑えたといえなくもないです…(マジメに通信量抑えるならもっと頑張らないとダメですが…みんなYoutubeとか見てるんでしょ?ゆるしてちょ…)

再描画時にサムネイルの画像サイズが正しく計算されない問題

サムネイルの表示はjustifiedGalleryを使用しています。Vue.jsによる画像リストの更新後はこのjustifiedGalleryのインスタンスを破棄して再作成する必要があります。

justifiedGalleryのインスタンスは$('要素').justifiedGallery('destroy')で破棄できるのですが、破棄して再作成した場合に画像のサイズが正しく計算されない問題にぶつかりました。どうも完全に破棄されていない(?)ようでした。特定条件の画像は再計算しない処理が入っていたため、常に再計算するように変更しました。おそらくこれはVue.jsのようなものでDOMを更新する前提としていないようなので仕様のような気がしなくもしません。

ですのでプルリクは出さずにForkしたリポジトリにpushしておきました。修正はコミットはこちらです

インデントを変更しているので変更量が多く見えますがjustifiedGallery.jsの下記のif文を消すだけです。

1
if ($entry.data('jg.loaded') !== true && $entry.data('jg.loaded') !== 'skipped') {

ランダム

justifiedGalleryにはサムネイルをランダムに表示する機能がありますが、これとlightgallery.jsを組み合わせるとサムネイルとクリックしたときに表示される画像が異なるという問題が発生してしまったのでランダム表示の処理は自前で実装しました。ただ、この問題は前述のjustifiedGalleryのインスタンスの破棄やVue.jsのDOM更新の部分と絡んでいるのでもしかしたら今もう一度やってみるとなおっているかもしれません。

今後の改善点


今はscriptに写真のリストをベタ書きしていますが、デプロイ時にディレクトリ内の画像リストを取得して、そいつをJSONにしてデプロイと同時にJSONをアップロード、Vue.jsはそれを取得して画像リストを生成するなどすればリスト手書きの手間がなくなるので楽になると思いますね。

まとめ


画像はとりあえず直近数年のものを中心にピックアップしていますが、もっと過去のものも多めに組み込む予定です。今年の目標が一つ達成できましたね。幸先が良いです。また、これをベースとして念願のSakeギャラリーも作りたいですね。