07-瀑布流布局
https://www.w3cschool.cn/article/48614293.html
https://blog.csdn.net/Jzsn_Paul/article/details/140169578
https://juejin.cn/post/7368855076130488339
https://developers.weixin.qq.com/community/develop/article/doc/00004a4ae7c7a8aaddca7dc4f56413
这个瀑布流布局真的坑很多很多,还是挺难的,网上绝大部分都有瑕疵,都不能用。
- 支持下一页加载
- 高度由里面的子元素(一般为图片)高撑开
- 需要考虑图片加载慢的问题
绝对定位实现
完美实现!!!
html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
*{margin: 0;padding: 0;}
.main{
border: 1px solid #c1c1c1;
width: 900px;
height: 100vh;
position: relative;
margin: 0 auto;
}
.item{
width: 150px;
float: left;
}
.auxiliary{ /* 很重要,用来设置子item的margin,曲线救国 */
background: #4CAF50;
margin: 5px;
}
.item-img {
}
img { /* 一定要加这个,不然item-img的高会比img的高多几个像素 */
display: block; /* 改为块级元素 */
vertical-align: top; /* 如果仍然希望保留行内特性 */
}
</style>
<script>
function getData(curSize){
let list = []
for (let i = 0; i < curSize; i++) {
list.push(`https://picsum.photos/200/${num(100, 300)}?v=` + Math.random())
}
return list;
}
let num = (min,max) => {
return Math.floor(Math.random() * max) + min
}
function parseHTML(htmlString) {
const parser = new DOMParser();
const doc = parser.parseFromString(htmlString, 'text/html');
return doc.body.firstChild; // 返回第一个子元素
}
function insertImgToPage(src){
let app = document.getElementById("app");
let htmlString = `<div class="item"><div class="auxiliary"><div class="item-img"><img style="width: 100%" src="${src}" alt=""></div></div></div>`
const domElement = parseHTML(htmlString);
app.appendChild(domElement);
return domElement;
}
let item_ok_list = [] //已经完成瀑布流布局的图片
let g_width = 150 //全局宽度
let column; //几列
function preDeal(parent){
let cParent =document.getElementById(parent);
let p_width =cParent.offsetWidth;
column =Math.floor(p_width/g_width);
}
window.onload = function (){
preDeal('app')
}
function add(){
// 1、获取数据
let list = getData(10);
// 2、获取图片url集合
let images = list;
// 3、判断那些图片加载OK,放到数组
let completeImgList = [] //加载好的图片
// 开始加载图片
images.forEach(src => {
const img = new Image();
// 监听图片加载完成事件
img.onload = function() {
console.log(src + ' loaded');
completeImgList.push(src);
// 4、将加载好的图片插入到页面
let item = insertImgToPage(src);
// 5、瀑布流布局
pubu('app',item)
};
img.onerror = function() {
console.error(src + ' failed to load');
};
img.src = src; // 开始加载图片
});
}
let boxHeightArr =[];
function pubu(parent,child){
if (item_ok_list.length < column){
boxHeightArr.push(child.offsetHeight);
}else{
let minHeight =Math.min(...boxHeightArr);
let minIndex =boxHeightArr.indexOf(minHeight);
child.style.position ='absolute';
child.style.top =`${minHeight}px`;
child.style.left =`${g_width * minIndex}px`;
boxHeightArr[minIndex]+=child.offsetHeight;
}
item_ok_list.push(child)
}
</script>
</head>
<body>
<button onclick="add()"> add </button>
<div id="app" class="main">
</div>
</body>
</html>
Vue3的写法
vue
<template>
<div>
<button @click="addImages">Add Images</button>
<div id="myPicture" class="main" ref="appRef"></div>
</div>
</template>
<script setup>
import { onMounted, ref, nextTick } from 'vue';
const appRef = ref(null);
//加载OK的全部照片集合
const item_ok_list = ref([]);
//当前查看的图片
const currentPic = ref('');
// 每列的高集合
const boxHeightArr = ref([]);
//容器ID
const parentId = "myPicture"
//固定宽
const g_width = 150;
//几列
let column;
function getData(curSize) {
let list = [];
for (let i = 0; i < curSize; i++) {
list.push(`https://picsum.photos/200/${num(100, 300)}?v=` + Math.random())
}
return list;
}
let num = (min,max) => {
return Math.floor(Math.random() * max) + min
}
function insertImgToPage(src) {
const htmlString = `
<div class="item">
<div class="auxiliary">
<div class="item-img">
<img style="width: 100%;" src="${src}" alt="" />
</div>
</div>
</div>
`;
const domElement = parseHTML(htmlString);
appRef.value.appendChild(domElement);
return domElement;
}
function parseHTML(htmlString) {
const parser = new DOMParser();
const doc = parser.parseFromString(htmlString, 'text/html');
return doc.body.firstChild;
}
function preDeal() {
const cParent = document.getElementById(parentId);
const p_width = cParent.offsetWidth;
column = Math.floor(p_width / g_width);
}
onMounted(() => {
preDeal()
});
function addImages() {
const list = getData(10);
list.forEach((src) => {
const img = new Image();
img.onload = () => {
console.log(`${src} loaded`);
const item = insertImgToPage(src);
pubu(item);
// 获取实际插入到 DOM 中的 img 元素,并绑定点击事件
const imgElement = item.querySelector('img');
bindClickEvent(imgElement);
};
img.onerror = () => {
console.error(`${src} failed to load`);
};
img.src = src;
});
}
function bindClickEvent(img) {
// 绑定 click 事件
img.addEventListener('click', function(event) {
currentPic.value = event.target.src
});
}
function pubu(child) {
if (item_ok_list.value.length < column) {
boxHeightArr.value.push(child.offsetHeight);
} else {
const minHeight = Math.min(...boxHeightArr.value);
const minIndex = boxHeightArr.value.indexOf(minHeight);
child.style.position = 'absolute';
child.style.top = `${minHeight}px`;
child.style.left = `${g_width * minIndex}px`;
boxHeightArr.value[minIndex] += child.offsetHeight;
}
item_ok_list.value.push(child);
}
</script>
<style>
#myPicture {
/*border: 1px solid #c1c1c1;*/
/* background: #4CAF50;*/
width: 900px;
min-height: 600px;
position: relative;
margin: 0 auto;
/*overflow: auto;*/
}
#myPicture .item {
width: 150px;
float: left;
}
#myPicture .auxiliary {
background: #4CAF50;
margin: 5px;
}
#myPicture .item-img {
display: block;
vertical-align: top;
}
#myPicture img {
display: block;
vertical-align: top;
}
/**隐藏滚动条*/
#myPicture::-webkit-scrollbar {
width: 0.5em;
}
#myPicture::-webkit-scrollbar-track {
background-color: transparent;
}
#myPicture::-webkit-scrollbar-thumb {
background-color: transparent;
}
</style>
grid实现
实现一
先看一个小demo,看似看不错,其实有很大的问题,这个高度不是动态的,是写死的。我们想要的是高度是由里面的子元素的高撑开的。
html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.main {
background: #e4e4e4;
display: grid;
width: 300px;
grid-template-columns: repeat(2, 1fr); /*指定两列,自动宽度*/
grid-gap: 1px; /*横向,纵向间隔*/
grid-auto-flow: row dense; /*是否自动补齐空白*/
grid-auto-rows: 20px; /*base高度,grid-row基于此运算*/
}
.main .item {
width: 100%;
background: #222;
color: #ddd;
}
.main .item:nth-of-type(3n+1) {
grid-row: auto / span 5;
}
.main .item:nth-of-type(3n+2) {
grid-row: auto / span 6;
}
.main .item:nth-of-type(3n+3) {
grid-row: auto / span 8;
}
</style>
</head>
<body>
<div class="main">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
<div class="item">6</div>
<div class="item">7</div>
<div class="item">8</div>
</div>
</body>
</html>
实现二
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Demo</title>
<style>
body {
margin: 0;
}
.masonry {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-gap: 0 60px;
grid-auto-rows: 2px;
align-items: end;
}
.item {
background: #f8f8fa;
display: flex;
justify-content: center;
align-items: center;
}
@media (min-width: 1280px) and (max-width: 1920px) {
.masonry {
grid-template-columns: repeat(3, 1fr);
}
}
@media (min-width: 768px) and (max-width: 1280px) {
.masonry {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 768px) {
.masonry {
grid-template-columns: repeat(1, 1fr);
}
}
</style>
</head>
<body>
<div class="masonry">
<div class="item">item1</div>
<div class="item">item2</div>
<div class="item">item3</div>
<div class="item">item4</div>
<div class="item">item5</div>
<div class="item">item6</div>
<div class="item">item7</div>
<div class="item">item8</div>
<div class="item">item9</div>
<div class="item">item10</div>
<div class="item">item11</div>
<div class="item">item12</div>
<div class="item">item13</div>
<div class="item">item14</div>
<div class="item">item15</div>
<div class="item">item16</div>
<div class="item">item17</div>
<div class="item">item18</div>
<div class="item">item19</div>
<div class="item">item20</div>
<div class="item">item21</div>
<div class="item">item22</div>
<div class="item">item23</div>
<div class="item">item24</div>
<div class="item">item25</div>
</div>
<script>
// 给每个元素模拟随机高度
window.addEventListener('load', () => {
document.querySelectorAll('.masonry > .item').forEach(item => {
item.style.height = `${Math.floor(Math.random() * 200) + 100}px`
})
})
const calcRows = () => {
const masonry = document.querySelector('.masonry')
const items = masonry.querySelectorAll('.item')
// 获取当前列数 这个API可以直接使用,grid专门提供的
const cols = getComputedStyle(masonry).gridTemplateColumns.split(" ").length;
items.forEach((item, index) => {
// 给需要上下间隔的元素增加上间隔(每列第一个元素无需上间隔)
const gapRows = index >= cols ? 8 : 0;
// 根据元素高度设置元素的需占行数
const rows = Math.ceil(item.clientHeight / 2) + gapRows;
item.style.gridRowEnd = `span ${rows}`;
})
}
window.addEventListener('resize', calcRows)
window.addEventListener('load', calcRows)
</script>
</body>
</html>
实现无限滚动
主要是利用了IntersectionObserver这个API的能力,检测目标dom是否出现在容器的视口中
描述: 定义了在目标元素与根相交时,触发回调的可见性阈值,范围在 0 到 1 之间。
threshold:
- 0 表示只要有任何部分可见,回调就会触发;
- 1 表示目标元素全部可见时才会触发。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Intersection Observer Demo</title>
<style>
#container {
height: 200px;
overflow: auto;
border: 1px solid #000;
}
#target {
height: 100px;
background-color: lightblue;
margin-top: 300px; /* initial position is outside of the viewport */
}
.spacer {
height: 500px; /* create space to scroll */
}
</style>
</head>
<body>
<div id="container">
<div class="spacer"></div>
<div id="target">Target Element</div>
</div>
<script>
const target = document.getElementById('target');
const container = document.getElementById('container');
const options = {
root: container, // 观察的上下文元素
rootMargin: '0px',
threshold: 0.1 // 目标元素在根元素中可见的百分比时触发
};
const callback = (entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// 元素出现在容器内,触发事件
console.log('Target is inside the container!');
// 这里可以执行更多的逻辑,比如调用其他函数等
// 如果你只希望触发一次事件,可以取消观察
observer.unobserve(entry.target);
}
});
};
const observer = new IntersectionObserver(callback, options);
observer.observe(target); // 开始观察目标元素
</script>
</body>
</html>