瀑布流布局
- 内容框宽度固定,高度不固定。
- 内容框从左到右排列,一行排满后,其余内容框就会按顺序排在短的一列后。
实现效果
实现思路
- 编写结构样式 三列 每一列中都有很多项 每一项的高度由图片的真实高度决定
- 发送ajax请求 从服务器获取数据
- 按照瀑布流的规则实现动态绑定 (每次拿出三条数据,把最小的图片插入到最高的列中)
- 为了优化页面的第一次渲染速度,我们最开始,并不会去加载真实的图片,当内容渲染完后,我们再加载真实的图片(图片的懒加载) 只有完全出现在可视窗口的图片 再去加载真实的图片
- 通过获取图片底部距离body父级参照物的上偏移(A) 和 获取页面底部距离父级参照物的偏移(B) A<=B 表示出现在了视口中
- 基于getBoundingClientRect优化懒加载条件
- IntersectionObserver ES6 新的监听 能够监听一个或者多个盒子和浏览器可视窗口的交叉信息
- 当页面滚动到底部,我们去加载更多的数据
HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>瀑布流</title>
<!-- IMPORT CSS -->
<link rel="stylesheet" href="css/reset.min.css">
<link rel="stylesheet" href="css/index.css">
</head>
<body>
<div class="container" id="fallsBox">
<!-- 第一列 -->
<div class="column">
<!-- <div class="item">
<a href="">
<div class="pic-box">
<img src="./images/10.jpg" alt="">
</div>
<p class="desc-box">
泰勒·斯威夫特(Taylor Swift),1989年12月13日出生于美国宾州,美国歌手、演员。2006年出道,同年发行专辑《泰勒·斯威夫特》,该专辑获得美国唱片业协会的白金唱片认证
</p>
</a>
</div> -->
</div>
<!-- 第二列 -->
<div class="column">
</div>
<!--第三列 -->
<div class="column">
</div>
</div>
<div class="loadMoreBox"></div>
<!-- IMPORT JS -->
<script src="js/index.js"></script>
</body>
</html>
CSS
编写css不成文小规定 1.控制显示和隐藏 2.控制位置 3.width/border/padding
html,body{
min-height: 100%;
background-color: rgb(219, 219, 219);
overflow-x: hidden;
}
.container{
box-sizing: border-box;
width: 760px;
margin: 20px auto;
/* 布局 */
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.container .column{
width: 240px;
}
.container .column .item{
margin-bottom: 10px;
padding: 5px;
background:#fff;
box-shadow: 3px 3px 10px 0 #222;
}
.container .column .item a{
display: block;
}
.container .column .item .pic-box{
background: #e6e5e5;
/* height: 50px; 动态添加高度*/
}
.container .column .item .pic-box img{
display: block;
width: 100%;
height: 100%; /*高度和宽度依靠父级盒子*/
opacity: 0; /*默认不显示*/
transition: opacity .3s ease;
}
/* .container .column .item .pic-box img[src=""] {
display: none;
} */
.container .column .item .dec-box{
margin-top: 10px;
color:black;
line-height: 20px;
}
/*更多加载的盒子*/
.loadMoreBox{
height: 50px;
background: transparent;
/* background-color: red; */
}
JS
①方案1 window.onscroll+用到了函数节流
- window.onscroll+用到了函数节流
- ONSCROLL事件 会在浏览器最快反应事件内触发一次 (谷歌:5~7MS IE10~17MS) 这样频率太快了
- 我们需要降低它的频率,【方案:函数的节流】
图片的懒加载思路
加载更多思路
let fallsModel = (function () {
// 获取元素
let fallsBox = document.querySelector('#fallsBox'),
columns = Array.from(document.querySelectorAll('.column')),
imgList = [];
let data = [];
// 工具类方法:offset 获取当前元素距离BODY的偏移(top/left)
const offset = function offset(element) {
let parent = element.offsetparent,
top = element.offsetTop,
left = element.offsetLeft;
while (parent) {
top += parent.clientTop + parent.offsetTop;
left += parent.clientLeft + parent.offsetLeft;
parent = parent.offsetparent;
}
return {
top,
left
}
};
// 工具类方法:函数节流处理
const throttle = function throttle(func, wait) {
if (typeof func !== "function") throw new TypeError('func must be an function');
if (typeof wait !== "number") wait = 300;
let timer,
previous = 0;
return function proxy(...params) {
let now = +new Date(),
remaining = wait - (now - previous),
self = this,
result;
if (remaining <= 0) {
if (timer) {
clearTimeout(timer);
timer = null;
}
result = func.call(self, ...params);
previous = now;
} else if (!timer) {
timer = setTimeout(() => {
if (timer) {
clearTimeout(timer);
timer = null;
}
result = func.call(self, ...params);
previous = +new Date();
}, remaining);
}
return result;
};
};
// 1.从服务器获取数据==========================================================
const queryData = function queryData() {
let xhr = new XMLHttpRequest();
xhr.open("GET", "./data.json", false);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
data = JSON.parse(xhr.responseText);
};
};
xhr.send();
};
// 2.动态渲染数据=====================================================================================
//瀑布流规则 每一次从数据中取出3条数据(group) 三列(colums)
//三条数据,按照图片高度排序[小,中,大]三列
//盒子,按照高度进行排序[大,中,小]
const binding = function binding() {
// 服务器获取到的图片宽度高度,按照真实渲染的宽度(230)动态计算出真实渲染的高度
// 服务器获取到的宽高 width:300 height:433 300/433 宽高比
// 真实渲染的宽度为width:230px 求真实渲染的高度? 高度为 230/300/433
// 用数组中的map方法
data = data.map((item) => {
let { width, height } = item;
item.width = 230;
item.height = 230 / (width / height);
return item;//在把更改完的元素返回
});
let i = 0, //如果不想生成块级上下文 则写在外面 不想产生闭包
group;
for (; i < data.length; i += 3) {
group = data.slice(i, i + 3); //从当前索引 取3项
//i=0 0 1 2
//i=3 3 4 5
group.sort((a, b) => {//按照图片高度 从小到大排序
return a.height - b.height;
});
columns.sort((a, b) => {//按照盒子的高度 从大到小排序
return b.offsetHeight - a.offsetHeight;
});
// 把排好序的数据依次插入到对应的排好序的列中
group.forEach((item, index) => {
let { pic, height, title, link } = item;
// 动态创建每一个图片盒子 插入到指定列
let box = document.createElement("div");
box.className = 'item';
box.innerHTML = `<a href="${link}" target="_blank">
<div class="pic-box" style="height:${height}px;">
<img src="" alt="" data-img="${pic}">
</div>
<p class="desc-box">
${title}
</p>
</a>`;
// 插入到指定列
columns[index].appendChild(box);
});
};
// 数据绑定完成后,获取到所有的IMG
imgList = Array.from(fallsBox.querySelectorAll('.pic-box img'));
};
// 3.图片的延迟加载============================================================
// +单张图片
const singleImgLazy = function singleImgLazy(img) {
// 传递的Img就是需要加载的图片对象
let pic = img.getAttribute('data-img'),
temp = new Image();//=>document.createElement('img')
temp.src = pic;
temp.onload = () => {
//加载成功:图片地址是正确的
img.src = pic;
img.style.opacity = 1; //更改透明度
};
// 处理过的IMG设置已经处理过的标识
img.setAttribute('isLoad', 'TRUE');
};
// 循环图片集合 进行懒加载
const lazyImgsLoading = function lazyImgsLoading() {
//ONSCROLL事件 会在浏览器最快反应事件内触发一次 (谷歌:5~7MS IE10~17MS) 这样频率太快了
// +我们需要降低它的频率,【方案:函数的节流】
imgList.forEach(item => {
// 已经处理过的IMG就无需判断了
if (item.getAttribute('isLoad')) return;
// item 当前迭代图片
let imgBox = item.parentNode, //父元素盒子
HTML = document.documentElement;
A = imgBox.offsetHeight + offset(imgBox).top;//图片盒子距离Body的上偏移
B = HTML.clientHeight + HTML.scrollTop;//窗口视口距离Body的上偏移
if (A <= B) {
singleImgLazy(item); //加载图片
};
});
};
//4.加载更多数据======================
let count=0;
const loadMoreData=function loadMoreData(){
let HTML=document.documentElement;
// HTML.scrollTop+HTML.clientHeight+100 =>卷去的高度+可视窗口高度+100(误差)
// HTML.scrollHeight页面真实的高度约等于
if(HTML.scrollTop+HTML.clientHeight+100>=HTML.scrollHeight){
// 滚动到底部了
count++;
if(count>5)return; //模拟加载5次,数据加载完不在进行处理
queryData();
binding();
lazyImgsLoading();
}
};
return {
init() {
queryData();//获取数据
binding();//动态绑定数据 按照瀑布流规则
lazyImgsLoading();//延迟加载
window.onscroll =throttle(()=>{
lazyImgsLoading();
loadMoreData();
},500); //滚动的时候 500MS
}
};
})();
fallsModel.init();
②方案2 基于getBoundingClientRect 优化懒加载条件
// 基于getBoundingClientRect 优化懒加载条件==============================
let fallsModel = (function () {
// 获取元素
let fallsBox = document.querySelector('#fallsBox'),
columns = Array.from(document.querySelectorAll('.column')),
imgList = [];
let data = [];
// 工具类方法:函数节流处理
const throttle = function throttle(func, wait) {
if (typeof func !== "function") throw new TypeError('func must be an function');
if (typeof wait !== "number") wait = 300;
let timer,
previous = 0;
return function proxy(...params) {
let now = +new Date(),
remaining = wait - (now - previous),
self = this,
result;
if (remaining <= 0) {
if (timer) {
clearTimeout(timer);
timer = null;
}
result = func.call(self, ...params);
previous = now;
} else if (!timer) {
timer = setTimeout(() => {
if (timer) {
clearTimeout(timer);
timer = null;
}
result = func.call(self, ...params);
previous = +new Date();
}, remaining);
}
return result;
};
};
// 1.从服务器获取数据==========================================================
const queryData = function queryData() {
let xhr = new XMLHttpRequest();
xhr.open('GET', './data.json', false);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
data = JSON.parse(xhr.responseText);
};
};
xhr.send();
};
// 2.动态渲染数据=====================================================================================
//瀑布流规则 每一次从数据中取出3条数据(group) 三列(colums)
//三条数据,按照图片高度排序[小,中,大]三列
//盒子,按照高度进行排序[大,中,小]
const binding = function binding() {
// 服务器获取到的图片宽度高度,按照真实渲染的宽度(230)动态计算出真实渲染的高度
// 服务器获取到的宽高 width:300 height:433 300/433 宽高比
// 真实渲染的宽度为width:230px 求真实渲染的高度? 高度为 230/300/433
// 用数组中的map方法
data = data.map((item) => {
let { width, height } = item;
item.width = 230;
item.height = 230 / (width / height);
return item;//在把更改完的元素返回
});
let i = 0, //如果不想生成块级上下文 则写在外面 不想产生闭包
group;
for (; i < data.length; i += 3) {
group = data.slice(i, i + 3); //从当前索引 取3项
//i=0 0 1 2
//i=3 3 4 5
group.sort((a, b) => {//按照图片高度 从小到大排序
return a.height - b.height;
});
columns.sort((a, b) => {//按照盒子的高度 从大到小排序
return b.offsetHeight - a.offsetHeight;
});
// 把排好序的数据依次插入到对应的排好序的列中
group.forEach((item, index) => {
let { pic, height, title, link } = item;
// 动态创建每一个图片盒子 插入到指定列
let box = document.createElement('div');
box.className = 'item';
box.innerHTML = `<a href="${link}" target="_blank">
<div class="pic-box" style="height:${height}px;">
<img src="" alt="" data-img="${pic}">
</div>
<p class="desc-box">
${title}
</p>
</a>`;
// 插入到指定列
columns[index].appendChild(box);
});
};
// 数据绑定完成后,获取到所有的IMG
imgList = Array.from(fallsBox.querySelectorAll('.pic-box img'));
};
// 3.图片的延迟加载============================================================
// +单张图片
const singleImgLazy = function singleImgLazy(img) {
// 传递的Img就是需要加载的图片对象
let pic = img.getAttribute('data-img'),
temp = new Image();//=>document.createElement('img')
temp.src = pic;
temp.onload = () => {
//加载成功:图片地址是正确的
img.src = pic;
img.style.opacity = 1; //更改透明度
};
// 处理过的IMG设置已经处理过的标识
img.setAttribute('isLoad', 'TRUE');
};
// 循环图片集合
const lazyImgsLoading = function lazyImgsLoading() {
//ONSCROLL事件 会在浏览器最快反应事件内触发一次 (谷歌:5~7MS IE10~17MS) 这样频率太快了
// +我们需要降低它的频率,【方案:函数的节流】
imgList.forEach(item => {
// 已经处理过的IMG就无需判断了
if (item.getAttribute('isLoad')) return;
// item 当前迭代图片
let imgBox = item.parentNode, //父元素盒子
HTML = document.documentElement,
// 基于getBoundingClientRect 优化懒加载条件
A = imgBox.getBoundingClientRect().bottom,
B = HTML.clientHeight;
if (A <= B) {
singleImgLazy(item); //加载图片
};
});
};
// 加载更多数据======================
let count = 0;
const loadMoreData = function loadMoreData() {
let HTML = document.documentElement;
// HTML.scrollTop+HTML.clientHeight+100 =>卷去的高度+可视窗口高度+100(误差)
// HTML.scrollHeight页面真实的高度约等于
if (HTML.scrollTop + HTML.clientHeight + 100 >= HTML.scrollHeight) {
// 滚动到底部了
count++;
if (count > 5) return; //模拟加载5次,数据加载完不在进行处理
queryData();
binding();
lazyImgsLoading();
}
};
return {
init() {
queryData();
binding();
lazyImgsLoading();
window.onscroll = throttle(() => {
lazyImgsLoading();
loadMoreData();
}, 500); //滚动的时候 500MS
}
};
})();
fallsModel.init();
③最终版本 使用监听器IntersectionObserver
新版加载更多思路
// 瀑布流的最终效果==================================================
let fallsModel = (function () {
// 获取元素
let fallsBox = document.querySelector('#fallsBox'),
columns = Array.from(document.querySelectorAll('.column')),
imgList = [],
loadMoreBox=document.querySelector('.loadMoreBox');
let data ;
// IntersectionObserver 不兼容IE浏览器
// 创建监听器 用来监听图片盒子和可视窗口的交叉状态
let ob=new IntersectionObserver(changes=>{
// 循环每个盒子监听到的交叉盒子,把出现在视口中的盒子做延迟加载
changes.forEach(change=>{
let{isIntersecting,target}=change;
// target监听的盒子
//isIntersecting 为 true 则是显示在了视口中
// console.log(change);
if(isIntersecting){
// 当前盒子已经出现在视口中 target监听的盒子
singleImgLazy(target.querySelector('img'));
// 处理后 ,这个盒子就没必要再监听了
ob.unobserve(target);
}
});
},{
// 控制完全出现在视口中 才算交叉状态改变
threshold:[1]
});
// 1.从服务器获取数据==========================================================
const queryData = function queryData() {
let xhr = new XMLHttpRequest();
xhr.open('GET', './data.json', false);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
data = JSON.parse(xhr.responseText);
};
};
xhr.send();
};
// 2.动态渲染数据=====================================================================================
//瀑布流规则 每一次从数据中取出3条数据(group) 三列(colums)
//三条数据,按照图片高度排序[小,中,大]三列
//盒子,按照高度进行排序[大,中,小]
const binding = function binding() {
// 服务器获取到的图片宽度高度,按照真实渲染的宽度(230)动态计算出真实渲染的高度
// 服务器获取到的宽高 width:300 height:433 300/433 宽高比
// 真实渲染的宽度为width:230px 求真实渲染的高度? 高度为 230/300/433
// 用数组中的map方法
data = data.map((item) => {
let { width, height } = item;
item.width = 230;
item.height = 230 / (width / height);
return item;//在把更改完的元素返回
});
let i = 0, //如果不想生成块级上下文 则写在外面 不想产生闭包
group;
for (; i < data.length; i += 3) {
group = data.slice(i, i + 3); //从当前索引 取3项
//i=0 0 1 2
//i=3 3 4 5
group.sort((a, b) => {//按照图片高度 从小到大排序
return a.height - b.height;
});
columns.sort((a, b) => {//按照盒子的高度 从大到小排序
return b.offsetHeight - a.offsetHeight;
});
// 把排好序的数据依次插入到对应的排好序的列中
group.forEach((item, index) => {
let { pic, height, title, link } = item;
// 动态创建每一个图片盒子 插入到指定列
let box = document.createElement("div");
box.className = 'item';
box.innerHTML = `<a href="${link}" target="_blank">
<div class="pic-box" style="height:${height}px;">
<img src="" alt="" data-img="${pic}">
</div>
<p class="desc-box">
${title}
</p>
</a>`;
// 插入到指定列
columns[index].appendChild(box);
});
};
// 数据绑定完成后,获取到所有的IMG
imgList = Array.from(fallsBox.querySelectorAll('.pic-box img'));
};
// 3.图片的延迟加载============================================================
// +单张图片
const singleImgLazy = function singleImgLazy(img) {
// 传递的Img就是需要加载的图片对象
let pic = img.getAttribute('data-img'),
temp = new Image();//=>document.createElement('img')
temp.src = pic;
temp.onload = () => {
//加载成功:图片地址是正确的
img.src = pic;
img.style.opacity = 1; //更改透明度
};
// 处理过的IMG设置已经处理过的标识
img.setAttribute('isLoad', 'TRUE');
};
// 循环图片集合
const lazyImgsLoading = function lazyImgsLoading() {
//ONSCROLL事件 会在浏览器最快反应事件内触发一次 (谷歌:5~7MS IE10~17MS) 这样频率太快了
// +我们需要降低它的频率,【方案:函数的节流】
imgList.forEach(item => {
// 已经处理过的IMG就无需判断了
if (item.getAttribute('isLoad')) return;
//把没有处理过的图片,它所在的盒子交给监听器去监听
ob.observe(item.parentNode);
});
};
// 加载更多数据======================
const loadMoreData = function loadMoreData() {
let count = 0;
let ob2=new IntersectionObserver(changes=>{
let change=changes[0];
if(change.isIntersecting){
count++;
if(count>5){
// 已经加载完数据了
ob2.unobserve(loadMoreBox);
return;
}
queryData();
binding();
lazyImgsLoading();
}
});
ob2.observe(loadMoreBox);
};
return {
init() {
queryData();
binding();
lazyImgsLoading(); //延迟加载
loadMoreData();
}
};
})();
fallsModel.init();
jQuery方案
let fallModel=(function(){
let $fallsBox=$('#fallsBox'),
$colums=$fallsBox.children('.column'),
$loadMoreBox=$('.loadMoreBox'),
$imglist=null,
data=[];
// 获取数据
const queryData=function queryData(){
$.ajax({
url:'./data.json',
method:"GET", //method和type一样 之前的是 type
async:false,
dataType:'json',
success(result){
data=result;
}
});
};
// 渲染数据
const binding=function binding(){
data=data.map(item=>{
let {width,height}=item;
item.width=230;
item.height=230/(width/height);
return item;
});
let i=0,
group;
for(;i<data.length;i+=3){
group=data.slice(i,i+3);
if(i>0){
// 从第二次开始,再把数据和列排序
group.sort((a,b)=>a.height-b.height);
// $colums类数组 jQuery=>sort 不改变原始数组
$colums=$colums.sort((a,b)=>b.offsetHeight-a.offsetHeight);
}
group.forEach((item,index)=>{
let {pic,height,title,link}=item;
$(`<div class="item">
<a href="${link}" target="_blank">
<div class="pic-box" style="height:${height}px">
<img src="" alt="" data-img="${pic}">
</div>
<p class="desc-box">
${title};
</p>
</a>
</div>`).appendTo($colums[index]); //子元素插入到父元素
});
};
// 获取IMG 进行监听
$imglist=$fallsBox.find('img[data-img]');
console.log($imglist);
// item 原生DOM对象
$imglist.each((_,item)=>ob.observe(item.parentNode));
};
//图片延迟加载
let config={
threshold:[1]
};
let ob=new IntersectionObserver(changes=>{
changes.forEach(item=>{
let {isIntersecting,target}=item;
if(isIntersecting){
singleImgLazy($(target).children('img'));
ob.unobserve(target);
}
});
},config);
// 单张延迟加载
const singleImgLazy=function singleImgLazy($img){
let pic=$img.attr('data-img'),
temp=new Image;
temp.src=pic;
temp.onload=()=>{
$img.attr('src',pic);
$img.css({
opacity:1
});
};
$img.removeAttr('data-img');
};
// 加载更多
const loadMoreData=function loadMoreData(){
let count=0;
let ob=new IntersectionObserver(changes=>{
let change=changes[0];
if(change.isIntersecting){
count++;
if(count>5){
ob.unobserve($loadMoreBox[0]);
return;
}
queryData();
binding();
}
});
ob.observe($loadMoreBox[0]);
};
return{
init(){
queryData();
binding();
loadMoreData();
}
};
})();
fallModel.init();