JavaScript 如何實現橫向瀑布流
最近在做一個小程式項目,在UI 上借鑒了一下其他App 設計,其中有一個圖片橫向佈局鋪滿的UI 感覺挺好看的,類似於傳統的瀑布流佈局橫過來一樣。於是就自己實現了一下,並且將原本的橫向兩張圖的方法擴展了下,改成了可以自訂顯示張數的方法。以下是基本的顯示效果:
單行顯示兩張圖
單行顯示五張圖
下面先說說寫這個方法時的想法:
效果分析
可以看到在上圖中,單行不管顯示幾張圖片,都幾乎能夠保證圖片被完整顯示出來,並且每一行的高度都不同——這是為了保證每張圖都能幾乎完整顯示,所以必須根據實際要顯示的圖片來動態地調整行高。
由於像素渲染必須取整,所以計算圖片的寬高方面會存在 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教學》
以上是JavaScript 如何實現橫向瀑布流的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

熱門話題

如何使用WebSocket和JavaScript實現線上語音辨識系統引言:隨著科技的不斷發展,語音辨識技術已成為了人工智慧領域的重要組成部分。而基於WebSocket和JavaScript實現的線上語音辨識系統,具備了低延遲、即時性和跨平台的特點,成為了廣泛應用的解決方案。本文將介紹如何使用WebSocket和JavaScript來實現線上語音辨識系

人臉偵測辨識技術已經是一個比較成熟且應用廣泛的技術。而目前最廣泛的網路應用語言非JS莫屬,在Web前端實現人臉偵測辨識相比後端的人臉辨識有優勢也有弱勢。優點包括減少網路互動、即時識別,大大縮短了使用者等待時間,提高了使用者體驗;弱勢是:受到模型大小限制,其中準確率也有限。如何在web端使用js實現人臉偵測呢?為了實現Web端人臉識別,需要熟悉相關的程式語言和技術,如JavaScript、HTML、CSS、WebRTC等。同時也需要掌握相關的電腦視覺和人工智慧技術。值得注意的是,由於Web端的計

股票分析必備工具:學習PHP和JS繪製蠟燭圖的步驟,需要具體程式碼範例隨著網路和科技的快速發展,股票交易已成為許多投資者的重要途徑之一。而股票分析是投資人決策的重要一環,其中蠟燭圖被廣泛應用於技術分析。學習如何使用PHP和JS繪製蠟燭圖將為投資者提供更多直觀的信息,幫助他們更好地做出決策。蠟燭圖是一種以蠟燭形狀來展示股票價格的技術圖表。它展示了股票價格的

WebSocket與JavaScript:實現即時監控系統的關鍵技術引言:隨著互聯網技術的快速發展,即時監控系統在各個領域中得到了廣泛的應用。而實現即時監控的關鍵技術之一就是WebSocket與JavaScript的結合使用。本文將介紹WebSocket與JavaScript在即時監控系統中的應用,並給出程式碼範例,詳細解釋其實作原理。一、WebSocket技

如何利用JavaScript和WebSocket實現即時線上點餐系統介紹:隨著網路的普及和技術的進步,越來越多的餐廳開始提供線上點餐服務。為了實現即時線上點餐系統,我們可以利用JavaScript和WebSocket技術。 WebSocket是一種基於TCP協定的全雙工通訊協議,可實現客戶端與伺服器的即時雙向通訊。在即時線上點餐系統中,當使用者選擇菜餚並下訂單

如何使用WebSocket和JavaScript實現線上預約系統在當今數位化的時代,越來越多的業務和服務都需要提供線上預約功能。而實現一個高效、即時的線上預約系統是至關重要的。本文將介紹如何使用WebSocket和JavaScript來實作一個線上預約系統,並提供具體的程式碼範例。一、什麼是WebSocketWebSocket是一種在單一TCP連線上進行全雙工

JavaScript和WebSocket:打造高效的即時天氣預報系統引言:如今,天氣預報的準確性對於日常生活以及決策制定具有重要意義。隨著技術的發展,我們可以透過即時獲取天氣數據來提供更準確可靠的天氣預報。在本文中,我們將學習如何使用JavaScript和WebSocket技術,來建立一個高效的即時天氣預報系統。本文將透過具體的程式碼範例來展示實現的過程。 We

隨著網路金融的快速發展,股票投資已經成為了越來越多人的選擇。而在股票交易中,蠟燭圖是常用的技術分析方法,它能夠顯示股票價格的變動趨勢,幫助投資人做出更精準的決策。本文將透過介紹PHP和JS的開發技巧,帶領讀者了解如何繪製股票蠟燭圖,並提供具體的程式碼範例。一、了解股票蠟燭圖在介紹如何繪製股票蠟燭圖之前,我們首先需要先了解什麼是蠟燭圖。蠟燭圖是由日本人
