当前位置: 首页 > news >正文

各种方法实现瀑布流

瀑布流又称瀑布流式布局,是比较流行的一种网站页面布局方式。即多行等宽元素排列,后面的元素依次添加到其后,等宽不等高,根据图片原比例缩放直至宽度达到我们的要求,依次按照规则放入指定位置。

相信大家在日常生活中看到很多次这种不规则瀑布流,我最近在给别人写网站的时候也遇到了这个需求,觉得解决问题的过程还是挺有意思的,所以就写下这篇文章。

比如 堆糖 网站 

网格布局 grid 布局解决了 flex 布局下多个元素最后一排会出现的问题

如果你用 grid 布局想实现这个这个效果是不行的,网格布局顾名思义他看起来就是像一个一个的表格,尽管你的元素高度不同,但是他的每一行起始位置还是相同的。 

  • 首先放上我的 vue 3 代码中的 HTML 部分
<template><div id="bigBox"><div class="waterfallBox" v-for="(item, index) in 20"v-bind:style="{ backgroundColor: getBackgroundColor(), height: getHeight() }">{{ index }}</div></div>
</template>
  •  其中用到了俩个方法也放下面了
const getBackgroundColor = () => {return `rgb(${Math.trunc(Math.random() * 255)},${Math.trunc(Math.random() * 255)},${Math.trunc(Math.random() * 255)})`
}const getHeight = () => {return Math.trunc(Math.random() * 300 + 50) + 'px'
}
  • 初始css
#bigBox {width: 100%;max-width: 1200px;margin: 20px auto;background-color: cyan;border: 4px dashed gray;.waterfallBox {display: flex;justify-content: center;align-items: center;color: white;font-weight: bold;font-size: 20px;}
}

 

纯CSS实现

flex 实现

以下代码是利用了 order 属性来控制显示的方位,但是这个方法非常不稳定。

在flex 布局中, order属性值来调整 flex 元素在 flex 容器中的排序顺序

order 属性也是我接触到这个瀑布流才知道的一个好东西,有时候我们做自适应布局时,有可能电脑端和手机端布局不一致,电脑端是图片左文字右,手机端可能就变成 文字上图片下,这个时候就可以巧借 order 属性,控制显示的顺序。

代码

#bigBox {display:flex;flex-direction: column;flex-wrap: wrap;// 需要提前计算高度,否则会出问题height: 600px;.waterfallBox{position: relative;margin-bottom: 20px;width: 30%;&::after{position: absolute;}}.waterfallBox:nth-child(3n+1) {order: 1;}.waterfallBox:nth-child(3n+2) {order: 2;}.waterfallBox:nth-child(3n) {order: 3;}
}

运气好的时候:

运气不好的时候

你会发现顺序全部乱套了/(ㄒoㄒ)/~~

利用 column-count

CSS column-count 属性是一个非常有用的CSS特性,它允许开发者将元素的内容分割成指定数量的列,从而创建多栏布局。这个属性接受一个正整数作为值,该值指定了元素内容应该被划分的理想列数。如果同时设置了 column-width 属性且其值不为 auto,则 column-count 仅表示允许的最大列数。

这个东西广泛使用于 报纸的布局,因为文字是需要这样现排版的

代码:(非常 nice 只有几行代码)

#bigBox{column-count: 3;column-gap: 20px;.waterfallBox{margin-bottom: 20px;/* 防止多列布局,分页媒体和多区域上下文中的意外中断 */break-inside: avoid;}
}

结果 (但是你发现了吗,这个地方的排放是以列来排放的)  前面有说过,广泛用于报纸文字的排版,报纸里面的文字也是这样一列一列来的

 因为他会计算,然后来分配每个列所占用的高度去填充盒子

这个方法适用于:不在意元素位置的排列

我一直在这俩个方法中徘徊,因为我想省事,能不写 js 的地方就绝对不写,查阅了很多资料,明白了 css 实现真正的瀑布流是不可能的,多多少少都会有问题,在真实的情况下,还是需要 js 来写会更好些,因为真实情况是 这些瀑布流一般都是动态加载的,而这个也需要 js 代码支撑

但是由于翻阅了很多资料的缘故——也逐渐知道了如何用 js 代码实现这个

但是由于我急于交付项目(本来想自己写,但是总是出问题,感觉可能还是心态没放平,因为这个问题已经困扰我好几天了)

于是我使用了别人写好的 js 代码,这个是真的香,程序员大佬中有句话:能使用别人已经写好的,就尽量不要自己写。

我使用的是,结合 grid 布局(因为我这边还需要做一个响应式设计)

https://masonry.desandro.com/

还有另外一个,没用过,但是感觉也不错

http://macyjs.com/

过了很久以后,才开始写了这个博客,写的时候就还是想挑战自己,于是还是手写了 js 代码,感觉没有那么难了,可能是心态放平了

实现思路:

设置最外层的 box 为 相对定位,里面的 所有子元素都使用 绝对定位 (子绝父相),然后通过计算每次都选取最小的高度,来作为 top 值,不断刷新这个 最小高度的数组,left 值可以一开始就计算好,通过 减去 gap 的值来平均分剩下的空间作为 每个子元素所占用 的大小。

附上:

<script lang="ts" setup>
import { onMounted } from "vue";const getBackgroundColor = () => {return `rgb(${Math.trunc(Math.random() * 255)},${Math.trunc(Math.random() * 255)},${Math.trunc(Math.random() * 255)})`
}const getHeight = () => {return Math.trunc(Math.random() * 300 + 50) + 'px'
}const columns = 3;
const gap = 20;let minArray: number[] = []
let maxHeight: number = 0// 获取最小元素的下标
const getMinIndex = () => {let minHeight = Number.MAX_SAFE_INTEGER;let pos = 0;minArray.forEach((item, index) => {pos = item < minHeight ? index : posminHeight = Math.min(item, minHeight)})return pos
}// 3.自己手写
const init = () => {const waterfallList = document.querySelectorAll('.waterfallBox') as NodeListOf<HTMLElement>;minArray = new Array(columns).fill(0)// 计算除去gap,物品的宽度const bigBoxNode = <HTMLDivElement>document.querySelector('#bigBox')const bigBox = bigBoxNode.clientWidthconst boxWidth = Math.trunc((bigBox - ((minArray.length - 1) * gap)) / columns)console.log(boxWidth)const left = minArray.map((item, index) => {return index * (boxWidth + gap)})console.log(left)waterfallList.forEach((item, index) => {// console.log(item.clientHeight, item.clientWidth)// 获取高度let currentPos = getMinIndex()console.log(currentPos)item.style.width = boxWidth + 'px'item.style.left = left[currentPos] + 'px'item.style.top = minArray[currentPos] + (minArray[currentPos] === 0 ? 0 : gap) + 'px'minArray[currentPos] += (minArray[currentPos] === 0 ? 0 : gap) + item.clientHeight;maxHeight = Math.max(...minArray)})bigBoxNode.style.height = maxHeight + gap + 'px';
}window.addEventListener('resize', init)onMounted(() => {init()
})
</script>

最后,想说的就完了,为什么要通过元素选择器来实现(我希望有些问题,我们能够脱离框架也能解决这个问题,而不是脱离了框架就不知道该如何使用)

附上完整代码:

<template><div id="bigBox"><div class="waterfallBox" v-for="(item, index) in 10"v-bind:style="{ backgroundColor: getBackgroundColor(), height: getHeight() }">{{ index }}</div></div>
</template><script lang="ts" setup>
import { onMounted } from "vue";const getBackgroundColor = () => {return `rgb(${Math.trunc(Math.random() * 255)},${Math.trunc(Math.random() * 255)},${Math.trunc(Math.random() * 255)})`
}const getHeight = () => {return Math.trunc(Math.random() * 300 + 50) + 'px'
}const columns = 3;
const gap = 20;let minArray: number[] = []
let maxHeight: number = 0// 获取最小元素的下标
const getMinIndex = () => {let minHeight = Number.MAX_SAFE_INTEGER;let pos = 0;minArray.forEach((item, index) => {pos = item < minHeight ? index : posminHeight = Math.min(item, minHeight)})return pos
}// 3.自己手写
const init = () => {const waterfallList = document.querySelectorAll('.waterfallBox') as NodeListOf<HTMLElement>;minArray = new Array(columns).fill(0)// 计算除去gap,物品的宽度const bigBoxNode = <HTMLDivElement>document.querySelector('#bigBox')const bigBox = bigBoxNode.clientWidthconst boxWidth = Math.trunc((bigBox - ((minArray.length - 1) * gap)) / columns)console.log(boxWidth)const left = minArray.map((item, index) => {return index * (boxWidth + gap)})console.log(left)waterfallList.forEach((item, index) => {// console.log(item.clientHeight, item.clientWidth)// 获取高度let currentPos = getMinIndex()console.log(currentPos)item.style.width = boxWidth + 'px'item.style.left = left[currentPos] + 'px'item.style.top = minArray[currentPos] + (minArray[currentPos] === 0 ? 0 : gap) + 'px'minArray[currentPos] += (minArray[currentPos] === 0 ? 0 : gap) + item.clientHeight;maxHeight = Math.max(...minArray)})bigBoxNode.style.height = maxHeight + gap + 'px';
}window.addEventListener('resize', init)onMounted(() => {init()
})
</script><style lang="scss" scoped>
#bigBox {width: 100%;max-width: 1200px;margin: 20px auto;background-color: cyan;border: 4px dashed gray;.waterfallBox {display: flex;justify-content: center;align-items: center;color: white;font-weight: bold;font-size: 20px;}
}// 1. 使用 flex 布局
// #bigBox {
//     display:flex;
//     flex-direction: column;
//     flex-wrap: wrap;
//     // 需要提前计算高度,否则会出问题
//     height: 600px;//     .waterfallBox{
//         position: relative;
//         margin-bottom: 20px;
//         width: 30%;//         &::after{
//             position: absolute;
//         }
//     }//     .waterfallBox:nth-child(3n+1) {
//         order: 1;
//     }//     .waterfallBox:nth-child(3n+2) {
//         order: 2;
//     }//     .waterfallBox:nth-child(3n) {
//         order: 3;
//     }
// }// 2.使用 column
#bigBox{column-count: 3;column-gap: 20px;.waterfallBox{margin-bottom: 20px;/* 防止多列布局,分页媒体和多区域上下文中的意外中断 */break-inside: avoid;}
}// 3.自己写
// #bigBox {
//     position: relative;//     .waterfallBox {
//         position: absolute;
//         left: 0;
//         top: 0;
//     }
// }// 4. 插件
// http://macyjs.com/
// https://masonry.desandro.com/
</style>


http://www.mrgr.cn/news/64876.html

相关文章:

  • 微信小程序 https://pcapi-xiaotuxian-front-devtest.itheima.net 不在以下 request 合法域名
  • 【自用】fastapi学习记录--请求和参数
  • 信道容量的概念推论
  • ESP8266 自定义固件烧录-Tcpsocket固件
  • FPGA时序分析里tsu、th、tco、tpd、tcd是什么?
  • Unity XR Interaction Toolkit 开发教程(2):导入 SDK【3.0 以上版本】
  • 026集——CAD动态效果—瞬态实现——vs CAD二次开发
  • 力扣题目解析--罗马数字转整型
  • Linux入门(2)
  • 手机App防沉迷系统
  • ValueError: images do not match
  • GB/T 28046.3-2011 道路车辆 电气及电子设备的环境条件和试验 第3部分:机械负荷(3)
  • 14. 数据的输入输出
  • 第六十三周周报 GGNN
  • scIDST:弱监督学习推断单细胞转录组数据中的疾病进展阶段
  • 数据结构与算法(1)
  • 从一到无穷大 #40:DB AI 融合
  • 第三次RHCSA作业
  • Java入门 (6) -- 动态编程的奥秘
  • 视频QoE测量学习笔记(二)
  • masm汇编字符输入输出演示
  • 20221403郑骁恒-TLCP 协议验证
  • IDEA控制台日志乱码问题
  • 我主编的电子技术实验手册(22)——RC并联电路
  • jsweb2
  • 从“被围剿”到“满堂彩”,3A游戏让中国游戏人挺直脊背