详解瀑布流布局的5种实现方式及object-fit

最近项目中需要处理与图片相关的布局,不得不说图片这玩意真想要得到完美的展示效果还真是要费些力气。因为图片的尺寸或者比例各不相同。所以想要不同尺寸的图片有好的显示效果,你就需要找到适合的方式。
而且图片往往是不可或缺元素。毕竟一图胜千言,有时候图片能给带来非常好的效果。
比如我们每天都会使用的表情包,它往往能够表达出我们无法用文字描述的信息,还比如我们经常在公众号里看到的漫画虽然短短几个字,但是却能够让我们看的不亦乐乎。
当然如果我们做图片网站的,那图片的处理就是绕不开的话题了。因对图片的处理经验不多,所以就边学边用。今天就把最近学习与图片相关的知识整理出来。
比如单个图片如何更好的展示,瀑布流布局都有哪些你不知道的实现方式。
接下来我们就直接进入正题,我们先从单张图片的展示说起。
设置宽或高 100%
因为图片其本身的独特性:

不设置宽高的情况下会按原有的尺寸显示在网页中。即有多大,显示多大。
在非等比缩放的情况下会被拉伸变形。
设置宽度或者高度时,会保持原宽高比进行缩放。

<style lang="scss" scoped>
.box1 {
  width: 150px;
  height: 150px;
  border: 2px solid red;
}
.box2 {
  width: 150px;
  height: 100px;
  border: 2px solid red;
  img {
    display: block;
    width: 100%;
    height: 100%;
  }
}
</style>
复制代码显然当我们采用 1、2 种方式的时候破坏性很强,无法应用到实际的项目中去。
所以往往我们会在项目中使用第 3 种方式,即设置高度或者宽度。它会保持原有比例进行缩放。

<style lang="scss" scoped>
.box {
  width: 150px;
  height: 150px;
  border: 2px solid red;
}
.img1 {
  width: 100%;
}
.img2 {
  height: 100%;
}
</style>
复制代码但是问题又来了,图片要么超出容器,要么就会留有空白,除非容器的宽高比恰好等于图片的宽高比时,才会完全贴合。
对于超出容器的图片我们可以使用 overflow: hidden 把超出部分隐藏。图片得到了好的展示效果。但相应的我们也损失了图片的一部分可视区域。
所以这个时候就需要你根据需求进行取舍了,到底是选择隐藏图片的一部分,还是留有空白。有的小伙伴会说,我们产品说了,图片变形没问题,你就给我充满容器就行了。好吧....
即使如此,你也要把这篇文章好好读一读,因为需求是千变万化的,保不齐哪一天就需要了。
又有小伙伴说,这 2 种都不符合我们的产品需求怎么办,还有其他的方式吗?答案是必须的,一起来看。
object-fit
CSS3 的 object-fit 属性是用来指定「可替换元素」的内容是如何适应到容器中的。它的值有 5 种。分别为:fill | contain | cover | none | scale-down。先看下效果在来一一解释它们到底都是什么意思。

<template>
  <div class="box">
    <img src="https://picsum.photos/id/1027/200/300"/>
  </div>
</template>
<style lang="scss" scoped>
.box {
  width: 150px;
  height: 150px;
  border: 2px solid red;
  img {
    width: 100%;
    height: 100%;
    object-fit: contain;
  }
}
</style>

复制代码看到上面的显示效果,理解起来并不难了。

fill:会充满整个容器,不考虑宽高比,所以会被拉伸变形。
contain:会缩放到容器内,保持宽高比。
cover:会保持比例进行缩放,内容的尺寸一定会大于等于容器,然后进行裁剪。
none:保持图片的原始尺寸。

而 scale-down 有两种表现方式所以我们单独来看。

scale-down:会在 none 或 contain 中选择一个,原则是:当容器小时,它的表现和 contain 一样;当图片小时,它的表现和 none 一样。即谁小选择谁。

到这里不知道有没有小伙伴和我一样,在看到图片的不同表现时,我特意去浏览器查看了下 <img>的真实尺寸,发现依然是 width: 100%;height: 100%; 是充满整个容器的。
但为什么内容显示却有不同的效果呢,这让我产生了疑惑。本着发现探索的精神,就去寻找答案了。
W3c 是这么描述的:<img> 标签创建的是被引用图像的占位空间。
而张鑫旭大大在半深入理解CSS3 object-position/object-fit属性一文中也指出:
<img>元素其实与内容是相互独立的。<img>相当于一个外壳包裹着内容。你控制的只是<img> 元素的尺寸。而内容的尺寸则由 object-fit 属性控制。
综上索述,<img> 是一个空间占位符,并不控制内容。原来如此。感觉自己又进步了。每一次的探索,都会发现新的东西,这种感觉很奇妙。特别是还把它整理出文章,提供大家学习,感觉牛逼哄哄带闪电。
知道了这些之后我们操控图片时更加的得心应手一些。那会了这些就结束了吗?不不不,这才是刚刚开始,精彩的还在后面。后面的布局才更加精彩。
多图片的布局
上面一直在说的都单张图片的显示。只要我们把图片用合适的方式放进容器即可。如果是图片列表呢?或者专门展示图片的网站会有大量的图片而且尺寸和比例千奇百怪,各不相同。
假设要想实现一个图片画廊效果,首先我们给图片一个 float: left,但是由于图片的尺寸不一样,导致每个图片的高度不同,下一行的图片就会卡住,导致布局错乱。

此时你不得不给容器设置高度,让图片能够底部对齐,但在文章一开始我们也提到了,这时候图片要么超出容器的高度,要么留有空白。
那如果使用 object-fit 属性按照业务需求去控制内容,貌似可以完成任务。

我们把值设为 contain,布局是没有问题了,但是其实很不美观。如果设为 cover,如果图片过大很多内容都会丢失看不到。 怎么办?有什么解决办法?这时候就是瀑布流布局的优势了。
瀑布流布局即不会出现错乱现象,而且会最大限度显示图片的内容。所以是众多图片网站选择的布局方式。
而瀑布流布局目前有两种形式:一是等宽型,二是等高型。我们先来说说等宽型。
等宽瀑布流

看到上面你实现的思路是什么?可以思考几秒,接下来一起来看这些实现方式中有没有和你一思路一样的。
思路1. JS 计算列数
关键思路:

首先设置列宽度,然后计算能够展示的列数。
向每一列中添加图片。

关键代码:
<script>
export default {
  methods: {
     //计算图片列数
    getColNumbers() {
      let clientWidth = this.$refs.waterfall.clientWidth
      this.colNumbers = Math.floor(clientWidth / this.colWidth)
    },
    //读取图片
    loadImage() {
      this.getColNumbers()
      for (let i = 0; i < 17; i++) {
        let colIndex = i % this.colNumbers
        let url = require(`@/assets/images/${i}.jpg`)
        if (this.imgList[colIndex]) {
          this.imgList[colIndex].push(url)
        } else {
          this.$set(this.imgList, colIndex, [url])
        }
      }
    },
  }
}
</script>
复制代码优势:思路清晰简单,不需要做过多的计算,只要计算需要显示的列数然后添加图片即可。
劣势:每列的末尾可能不够友好,可能出现有些列会很长,有些又会很短。
思路2. 利用绝对定位
关键思路:

首先设置列宽度,然后计算能够展示的列数。
把图片设置为绝对定位,然后计算出每个图片的top,left值。
先把第一行图片排好,top 为 0,left 为 列的索引*列宽。
从第二行开始,每张图片都放到最短的一列下面。然后增加此列高度,此时列的高度发生变化,下张图片又会寻找其他最短的列。以此持续计算下去。

关键代码:
<script>
export default {
  methods: {
    //计算图片列数
    getColNumbers() {
      let clientWidth = this.$refs.waterfall.clientWidth
      this.colNumbers = Math.floor(clientWidth / this.colWidth)
    },
    //读取图片
    loadImage() {
      this.getColNumbers()
      for (let i = 0; i < 17; i++) {
        let image = new Image()
        let url = require(`@/assets/images/${i}.jpg`)
        image.src = url
        image.onload = () => {
          this.render({
            index: i,
            url: url,
            ratio: image.width / image.height
          })
        }
      }
    },
    render(imgInfo) {
      let colIndex = imgInfo.index % this.colNumbers
      imgInfo.left = colIndex * this.colWidth
      //首行 top为 0,记录每列的高度
      if (imgInfo.index < this.colNumbers) {
        imgInfo.top = 0
        this.colHeight[colIndex] = this.colWidth / imgInfo.ratio
      } else {
        //获取高度的最小值
        let minHeight = Math.min.apply(null, this.colHeight)
        let minIndex = this.colHeight.indexOf(minHeight)
        //此图片的 top 为上面图片的高度,left 相等
        imgInfo.top = minHeight
        imgInfo.left = minIndex * this.colWidth
        //把高度加上去
        this.colHeight[minIndex] += this.colWidth / imgInfo.ratio
      }
      this.imgList.push(imgInfo)
    }
  }
}
</script>
复制代码优势:因为每次追加的图片都是最短列,所以末尾的展示会比思路 1 中要友好很多。
劣势:没渲染一张都会计算一次 top,left 值。而且图片的顺序是打乱的。
思路3. CSS3 column 属性
关键思路:

column-count:指定列数
column-gap: 设置列之间的间距

关键代码:
<template>
  <div class="waterfall-width-column">
    <div class="image-box" v-for="img in imgList" :key="img">
      <img :src="img"  />
    </div>
  </div>
</template>
<style lang="scss" scoped>
.waterfall-width-column {
  column-count: 3;
  column-gap: 10px;
  .image-box {
    img {
      display: block;
      width: 100%;
    }
  }
}
</style>
复制代码优势:更加简单,不用额外计算,直接使用CSS渲染高效。
劣势:图片的顺序是从上向下排列的,这个要看业务需求允不允许了。另外列数固定。
不过你可以尝试通过媒体查询设置不同列数
@media (min-width: 768px) {
  .waterfall-width-column {
    column-count: 3;
  }
}
@media (min-width: 992px) {
  .waterfall-width-column {
    column-count: 4;
  }
}
@media (min-width: 1200px) {
  .waterfall-width-column {
    column-count: 6;
  }
}
复制代码等高瀑布流
说完了等宽型接下来我们来说说等高型。

思路1. JS计算缩放

首先给定一个基准高度
图片获取基准高度下的宽度,然后计算每一行能够放入多少张
此时每一行图片肯定会小于容器宽度,然后这一行进行缩放到容器大小。在重新计算放大后的高度。

关键代码:
<script>
export default {
  data() {
    return {
      baseHeight: 200, //图片的基础计算高度
      imgList: [[]], //用二维数据保存每一行数据
      rowWidth: 0, //每行的图片宽度
      rowCount: 0 //每行的索引
    }
  },
  methods: {
    loadImage() {
      for (let i = 0; i < 17; i++) {
        let image = new Image()
        let url = require(`@/assets/images/${i}.jpg`)
        image.src = url
        image.onload = () => {
          this.compare({
            url: url,
            width: this.baseHeight * (image.width / image.height),
            height: this.baseHeight
          })
        }
      }
    },
    //缩放后的总图片宽度与屏幕宽度比较
    compare(image) {
      //容器宽度
      let clientWidth = this.$refs.waterfall.clientWidth
      //计算每行宽度
      this.rowWidth += image.width
      //如果宽度大于容器宽度,去掉多余的宽度,整体进行缩放适应容器让右边对齐
      if (this.rowWidth > clientWidth) {
        //减去每个css padding边距
        clientWidth = clientWidth - this.imgList[this.rowCount].length * 10
        this.rowWidth = this.rowWidth - image.width
        //把高度调整为放大后的
        let growAfterHeight = (clientWidth * this.baseHeight) / this.rowWidth
        this.imgList[this.rowCount].forEach(item => {
          item.height = growAfterHeight
        })
        //把多余图片放入到下一行
        this.rowWidth = image.width
        this.rowCount++
        this.$set(this.imgList, this.rowCount, [image])
      } else {
        this.imgList[this.rowCount].push(image)
      }
    }
  }
}
</script>
复制代码优势:图片的内容得到全部展示,不会被隐藏。
劣势:需要反复计算以及缩放。
思路2. Flex布局

首先给图片一个固定高度,然后利用flex-grow的比例分配的特性
给图片设定object-fit属性让其保持比例充满容器

<template>
  <div class="waterfall-height-css">
    <div class="image-box" v-for="img in imgList" :key="img.url">
      <img :src="img.url" />
    </div>
  </div>
</template>
<script>
<style lang="scss" scoped>
.waterfall-height-css {
  display: flex;
  flex-wrap: wrap;
  .image-box {
    flex-grow: 1;
  }
  img {
    display: block;
    min-width: 100%;
    height: 200px;
    object-fit: cover;
  }
}
</style>
复制代码此时你会发现,每一行的图片都得到了很好的显示效果。但是唯独最后一行会出现一个小小的问题。
想象一下,假如最后一行只有一张图片的话,他会被缩放到充满一行,导致图片只会显示非常小的一部分内容。
所以,我们最后一行的图片不进行缩放处理即可。只需要添加以下css属性即可。
<style lang="scss" scoped>
.waterfall-height-css {
  &:after {
    content: ‘‘;
    display: block;
    flex-grow: 99999;
  }
}
</style>
复制代码因为flex-grow: 99999的值非常大,所以会把最后一行的剩余空间几乎全部占用,导致图片分配不了,只会按照原尺寸显示,就不会缩放占满一行啦。
优势:css 设置简单,渲染高效。
劣势:会损失图片的一部分可见区域。
到此,我们介绍了图片的显示特性以及如何利用 object-fit 进行内容的控制。
对于多图片的布局,要想比较合理的显示图片,瀑布流布局是非常好的选择,当然如果业务需求对图片的展示友好度及美观度不做要求,你大可利用 object-fit 控制内容即可。
但是我认为瀑布流布局也是我们应该掌握的内容之一,即便此时用不到,也可以先把文章收藏起来,以备不时之需,文中采用了多种方式的实现,你可以选择一种最贴合你需求的方式。
当然,案例中其实还有很多细节没有处理,比如浏览器窗口发上变化时重新加载图片会发生闪动该如何优化体验?小伙伴们不妨自己去尝试进行优化。动手实践是掌握技能的重要手段。

来源于:https://juejin.im/post/5d365a65e51d454d1d6285eb
作者:六小登登
链接:https://juejin.im/post/5d365a65e51d454d1d6285eb
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  

原文地址:https://www.cnblogs.com/hjptopshow/p/11231456.html

时间: 2024-10-08 15:08:49

详解瀑布流布局的5种实现方式及object-fit的相关文章

(转)详解LVS负载均衡之三种工作模型原理和10种调度算法

前言:最近在为我们的产品在做高可用,一边搭环境,一边了解相关知识,搜到这篇博客,质量不错,表述清晰,于是转载过来学习. 标签:详解LVS负载均衡之三种工作模型原理和10种调度算法 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://linuxnx.blog.51cto.com/6676498/1195379 LVS负载均衡原理和算法详解    Internet的快速增长使多媒体网络服务器面对的访问数量快速增加,服务器需要具备提供大

详解Android中那些酷炫返回方式的实现

Android手机都会有返回键,不管是实体键,还是虚拟键.Android用户主要也都是通过这个返回键操控页面返回方式的,不比IOS逼格甚高的只保留一个操作键.这种方式是最普遍的返回方式,还有一种也是比较常见的,那就是页面内部自己响应.绝大多数APP每个页面的设计图顶部左侧都会有一个返回键图标,偶尔也有奇葩的设计放在底部左侧,点击这个图标即finish掉当前页面.简单的介绍完了最常见的两种方式,下面为大家介绍两种更友好的交互方式. 拿大家比较常用的三款社交软件的交互来说.腾讯微博的返回方式除去上述

opencv cv.findContours 函数详解 图像轮廓层级 图像轮廓检索方式详解

函数 cv.findContours contours, hierarchy = cv.findContours( image, mode, method[, contours[, hierarchy[, offset]]] ) 参数1:源图像 参数2:轮廓的检索方式,这篇文章主要讲解这个参数 参数3:一般用 cv.CHAIN_APPROX_SIMPLE,就表示用尽可能少的像素点表示轮廓 contours:图像轮廓坐标,是一个链表 hierarchy:[Next, Previous, First

02JavaIO详解_IOl流的三种分类以及InputStream和OutputStream

IO流指的就是输入流和输出流.怎么定义输入和输出流呢,以程序为参考,流入到程序里就是输入流,从程序里面流出去就是输出流. 输入/输出流的介绍: 流有三种分类: 1.  从功能上分为:输入流和输出流. 2.从流结构上分为:字节流和字符流. 3.节点流和过滤流. 说明:上面提到了字节流和字符流.其实字符流就是以字节流为基础的,我们操纵String更加方便啊,所以有了字符流.字节流掌握好以后,字符流就很简单了. 字符流和字节流的说明: 字节流的基础是InputStream和OutputStream.我

详解 UWP 中的两种 HttpClient API

摘要: 本文为个人博客备份文章,原文地址:http://validvoid.net/demystifying-httpclient-apis-in-the-uwp/ 本文编译自微软 Building Apps for Windows 博客,原文地址:Demystifying HttpClient APIs in the Universal Windows Platform.本文原文由 Windows 网络 API 组的 Program Manager Sidharth Nabar 撰写. UWP

详解PHP中的八种数据类型

美元符号$是变量的标识符,所有变量都是以$符号开头的,无论是声明变量还是调用变量,都应使用$符号. PHP一共支持8种原始类型: 1.boolean (布尔型):取值只有 true 或 false:true和false是PHP的内部关键字. 通常布尔型变量都是应用在条件或循环语句的表达式中.在PHP中,不是只有false值才为假,在一些特殊情况下boolean值也被认为是false.这些特殊情况为:0.0.0."0".空白字符串("").只声明没有复制的数组等. 2

Java开发中的23种设计模式详解之二:7种结构型模式

我们接着讨论设计模式,上篇文章我讲完了5种创建型模式,这章开始,我将讲下7种结构型模式:适配器模式.装饰模式.代理模式.外观模式.桥接模式.组合模式.享元模式.其中对象的适配器模式是各种模式的起源,我们看下面的图: 适配器模式将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题.主要分为三类:类的适配器模式.对象的适配器模式.接口的适配器模式.首先,我们来看看类的适配器模式,先看类图: 核心思想就是:有一个Source类,拥有一个方法,待适配,目标接口时

java list详解及arrayList的四种遍历方法

1.List接口提供的适合于自身的常用方法均与索引有关,这是因为List集合为列表类型,以线性方式存储对象,可以通过对象的索引操作对象.   List接口的常用实现类有ArrayList和LinkedList,在使用List集合时,通常情况下声明为List类型,实例化时根据实际情况的需要,实例化为   ArrayList或LinkedList,例如:List<String> l = new ArrayList<String>();// 利用ArrayList类实例化List集合Li

详解Oracle临时表的几种用法及意义

Oracle临时表可以说是提高数据库处理性能的好方法,在没有必要存储时,只存储在Oracle临时表空间中.希望本文能对大家有所帮助. 1 .前言 目前所有使用 Oracle 作为数据库支撑平台的应用,大部分数据量比较庞大的系统,即表的数据量一般情况下都是在百万级以上的数据量. 当然在 Oracle 中创建分区是一种不错的选择,但是当你发现你的应用有多张表关联的时候,并且这些表大部分都是比较庞大,而你关联的时候发现其中的某一张或者某几张表关联之后得到的结果集非常小并且查询得到这个结果集的速度非常快