最近、小さなプログラムのプロジェクトに取り組んでいるのですが、他のアプリのデザインから UI を借用しているのですが、その中に、写真が横にレイアウトされた UI があって、とても見栄えがします。伝統的な滝に似ていて、流れのレイアウトが水平方向に同じです。そこで自分で実装して、当初の横2枚の方法を拡張して表示枚数をカスタマイズできるようにしました。基本的な表示効果は次のとおりです。
2 つの画像を 1 行に表示します。
5 つの画像を 1 行に表示します。単一行
まず、このメソッドを作成するときのアイデアについて話しましょう:
効果分析
上の図からわかるように、問題はありません。 1 行に表示される画像の数。画像が完全に表示されることがほぼ保証され、各行の高さは異なります。これは、各画像をほぼ完全に表示できるようにするためです。そのため、行の高さは次のとおりにする必要があります。実際に表示される画像に応じて動的に調整されます。
ピクセルレンダリングを丸める必要があるため、画像の幅と高さの計算には 1 ~ 2px の誤差が生じます。このエラーは直接無視でき、画像が視覚的に引き伸ばされることはありません。
効果を分析した後、次のような疑問が生じます:
1. 各行が内部の画像を完全に表示できるようにするにはどうすればよいでしょうか?各画像の幅と高さが異なることを知っておく必要があります
2.各行の高さを動的に計算するにはどうすればよいですか?
3. 最終的に残りの画像数が1行に表示される画像数に満たない場合、最後の行に画像を配置するにはどうすればよいですか?
4,...
問題分析
まず最初の質問を見てみましょう: 単一行のすべての画像が確実に表示されるようにする方法完全に表示されます。
まず、1 行に表示される画像の数を決定します。これは、上記の 5 つの画像の 1 行のように、numberInLine = 5 のように事前に設定されています。同じ行内の各ピクチャの高さは同じであるため、ピクチャが実際にレンダリングされるときの 1 行の幅は、すべてのピクチャの幅の合計に対するピクチャの幅の比率に基づいて計算できます。式は次のとおりです。 :
imageRenderWidth = (imageWidth / imagesTotalWidth) * lineWidth
画像の実際の幅と高さは異なりますが、画像の 1 行の高さは同じであるため、まず標準の高さ stdHeight を仮定し、各画像の幅を比例して拡大縮小します。この標準の高さを計算することで、計算がスムーズになります。すべての画像の幅の合計に対する 1 つの画像の幅の比率を取得します。
各行の高さを計算する方法# ############画像の幅が決まる前提で、各行を決定する必要があります。高さは比較的単純です。各行の最初の画像に基づいて、最初に最初の画像のレンダリング幅を計算し、次にこの画像のレンダリング高さを計算し、これを行の高さとして使用します。後続の各画像は、行の高さによって独自の高さを計算します。レンダリング幅。ただし、単一の行を埋めるために、最後の画像は、前のすべての画像の幅の合計である合計幅で計算する必要があることに注意してください。そうしないと、ギャップが生じます。式の式は次のとおりです。
// 第一张图片渲染宽度 firstImageRenderWidth = (firstImageWidth / imagesTotalWidth) * lineWidth // 第一张图片渲染高度,即行高,即该行所有图片高度 lineHeight = imagesRenderHeight = firstImageRenderWidth / (firstImageWidth / firstImageHeight) // 中间图片渲染宽度 middleImageRenderWidth = lineHeight * (middleImageWidth / middleImageHeight) // 最后一张图片渲染宽度 lastImageRenderWidth = lineWidth - otherImagesTotalRenderWidth
この問題は 2 つの状況で考慮する必要があります:
1. 1 行に 5 つの画像が必要で、残りの数が 5 未満で 1 より大きい場合 (例: 4 枚の写真) : この行は残りの写真の数に応じてレイアウトでき、アルゴリズムは上記と同じです。したがって、この点に関しては、コードを再利用できる必要があります;
2. 画像が 1 つしか残っていない場合、いくつかの処理方法があります:
この画像は行全体を埋めることができます幅は完全に表示されますが、この画像が高さの非常に高い画像である場合、レイアウトの美観に重大な影響を及ぼします。
画像は依然として線幅を埋めますが、高さが指定されている場合、最大の高さが指定されます。最大高さ未満の場合は全体が表示され、最大高さを超える場合は一部のみが表示されるため、レイアウトの美しさは確保できますが、最後の画像の表示に欠陥が生じます。
前の行の行の高さを最後の行の行の高さとみなします。これにより、全体のレイアウトの一貫性を確保しながら画像を完全に表示できますが、多くの空白スペースが残ります。
上記の 3 つの処理方法では、著者は 2 番目の処理方法を使用します。興味のある友達は、他の 2 つの方法を自分で試してみてください。または、より良いレイアウト方法がある場合は、コメントで作者に伝えることもできます。
上記の 3 つの質問の説明が理解できたかどうかわかりませんが、理解できなくても大丈夫です。これらの問題を解決する方法をコードから直接学ぶことができます。
コードの実装以上がJavaScriptで水平ウォーターフォールフローを実装する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。/* 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)
})