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 怎样实现横向瀑布流的具体内容,更多请关注百分百源码网其它相关文章!