圖片的橫向瀑布流,其實簡單地按順序排列就可以了 但要實現每行中各圖片都等高(各行不一定等高,但每行裡面等高),且每行都占滿,就需要用到flex的特性了 控制每行圖片高度都一致,可能會影響圖片的比例,所以不能簡單暴力地設置高度,需要按比例來動態計算 另外,如要限製圖片展示的行數,則只需判斷好每行總高度 ...
圖片的橫向瀑布流,其實簡單地按順序排列就可以了
但要實現每行中各圖片都等高(各行不一定等高,但每行裡面等高),且每行都占滿,就需要用到flex的特性了
控制每行圖片高度都一致,可能會影響圖片的比例,所以不能簡單暴力地設置高度,需要按比例來動態計算
另外,如要限製圖片展示的行數,則只需判斷好每行總高度與容器總高度的關係即可
這裡就來實現一下這個小功能
因為都是假數據的關係,圖片的寬高值是隨機數,並非原圖寬高值,僅作參考
看完上面那張大大的圖,先想一下可以怎麼實現..
要實現每行都能夠占滿,需要用到 flex-grow 這個屬性
flex-grow基於flex-basis基準值來計算,而flex-basis則基於項目的width、min|max-width相關的值來計算,或者手動定義
使用flex-grow可以分配按比例分配主軸的剩餘空間
如果有10張圖片需要放置,第一行僅可以放置四張圖片,剩餘100px的空間,那麼各圖片的flex-grow可以直接配置成圖片的寬度width值,即可很方便精準地分配好這剩餘的空間
第二行可以放五張圖片,剩餘N px的空間... 按照這種計算方式來鋪滿每一行
<h1 class="get-latest-update"> <a href="javascript:;">獲取最近更新</a> </h1> <div class="img-items"></div> <script type="text/template" id="img-item-tpl"> <div class="img-item" style="flex-grow: {{width}}; width: {{width}}px;"> <a href="#/img/{{id}}" style="padding-top: {{paddingTop}}%;"> <img data-src="{{src}}" src="{{src}}" width="100%" height="100%"> </a> </div> </script>
上面頁面模板中,flex-grow 與 width的值一致,用以按比例分配每行剩餘空間
另外可以看到這裡有個 padding-top 的百分比值
我們都知道 padding-top 的百分比值是基於父元素的寬度來計算的,根據盒模型,一般這種計算方式是為了獲取固定寬高比
當父元素有寬度,但高度為0時,整體高度則由padding-top值來撐開,則父元素就有了一個設定的寬高比,
同時我們將子元素(這裡是圖片)position值設置為absolute,寬高占滿父元素,則子元素圖片也有了一定的寬高比,實現按比例的圖片縮放
來看看對應的樣式設置
body { background-color: #f2f2f2; } .get-latest-update { font-size: 20px; cursor: pointer; > a { color: #0183fd; text-decoration: none; } } .img-items { display: flex; flex-wrap: wrap; overflow: hidden; } .img-item { margin-right: 10px; margin-bottom: 10px; background-color: #fff; box-shadow: 0 0 10px #ddd; > a { position: relative; display: block; width: 100%; } img { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } }
那麼,這個width和padding-top的該怎麼計算出來呢
核心代碼是
// 圖片預定義的高度 var baseHeight = 200; for (var i = 1; i <= num; ++i) { var w = getRandom(width.min, width.max); var h = getRandom(height.min, height.max); imgs.push({ id: i, src: imgSrcBase[Math.floor(i / 10)] + (i % 10 + 1) + '.jpg', // 設置圖片的寬度,需根據預定義的高度值來做好比例處理 // 為了讓每行各圖片按自身寬度自動flex-glow,同時利用這個比例處理保證每行圖片的高度一致 width: w * baseHeight / h, height: h, // padding-top的百分比,用以基於父元素寬度設置該元素的高度 // 為了保證圖片寬高按比例 paddingTop: h / w * 100 }); }
paddingTop的值,按照以下這個映射關係來看就好
容器高度 == 容器寬度 * paddingTop %
最終會形成
容器高度 == 圖片高度
容器寬度 == 圖片寬度
所以
圖片高度 == 圖片寬度 * paddingTop %
width值的計算可能比較繞
假設這裡 width直接取 圖片寬度w值,就會出現一行中圖片高度不一致的情況
因為最終的圖片高度即為容器的高度,而容器的高度是由容器寬度決定的(註意這裡的paddingTop值已經確定),而容器寬度就是由這裡的width來決定的。圖片寬度的不同,就直接導致了最終高度的不同
所以,為了確保圖片高度一致,假設有三張圖片 50*50 100*100 50*150 放在了同一行中,flex佈局會將三張圖片所在容器的高度自適應為最高的那個150,如果flex-grow值起作用了,這個最高值還會再多一些
我們可以考慮最簡單的情況,正好放滿一行。那麼最終三張圖片的高度都應該為150,按照各自的圖片比例來調整,則最終第一張圖片寬度的計算 50 / 50 === width / 150 , 則 width = 50 / 50 * 150
可能有些行最高的圖片還是不夠高,為了也能夠顯示出比較大的圖片,我們還可以定義好這個基準高度值,比如 baseHeight設置為 200
所以,最終每一張圖片的寬度width值為 w / h * baseHeight
還要一個問題,如何實現只顯示三行
顯示三行,每行的圖片數量不固定,這是通過flex佈局自動排列每一行的,都會經過 基本排列 -> 分配剩餘空間 的步驟
目前想到的方法是對每一行的容器所占位置進行累加,最後對比即可
不過這種方式會有比較大的性能損耗,看還能不能有更優雅的做法吧
// 設置顯示的圖片行數 function setLineLimit(num) { // 內容區寬度 var contentWidth = $('.img-items').outerWidth(); // 定義的外邊距 var marginWidth = 10; // 每行寬度 var curWidth = 0; // 行標識 var lineIndex = 1; // 初始需將圖片設置為可見,否則flex無法自適應排版 $('.img-item').show() .each(function() { var $item = $(this); var itemWidth = $item.outerWidth(); // 隱藏多餘的行 if (lineIndex > num) { $item.hide(); return; } $item.show(); // 某一行 if (curWidth + itemWidth + marginWidth <= contentWidth + marginWidth) { curWidth += itemWidth + marginWidth; } // 下一行 else { ++lineIndex; curWidth = itemWidth; if (lineIndex > num) { $item.hide(); } } }); }
主要註意的點是,為了兼顧視窗縮放的過程中,自動排列也能照常進行,在計算的時候需要將每個項先顯示出來,再進入計算環節
// 視窗縮放時處理可視的圖片 $(window).resize(throttle(setLineLimit.bind(this, 3), 200));
完整JS代碼
1 // 事件綁定 2 function addEvent(elem, type, handler) { 3 elem.addEventListener(type, handler, false); 4 } 5 6 function qs(selector) { 7 return document.querySelector(selector); 8 } 9 10 function qsa(selectors) { 11 return document.querySelectorAll(selectors); 12 } 13 14 // 函數節流,頻繁操作中間隔 delay 的時間才處理一次 15 function throttle(fn, delay) { 16 delay = delay || 200; 17 18 var timer = null; 19 // 每次滾動初始的標識 20 var timestamp = 0; 21 22 return function () { 23 var arg = arguments; 24 var now = Date.now(); 25 26 // 設置開始時間 27 if (timestamp === 0) { 28 timestamp = now; 29 } 30 31 clearTimeout(timer); 32 timer = null; 33 34 // 已經到了delay的一段時間,進行處理 35 if (now - timestamp >= delay) { 36 fn.apply(this, arg); 37 timestamp = now; 38 } 39 // 添加定時器,確保最後一次的操作也能處理 40 else { 41 timer = setTimeout(function () { 42 fn.apply(this, arg); 43 // 恢復標識 44 timestamp = 0; 45 }, delay); 46 } 47 } 48 } 49 50 // 獲取隨機數 51 function getRandom(min, max) { 52 return Math.round(Math.random() * (max - min + 1) + min); 53 } 54 55 // 構造圖片數據 56 function createMockImgs(num) { 57 var imgs = []; 58 59 // 圖片寬高數據範圍 60 var width = { 61 min: 50, 62 max: 200 63 }; 64 65 var height = { 66 min: 150, 67 max: 300 68 }; 69 70 // 圖片源 71 var imgSrcBase = [ 72 'http://www.deskcar.com/desktop/movietv/2009/2009227225145/', 73 'http://www.deskcar.com/desktop/fengjing/2017418153624/', 74 'http://www.deskcar.com/desktop/fengjing/2017418153624/', 75 'http://www.deskcar.com/desktop/fengjing/2017418153624/', 76 'http://www.deskcar.com/desktop/fengjing/2017418153624/', 77 'http://www.deskcar.com/desktop/else/20161228125639/', 78 'http://www.deskcar.com/desktop/fengjing/2017418153446/' 79 ]; 80 81 // 圖片預定義的高度 82 var baseHeight = 200; 83 84 for (var i = 1; i <= num; ++i) { 85 var w = getRandom(width.min, width.max); 86 var h = getRandom(height.min, height.max); 87 88 imgs.push({ 89 id: i, 90 src: imgSrcBase[Math.floor(i / 10)] + (i % 10 + 1) + '.jpg', 91 // 設置圖片的寬度,需根據預定義的高度值來做好比例處理 92 // 為了讓每行各圖片按自身寬度自動flex-glow,同時利用這個比例處理保證每行圖片的高度一致 93 width: w * baseHeight / h, 94 height: h, 95 // padding-top的百分比,用以基於父元素寬度設置該元素的高度 96 // 為了保證圖片寬高按比例 97 paddingTop: h / w * 100 98 }); 99 } 100 101 return imgs; 102 } 103 104 // 視窗縮放時處理可視的圖片 105 $(window).resize(throttle(setLineLimit.bind(this, 3), 200)); 106 107 // 設置顯示的圖片行數 108 function setLineLimit(num) { 109 // 內容區寬度 110 var contentWidth = $('.img-items').outerWidth(); 111 // 定義的外邊距 112 var marginWidth = 10; 113 // 每行寬度 114 var curWidth = 0; 115 // 行標識 116 var lineIndex = 1; 117 118 // 初始需將圖片設置為可見,否則flex無法自適應排版 119 $('.img-item').show() 120 .each(function() { 121 var $item = $(this); 122 var itemWidth = $item.outerWidth(); 123 124 // 隱藏多餘的行 125 if (lineIndex > num) { 126 $item.hide(); 127 return; 128 } 129 130 $item.show(); 131 132 // 某一行 133 if (curWidth + itemWidth + marginWidth <= contentWidth + marginWidth) { 134 curWidth += itemWidth + marginWidth; 135 } 136 // 下一行 137 else { 138 ++lineIndex; 139 curWidth = itemWidth; 140 141 if (lineIndex > num) { 142 $item.hide(); 143 } 144 } 145 }); 146 } 147 148 var mockImgs = createMockImgs(60); 149 150 console.log(mockImgs); 151 152 // 點擊渲染 153 addEvent(qs('.get-latest-update'), 'click', function() { 154 renderList(mockImgs); 155 setLineLimit(3); 156 }); 157 158 var itemTpl = qs('#img-item-tpl').innerHTML; 159 var itemsDOM = qs('.img-items'); 160 161 /** 162 * 渲染數據 163 * @param {[type]} data [description] 164 * @return {[type]} [description] 165 */ 166 function renderList(data) { 167 var html = ''; 168 var fragment = document.createDocumentFragment(); 169 170 data.forEach(function(item) { 171 var divTemp = document.createElement('div'); 172 173 // 模板替換 174 divTemp.innerHTML = itemTpl.replace(/{{(\w+)}}/g, function(input, match) { 175 return match ? item[match] || '' : ''; 176 }); 177 178 fragment.appendChild(divTemp.firstElementChild); 179 }); 180 181 // 渲染 182 itemsDOM.appendChild(fragment); 183 }View Code