圖片布局的最全實現方式都在這了!附源碼
- 2019 年 10 月 25 日
- 筆記
版權聲明:本文為部落客原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
本文鏈接:https://blog.csdn.net/qq_32135281/article/details/98703095
最近項目中需要處理與圖片相關的布局,不得不說圖片這玩意真想要得到完美的展示效果還真是要費些力氣。因為圖片的尺寸或者比例各不相同。所以想要不同尺寸的圖片有好的顯示效果,你就需要找到適合的方式。
而且圖片往往是不可或缺元素。畢竟一圖勝千言,有時候圖片能給帶來非常好的效果。
比如我們每天都會使用的表情包,它往往能夠表達出我們無法用文字描述的資訊,還比如我們經常在公眾號里看到的漫畫雖然短短几個字,但是卻能夠讓我們看的不亦樂乎。
當然如果我們做圖片網站的,那圖片的處理就是繞不開的話題了。因對圖片的處理經驗不多,所以就邊學邊用。今天就把最近學習與圖片相關的知識整理出來。
比如單個圖片如何更好的展示,瀑布流布局都有哪些你不知道的實現方式。
接下來我們就直接進入正題,我們先從單張圖片的展示說起。
設置寬或高 100%
因為圖片其本身的獨特性:
- 不設置寬高的情況下會按原有的尺寸顯示在網頁中。即有多大,顯示多大。
- 在非等比縮放的情況下會被拉伸變形。
- 設置寬度或者高度時,會保持原寬高比進行縮放。

<style lang="scss" scoped> .box1 { width: 150px; height: 150px; border: 2px solid red; } .box2 { width: 150px; height: 100px; border: 2px solid red; img { display: block; width: 100%; height: 100%; } } </style>
顯然當我們採用 1、2 種方式的時候破壞性很強,無法應用到實際的項目中去。
所以往往我們會在項目中使用第 3 種方式,即設置高度或者寬度。它會保持原有比例進行縮放。

<style lang="scss" scoped> .box { width: 150px; height: 150px; border: 2px solid red; } .img1 { width: 100%; } .img2 { height: 100%; } </style>
但是問題又來了,圖片要麼超出容器,要麼就會留有空白,除非容器的寬高比恰好等於圖片的寬高比時,才會完全貼合。
對於超出容器的圖片我們可以使用 overflow: hidden
把超出部分隱藏。圖片得到了好的展示效果。但相應的我們也損失了圖片的一部分可視區域。
所以這個時候就需要你根據需求進行取捨了,到底是選擇隱藏圖片的一部分,還是留有空白。有的小夥伴會說,我們產品說了,圖片變形沒問題,你就給我充滿容器就行了。好吧….
即使如此,你也要把這篇文章好好讀一讀,因為需求是千變萬化的,保不齊哪一天就需要了。
又有小夥伴說,這 2 種都不符合我們的產品需求怎麼辦,還有其他的方式嗎?答案是必須的,一起來看。
object-fit
CSS3 的 object-fit 屬性是用來指定「可替換元素」的內容是如何適應到容器中的。它的值有 5 種。分別為:fill | contain | cover | none | scale-down
。先看下效果在來一一解釋它們到底都是什麼意思。

<template> <div class="box"> <img src="https://picsum.photos/id/1027/200/300"/> </div> </template> <style lang="scss" scoped> .box { width: 150px; height: 150px; border: 2px solid red; img { width: 100%; height: 100%; object-fit: contain; } } </style>
看到上面的顯示效果,理解起來並不難了。
- fill:會充滿整個容器,不考慮寬高比,所以會被拉伸變形。
- contain:會縮放到容器內,保持寬高比。
- cover:會保持比例進行縮放,內容的尺寸一定會大於等於容器,然後進行裁剪。
- none:保持圖片的原始尺寸。
而 scale-down
有兩種表現方式所以我們單獨來看。

- scale-down:會在 none 或 contain 中選擇一個,原則是:當容器小時,它的表現和 contain 一樣;當圖片小時,它的表現和 none 一樣。即誰小選擇誰。
到這裡不知道有沒有小夥伴和我一樣,在看到圖片的不同表現時,我特意去瀏覽器查看了下 <img>
的真實尺寸,發現依然是 width: 100%;height: 100%;
是充滿整個容器的。
但為什麼內容顯示卻有不同的效果呢,這讓我產生了疑惑。本著發現探索的精神,就去尋找答案了。
W3c 是這麼描述的:<img>
標籤創建的是被引用影像的佔位空間。
而張鑫旭大大在半深入理解CSS3 object-position/object-fit屬性一文中也指出:
<img>
元素其實與內容是相互獨立的。<img>
相當於一個外殼包裹著內容。你控制的只是<img>
元素的尺寸。而內容的尺寸則由 object-fit
屬性控制。
綜上索述,<img>
是一個空間佔位符,並不控制內容。原來如此。感覺自己又進步了。每一次的探索,都會發現新的東西,這種感覺很奇妙。特別是還把它整理出文章,提供大家學習,感覺牛逼哄哄帶閃電。
知道了這些之後我們操控圖片時更加的得心應手一些。那會了這些就結束了嗎?不不不,這才是剛剛開始,精彩的還在後面。後面的布局才更加精彩。
多圖片的布局
上面一直在說的都單張圖片的顯示。只要我們把圖片用合適的方式放進容器即可。如果是圖片列表呢?或者專門展示圖片的網站會有大量的圖片而且尺寸和比例千奇百怪,各不相同。
假設要想實現一個圖片畫廊效果,首先我們給圖片一個 float: left
,但是由於圖片的尺寸不一樣,導致每個圖片的高度不同,下一行的圖片就會卡住,導致布局錯亂。

此時你不得不給容器設置高度,讓圖片能夠底部對齊,但在文章一開始我們也提到了,這時候圖片要麼超出容器的高度,要麼留有空白。
那如果使用 object-fit
屬性按照業務需求去控制內容,貌似可以完成任務。

我們把值設為 contain,布局是沒有問題了,但是其實很不美觀。如果設為 cover,如果圖片過大很多內容都會丟失看不到。怎麼辦?有什麼解決辦法?這時候就是瀑布流布局的優勢了。
瀑布流布局即不會出現錯亂現象,而且會最大限度顯示圖片的內容。所以是眾多圖片網站選擇的布局方式。
而瀑布流布局目前有兩種形式:一是等寬型,二是等高型。我們先來說說等寬型。
等寬瀑布流

看到上面你實現的思路是什麼?可以思考幾秒,接下來一起來看這些實現方式中有沒有和你一思路一樣的。
思路1. JS 計算列數
關鍵思路:
- 首先設置列寬度,然後計算能夠展示的列數。
- 向每一列中添加圖片。
關鍵程式碼:
<script> export default { methods: { //計算圖片列數 getColNumbers() { let clientWidth = this.$refs.waterfall.clientWidth this.colNumbers = Math.floor(clientWidth / this.colWidth) }, //讀取圖片 loadImage() { this.getColNumbers() for (let i = 0; i < 17; i++) { let colIndex = i % this.colNumbers let url = require(`@/assets/images/${i}.jpg`) if (this.imgList[colIndex]) { this.imgList[colIndex].push(url) } else { this.$set(this.imgList, colIndex, [url]) } } }, } } </script>
優勢:思路清晰簡單,不需要做過多的計算,只要計算需要顯示的列數然後添加圖片即可。
劣勢:每列的末尾可能不夠友好,可能出現有些列會很長,有些又會很短。
思路2. 利用絕對定位
關鍵思路:
- 首先設置列寬度,然後計算能夠展示的列數。
- 把圖片設置為絕對定位,然後計算出每個圖片的top,left值。
- 先把第一行圖片排好,top 為 0,left 為 列的索引*列寬。
- 從第二行開始,每張圖片都放到最短的一列下面。然後增加此列高度,此時列的高度發生變化,下張圖片又會尋找其他最短的列。以此持續計算下去。
關鍵程式碼:
<script> export default { methods: { //計算圖片列數 getColNumbers() { let clientWidth = this.$refs.waterfall.clientWidth this.colNumbers = Math.floor(clientWidth / this.colWidth) }, //讀取圖片 loadImage() { this.getColNumbers() for (let i = 0; i < 17; i++) { let image = new Image() let url = require(`@/assets/images/${i}.jpg`) image.src = url image.onload = () => { this.render({ index: i, url: url, ratio: image.width / image.height }) } } }, render(imgInfo) { let colIndex = imgInfo.index % this.colNumbers imgInfo.left = colIndex * this.colWidth //首行 top為 0,記錄每列的高度 if (imgInfo.index < this.colNumbers) { imgInfo.top = 0 this.colHeight[colIndex] = this.colWidth / imgInfo.ratio } else { //獲取高度的最小值 let minHeight = Math.min.apply(null, this.colHeight) let minIndex = this.colHeight.indexOf(minHeight) //此圖片的 top 為上面圖片的高度,left 相等 imgInfo.top = minHeight imgInfo.left = minIndex * this.colWidth //把高度加上去 this.colHeight[minIndex] += this.colWidth / imgInfo.ratio } this.imgList.push(imgInfo) } } } </script>
優勢:因為每次追加的圖片都是最短列,所以末尾的展示會比思路 1 中要友好很多。
劣勢:沒渲染一張都會計算一次 top,left 值。而且圖片的順序是打亂的。
思路3. CSS3 column 屬性
關鍵思路:
- column-count:指定列數
- column-gap: 設置列之間的間距
關鍵程式碼:
<template> <div class="waterfall-width-column"> <div class="image-box" v-for="img in imgList" :key="img"> <img :src="img" alt="" /> </div> </div> </template> <style lang="scss" scoped> .waterfall-width-column { column-count: 3; column-gap: 10px; .image-box { img { display: block; width: 100%; } } } </style>
優勢:更加簡單,不用額外計算,直接使用CSS渲染高效。
劣勢:圖片的順序是從上向下排列的,這個要看業務需求允不允許了。另外列數固定。
不過你可以嘗試通過媒體查詢設置不同列數
@media (min-width: 768px) { .waterfall-width-column { column-count: 3; } } @media (min-width: 992px) { .waterfall-width-column { column-count: 4; } } @media (min-width: 1200px) { .waterfall-width-column { column-count: 6; } }
等高瀑布流

說完了等寬型接下來我們來說說等高型。思路1. JS計算縮放
- 首先給定一個基準高度
- 圖片獲取基準高度下的寬度,然後計算每一行能夠放入多少張
- 此時每一行圖片肯定會小於容器寬度,然後這一行進行縮放到容器大小。在重新計算放大後的高度。
關鍵程式碼:
<script> export default { data() { return { baseHeight: 200, //圖片的基礎計算高度 imgList: [[]], //用二維數據保存每一行數據 rowWidth: 0, //每行的圖片寬度 rowCount: 0 //每行的索引 } }, methods: { loadImage() { for (let i = 0; i < 17; i++) { let image = new Image() let url = require(`@/assets/images/${i}.jpg`) image.src = url image.onload = () => { this.compare({ url: url, width: this.baseHeight * (image.width / image.height), height: this.baseHeight }) } } }, //縮放後的總圖片寬度與螢幕寬度比較 compare(image) { //容器寬度 let clientWidth = this.$refs.waterfall.clientWidth //計算每行寬度 this.rowWidth += image.width //如果寬度大於容器寬度,去掉多餘的寬度,整體進行縮放適應容器讓右邊對齊 if (this.rowWidth > clientWidth) { //減去每個css padding邊距 clientWidth = clientWidth - this.imgList[this.rowCount].length * 10 this.rowWidth = this.rowWidth - image.width //把高度調整為放大後的 let growAfterHeight = (clientWidth * this.baseHeight) / this.rowWidth this.imgList[this.rowCount].forEach(item => { item.height = growAfterHeight }) //把多餘圖片放入到下一行 this.rowWidth = image.width this.rowCount++ this.$set(this.imgList, this.rowCount, [image]) } else { this.imgList[this.rowCount].push(image) } } } } </script>
優勢:圖片的內容得到全部展示,不會被隱藏。
劣勢:需要反覆計算以及縮放。
思路2. Flex布局
- 首先給圖片一個固定高度,然後利用flex-grow的比例分配的特性
- 給圖片設定object-fit屬性讓其保持比例充滿容器
<template> <div class="waterfall-height-css"> <div class="image-box" v-for="img in imgList" :key="img.url"> <img :src="img.url" /> </div> </div> </template> <script> <style lang="scss" scoped> .waterfall-height-css { display: flex; flex-wrap: wrap; .image-box { flex-grow: 1; } img { display: block; min-width: 100%; height: 200px; object-fit: cover; } } </style>
此時你會發現,每一行的圖片都得到了很好的顯示效果。但是唯獨最後一行會出現一個小小的問題。
想像一下,假如最後一行只有一張圖片的話,他會被縮放到充滿一行,導致圖片只會顯示非常小的一部分內容。
所以,我們最後一行的圖片不進行縮放處理即可。只需要添加以下css屬性即可。
<style lang="scss" scoped> .waterfall-height-css { &:after { content: ''; display: block; flex-grow: 99999; } } </style>
因為flex-grow: 99999
的值非常大,所以會把最後一行的剩餘空間幾乎全部佔用,導致圖片分配不了,只會按照原尺寸顯示,就不會縮放佔滿一行啦。
優勢:css 設置簡單,渲染高效。
劣勢:會損失圖片的一部分可見區域。
到此,我們介紹了圖片的顯示特性以及如何利用 object-fit
進行內容的控制。
對於多圖片的布局,要想比較合理的顯示圖片,瀑布流布局是非常好的選擇,當然如果業務需求對圖片的展示友好度及美觀度不做要求,你大可利用 object-fit
控制內容即可。
但是我認為瀑布流布局也是我們應該掌握的內容之一,即便此時用不到,也可以先把文章收藏起來,以備不時之需,文中採用了多種方式的實現,你可以選擇一種最貼合你需求的方式。
當然,案例中其實還有很多細節沒有處理,比如瀏覽器窗口發上變化時重新載入圖片會發生閃動該如何優化體驗?小夥伴們不妨自己去嘗試進行優化。動手實踐是掌握技能的重要手段。
文中所有案例的程式碼地址:gitHub地址
https://github.com/liuxiaodeng/vue-admin-pro