您好,登錄后才能下訂單哦!
這篇文章給大家分享的是原生js實現橫向瀑布流的詳細介紹,相信大部分人都還沒學會這個技能,為了讓大家更加了解,給大家總結了以下內容,話不多說,一起往下看吧。
顯示效果:
單行顯示兩張圖
單行顯示五張圖
下面先說說寫這個方法時的思路:
效果分析
可以看到在上圖中,單行不管顯示幾張圖片,都幾乎能夠保證圖片被完整顯示出來,并且每一行的高度都不同——這是為了保證每張圖都能幾乎完整顯示,所以必須要根據實際要顯示的圖片來動態地調整行高。
由于像素渲染必須取整,所以計算圖片的寬高方面會存在 1~2px 的誤差。這個誤差可以直接忽略,并不會導致圖片在視覺上產生拉伸。
分析完效果后,就有了下面幾個問題:
1、如何保證每一行都能完整顯示里面的圖片?要知道每張圖片的寬高都是不同的
2、如何動態計算每一行的高度?
3、在最后圖片剩余數量不滿足單行顯示的圖片數的情況下,如何對最后一行的圖片進行布局?
4、……
問題分析
先來看第一個問題:如何保證單行的每一張圖片都能完整顯示。
首先我們可以確定單行的圖片顯示數量,這個是預先設置好的,比如上面的單行 5 張圖 numberInLine = 5。而同一行中的每張圖片高度都相同,這樣就可以根據圖片寬度與所有圖片的總寬度的比值,計算出這張圖片實際渲染時占單行的寬度,公式如下:
imageRenderWidth = (imageWidth / imagesTotalWidth) * lineWidth
雖然圖片的實際寬高各不相同,但是由于單行圖片的高度都相同,我們就可以通過先假設一個標準高度 stdHeight ,通過這個標準高度把每張圖片的寬度都進行比例縮放,這樣就可以順利計算出單張圖片寬度在所有圖片總寬度中的比值
如何計算每一行的高度
在能夠確定圖片寬度的前提下,要確定每一行的高度相對就非常簡單了。以每行第一張圖片為基準,先計算出第一張圖片的渲染寬度,然后計算出這張圖片的渲染高度,并以此作為行高,之后的每張圖片都通過行高計算出各自的渲染寬度。但是需要注意的是,為了填滿單行,最后一張圖片需要通過總寬度-之前所有圖片寬度之和的方式計算出,否則就會有空白,表達公式如下:
// 第一張圖片渲染寬度 firstImageRenderWidth = (firstImageWidth / imagesTotalWidth) * lineWidth // 第一張圖片渲染高度,即行高,即該行所有圖片高度 lineHeight = imagesRenderHeight = firstImageRenderWidth / (firstImageWidth / firstImageHeight) // 中間圖片渲染寬度 middleImageRenderWidth = lineHeight * (middleImageWidth / middleImageHeight) // 最后一張圖片渲染寬度 lastImageRenderWidth = lineWidth - otherImagesTotalRenderWidth
當剩余圖片數量不足單行數量時如何布局?
這個問題需要分兩種情況來考慮:
1、當單行需要 5 張,而剩余數量不足 5 張但大于 1 張時(如 4 張圖片):該行可按照剩余圖片的數量布局,算法依然如上。所以對于這點,需要讓代碼具有可復用性;
2、只剩下 1 張圖片時,有下面幾種處理方法:
可以將這張圖片占滿全部行寬并且完整顯示,但是如果這張圖片是高度很高的圖片,就會嚴重影響布局的美觀性
依然將圖片占滿行寬,但是給定一個最大高度,當高度不及最大高度時完整顯示,當超過時只顯示部分,這樣能保證布局美觀性,但是最后一張圖片的顯示存在瑕疵
取前一行的行高作為最后一行的行高,這樣可以在保證整體布局一致性的情況下,依然可以完整顯示圖片,但是這樣做最后一行會留有大量空白位置
對于上面三種處理方式,作者采用的是第二種。感興趣的小伙伴可以自己嘗試其他兩種方式。或者如果你有更好的布局方式,也可以在評論里告訴作者哦!
不知道上面三個問題的解釋小伙伴們有沒有理解了呢?不理解也沒事,可以直接通過代碼來了解是如何解決這些問題的。
代碼實現
/* imagesLayout.js */ /* * 圖片橫向瀑布流布局 最大限度保證每張圖片完整顯示 可以獲取最后計算出來的圖片布局寬高信息 最后的瀑布流效果需要配合 css 實現(作者通過浮動布局實現)當然你也可以對代碼進行修改 讓其能夠直接返回一段已經布局完成的 html 結構 * 需要先提供每張圖片的寬高 如果沒有圖片的寬高數據 則可以在代碼中添加處理方法從而獲取到圖片寬高數據后再布局 但是并不推薦這樣做 * 盡量保證圖片總數能被單行顯示的數量整除 避免最后一行單張顯示 否則會影響美觀 * 每張圖由于寬高取整返回的寬高存在0-2px的誤差 可以通過 css 保證視覺效果 */ /* * @param images {Object} 圖片對象列表,每一個對象有 src width height 三個屬性 * @param containerWidth {Integer} 容器寬度 * @param numberInLine {Integer} 單行顯示圖片數量 * @param limit {Integer} 限制需要進行布局的圖片的數量 如果傳入的圖片列表有100張 但只需要對前20張進行布局 后面的圖片忽略 則可以使用此參數限制 如果不傳則默認0(不限制) * @param stdRatio {Float} 圖片標準寬高比 */ class ImagesLayout { constructor(images, containerWidth, numberInLine = 10, limit = 0, stdRatio = 1.5) { // 圖片列表 this.images = images // 布局完畢的圖片列表 通過該屬性可以獲得圖片布局的寬高信息 this.completedImages = [] // 容器寬度 this.containerWidth = containerWidth // 單行顯示的圖片數量 this.numberInLine = numberInLine // 限制布局的數量 如果傳入的圖片列表有100張 但只需要對前20張進行布局 后面的圖片忽略 則可以使用此參數限制 如果不傳則默認0(不限制) this.limit = limit // 圖片標準寬高比(當最后一行只剩一張圖片時 為了不讓布局看上去很奇怪 所以要有一個標準寬高比 當圖片實際寬高比大于標準寬高比時會發生截取 小于時按照實際高度占滿整行顯示) this.stdRatio = stdRatio // 圖片撐滿整行時的標準高度 this.stdHeight = this.containerWidth / this.stdRatio this.chunkAndLayout() } // 將圖片列表根據單行數量分塊并開始計算布局 chunkAndLayout () { // 當圖片只有一張時,完整顯示這張圖片 if (this.images.length === 1) { this.layoutFullImage(this.images[0]) return } let temp = [] for (let i = 0; i < this.images.length; i++) { if (this.limit && i >= this.limit) return temp.push(this.images[i]) // 當單行圖片數量達到限制數量時 // 當已經是最后一張圖片時 // 當已經達到需要布局的最大數量時 if (i % this.numberInLine === this.numberInLine - 1 || i === this.images.length - 1 || i === this.limit - 1) { this.computedImagesLayout(temp) temp = [] } } } // 完整顯示圖片 layoutFullImage (image) { let ratio = image.width / image.height image.width = this.containerWidth image.height = parseInt(this.containerWidth / ratio) this.completedImages.push(image) } // 根據分塊計算圖片布局信息 computedImagesLayout(images) { if (images.length === 1) { // 當前分組只有一張圖片時 this.layoutWithSingleImage(images[0]) } else { // 當前分組有多張圖片時 this.layoutWithMultipleImages(images) } } // 分組中只有一張圖片 該張圖片會單獨占滿整行的布局 如果圖片高度過大則以標準寬高比為標準 其余部分剪裁 否則完整顯示 layoutWithSingleImage (image) { let ratio = image.width / image.height image.width = this.containerWidth // 如果是長圖,則布局時按照標準寬高比顯示中間部分 if (ratio < this.stdRatio) { image.height = parseInt(this.stdHeight) } else { image.height = parseInt(this.containerWidth / ratio) } this.completedImages.push(image) } // 分組中有多張圖片時的布局 // 以相對圖寬為標準,根據每張圖的相對寬度計算占據容器的寬度 layoutWithMultipleImages(images) { let widths = [] // 保存每張圖的相對寬度 let ratios = [] // 保存每張圖的寬高比 images.forEach(item => { // 計算每張圖的寬高比 let ratio = item.width / item.height // 根據標準高度計算相對圖寬 let relateWidth = this.stdHeight * ratio widths.push(relateWidth) ratios.push(ratio) }) // 計算每張圖片相對寬度的總和 let totalWidth = widths.reduce((sum, item) => sum + item, 0) let lineHeight = 0 // 行高 let leftWidth = this.containerWidth // 容器剩余寬度 還未開始布局時的剩余寬度等于容器寬度 images.forEach((item, i) => { if (i === 0) { // 第一張圖片 // 通過圖片相對寬度與相對總寬度的比值計算出在容器中占據的寬度與高度 item.width = parseInt(this.containerWidth * (widths[i] / totalWidth)) item.height = lineHeight = parseInt(item.width / ratios[i]) // 第一張圖片布局后的剩余容器寬度 leftWidth = leftWidth - item.width } else if (i === images.length - 1) { // 最后一張圖片 // 寬度為剩余容器寬度 item.width = leftWidth item.height = lineHeight } else { // 中間圖片 // 以行高為標準 計算出圖片在容器中的寬度 item.height = lineHeight item.width = parseInt(item.height * ratios[i]) // 圖片布局后剩余的容器寬度 leftWidth = leftWidth - item.width } this.completedImages.push(item) }) } }
<!-- imagesLayout.html --> <!-- css 布局通過浮動實現 --> <style> * { box-sizing: border-box; } #horizontal-waterfull { width: 300px; } #horizontal-waterfull:before, #horizontal-waterfull:after { content: ''; display: table; clear: both; } img { display: block; width: 100%; height: 100%; } .image-box { float: left; padding: 1px; overflow: hidden; } </style> // 水平布局瀑布流容器 <div id="horizontal-waterfull"></div> <script src="./imagesLayout.js"></script> <script> // 待布局圖片列表 const images = [{ src: 'https://static.cxstore.top/images/lake.jpg', width: 4000, height: 6000 }, { src: 'https://static.cxstore.top/images/japan.jpg', width: 1500, height: 1125 }, { src: 'https://static.cxstore.top/images/girl.jpg', width: 5616, height: 3266 }, { src: 'https://static.cxstore.top/images/flower.jpg', width: 4864, height: 3648 }, { src: 'https://static.cxstore.top/images/lake.jpg', width: 4000, height: 6000 }, { src: 'https://static.cxstore.top/images/japan.jpg', width: 1500, height: 1125 }, { src: 'https://static.cxstore.top/images/grass.jpg', width: 5184, height: 2916 }] // 獲取布局容器 const $box = document.getElementById('horizontal-waterfull') // 創建一個布局實例 const layout = new ImagesLayout(images, $box.clientWidth, 4) // 通過 layout 的 completedImages 獲取所有圖片的布局信息 layout.completedImages.forEach(item => { let $imageBox = document.createElement('div') $imageBox.setAttribute('class', 'image-box') $imageBox.style.width = item.width + 'px' $imageBox.style.height = item.height + 'px' let $image = document.createElement('img') $image.setAttribute('src', item.src) $imageBox.appendChild($image) $box.appendChild($imageBox) })
看完上述內容,你們掌握原生js實現橫向瀑布流的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。