このようなgoogle imagesや500pxのように画像をレンガ状に並べるレイアウトをvue.jsで実装していきます。
レンガ状のレイアウトを brick layout というらしいです。これとは別にPinterestのようなレイアウトは water fall と呼ばれています。
こちらのissuesを参考にして作りました。とっても素晴らしい内容でした。これを見つけるのが結構大変でした。なかなかいいサンプルがなかったので大変助かりました。
こちらでサンプルを見ることができます。
今回はこちらをvue.jsで実装したいと思います。
実装
遅延読み込み
画像を多く使用するアプリの場合、UXを向上させるためにlazy-loadを用いて遅延読み込みを行う場合が多いと思うので今回は画像遅延読み込みライブラリのvue-lazyloadを使用します。
こちらを参考にさせていただきました。
インストール
npm install vue-lazyload -D
読み込み
import VueLazyload from 'vue-lazyload';
Vue.use(VueLazyload, {
preLoad: 1.3, // 事前ロードする高さの割合指定
error: 'images/no-image.jpg', // エラー時に表示する画像指定
loading: 'images/loading.gif', // ロード中に表示する画像指定
attempt: 1 // ロード失敗した時のリトライの上限指定
});
brick layout
brick layout本体を実装していきます。
コンポーネントを分けています。
BrickLayoutSampleComponent.vue
<template>
<div class="images-brick-layout">
<brick-image-sample-component :image_url="image_url" v-for="(image_url,index) in images" v-if="image_url" :key="index"></brick-image-sample-component>
</div>
</template>
<script>
import BrickImageSampleComponent from './BrickImageSampleComponent.vue'
export default {
components:{
BrickImageSampleComponent
},
data: () => ({
images: [
// 画像のurl1
'https://d1f5hsy4d47upe.cloudfront.net/5a/5a877738eacec15c92ad9fd53d331474_t.jpeg',
'https://d1f5hsy4d47upe.cloudfront.net/55/552d329bfefdd3b6be0394e231ab01b8_t.jpeg',
'https://pics.prcm.jp/e0de49487bc60/42563491/jpeg/42563491_206x291.jpeg',
'https://d1f5hsy4d47upe.cloudfront.net/b9/b99ccc8633ebf2fb1037f44e4133b0f2_t.jpeg',
'https://pics.prcm.jp/e0de49487bc60/42563491/jpeg/42563491_206x291.jpeg',
],
}),
}
</script>
BrickImageSampleComponent.vue
<template>
<div class="images-brick-item" :style="{ width: width, flexGrow: flexGrow }" v-if="image_url">
<i :style="{ paddingBottom: paddingBottom}"></i>
<img v-lazy="image_url" alt="" class="brick-item-image">
</div>
</template>
<script>
export default {
props:{
image_url: '',
},
data: () => ({
width: '',
height: 280,
imageWidth: 200,
imageHeight: 200,
flexGrow: 0,
paddingBottom: 0,
}),
created() {
if (this.image_url) {
this.setDivWidth(this.image_url)
this.setDivFlexGrow(this.image_url)
this.setIPaddingBottom(this.image_url)
}
},
methods:{
async setDivWidth(image_url) {
const res = await this.loadImage(image_url)
this.width = (res.width*this.height/res.height) + 'px'
},
async setDivFlexGrow(image_url) {
const res = await this.loadImage(image_url)
this.flexGrow = res.width*this.height/res.height
},
async setIPaddingBottom(image_url) {
const res = await this.loadImage(image_url)
this.paddingBottom = res.height/res.width*100
},
loadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = (e) => reject(e);
img.src = src;
});
},
}
}
</script>
画像の読み込みを待つため非同期処理を行なっています。
画像の高さと幅を取得して伸び率を指定するflex-growを調整しています。
scss
.images-brick-layout{
display: flex;
flex-wrap: wrap;
&:after{
content: '';
flex-grow: 999999999;
}
.images-brick-item{
cursor: pointer;
margin: 2px;
position: relative;
}
i{
display: block;
}
img {
top: 0;
width: 100%;
vertical-align: bottom;
}
}
アイテム間の幅は、images-brick-itemのmarginで調整できます。
これで完成です。たったこれだけでできてしまうのでとても簡単です。
画像の遅延読み込みは、
<img v-lazy="image_url" alt="" class="brick-item-image">
のv-lazyで行なっています。
まとめ
レスポンシブですが、画面幅によっては最後の画像が幅いっぱいに広がらない場合があります。そこらへんはcssでうまく調整してみてください。