Ajax + 懒加载 + 预加载 实现的瀑布流
首先, 效果如下
同时在挂一个github链接,里面有一些自己写的demo, 也有很多素材,可以选择性打开选择(如果不需要的话可以忽略这条)我的github该项目地址
话不多说, 既然要写出这个简单的小demo, 同时掌握ajax + 懒加载 + 预加载三样虽然简单但却特别常用的技术, 我们还得追根溯源拆分成三大模块说起(如果对这三块知识了解比较好, 有时间可以看看下面的内容, 也给出朋友你的一份建议, 如果没有时间可以直接跳到最下方看demo操作》)
懒加载和预加载
懒加载
先来说说懒加载,我们来看看官方定义
一般来说官方的话说的都不是给新手看的,我个人的理解懒加载就是惰性加载, 比如淘宝首页有几百上千张图片, 当我们打开淘宝页面的时候我们要一次性把这几百几千张图片都加载过来吗, 当然不用, 因为有的用户根本就不会滑下去看那下面的那么多图片, 大多数人打开淘宝就是搜索, 但是我们如果一次性加载完毕的话首页是不是很难打开, 于是乎前辈们想到了先加载首屏, 也就是打开淘宝不滑动鼠标滚轮的前提下大家能看到的第一版内容, 而剩下的内容当用户有需求并且华东鼠标滑到那一块之前一点点距离再进行加载, 称之为懒加载
说到底懒加载就是优化页面的一种方案, 只要我们按需加载那么首屏加载一定会很快, 不会造成过多的网络请求的负担
举个例子如下, 我在body中设置有两个盒子, 一个为首屏盒子是蓝色, 一个为box盒子为白色, box盒子中有一个loading-box是我们将要用来做懒加载的盒子, 我用一个颜色的变化来表示盒子中的图片的加载
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>懒加载</title>
<style>
body {
margin: 0;
background-color: #eeeeee;
}
.first-screen-content {
width: 1190px;
height: calc(100vh - 40px);
margin: 40px auto 0;
background-color: rgba(30, 144, 255, 0.5);
color: #fff;
font-size: 48px;
text-align: center;
line-height: 800px;
}
.box {
position: relative;
width: 1190px;
margin: 80px auto 40px;
height: 800px;
background-color: #fff;
font-size: 48px;
text-align: center;
line-height: 800px;
}
.loading-box {
width: 100px;
height: 100px;
background-color: rgba(255, 215, 0, 0.1);
position: absolute;
bottom: 0;
left: 50%;
transform: translate(-50%);
transition: opacity 2s linear;
font-size: 10px;
color: #000;
line-height: 24px;
}
</style>
</head>
<body>
<div class="first-screen-content">我是首屏内容</div>
<div class="box">
我这里面有很多的图片和文字
<div class="loading-box"></div>
</div>
</body>
</html>
目前效果如下
透明金色的小方块在我滑下去的时候依然是透明的, 我们可以理解为这个金色小方块并没有加载出来, 或者可以理解为根本没有这个元素, 我因为显示直观一点, 给了透明度, 假设这个小方块有1个G那么大(说的比较夸张哈哈), 那么这个金色小方块没有加载出来的话, 那我们加载页面的速度就会快上不少, 因为我们至少不用去加载这一个G的东西, 为什么要这么做呢, 因为大部分用户打开网页甚至不会滚动往下看, 在电商类网页中大多都是这样, 用户都不会下去看我还加载他, 不是吃饱了撑的 用户也觉得难受, 打开个网页这么慢, 90%的时间都用在加载这个小方块的身上了, 现在好了我们不用加载, 当用户滑下去想看这一块我们再加载这一块也不迟
当用户滑到接近那一块的时候, 我们需要进行加载那个金色小方块, 将他从0.3透明度变换成1透明度表示里面的数据加载出来, JS代码如下
(function(){
const loadingBox = document.querySelector('.loading-box');
// 入口函数
const init = () => {
bindEvent();
}
// 绑定事件
const bindEvent = () => {
// 监听鼠标滚轮事件
window.onscroll = () => {
// 获取到滚轮滚动的距离
let scrollTop = document.documentElement.scrollTop;
// 获取到窗口的高度
let clientHeight = document.documentElement.clientHeight;
console.log(scrollTop, clientHeight);
// 当我们scrollTop > clientHeight的时候我们就开始加载金色小方块
// demo中这样, 在项目中肯定是需要更加精确的计算
(scrollTop > clientHeight) && loadingImage();
}
loadingBox.addEventListener('transitionend', () => {
console.log(this);
loadingBox.innerHTML = '<span>经过懒加载, 我这个1G的东西加载出来啦</span>'
}, false)
}
// 加载图片函数
const loadingImage = () => {
console.log(loadingBox)
loadingBox.style.opacity = '1';
}
init();
})()
此时效果如下,我们也就完成了懒加载
懒加载又叫按需加载,记住啦
预加载
我们再来看看预加载的官方定义
我个人对于预加载的理解为, 当一张图片在加载过程中, 如果网速过慢, 他会出现一部分一部分的加载并出现在页面中, 这样对用户体验并不是很好,会显得页面很卡顿, 于是前辈们推出预加载机制, 也就是在图片未完全加载完成之前我们并不将他展示进页面, 等到他完全加载完毕以后我们再将其放进页面元素中
我们先来看看没有进行预加载的页面和进行预加载的页面分别是什么场景, 这将帮助你更好的理解预加载的作用
- 没有进行预加载的图片,即使我网速足够快, 亚马逊在进入钻石系列电动牙刷的时候主要区域的橙色图片也是一部分一部分加载出来, 如果遇到网速特别慢的时候会相当的膈应人啊
- 进行预加载的案例我就自己写了一个, 也是更好为大家理解预加载的写法和原理
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>懒加载</title>
<style>
body {
margin: 0;
background-color: #eeeeee;
}
.box {
position: relative;
width: 1190px;
margin: 80px auto 40px;
height: 800px;
background-color: #fff;
font-size: 48px;
text-align: center;
line-height: 800px;
}
</style>
</head>
<body>
<div class="box">
<!-- 我们要在这个盒子里面插入一张图片 -->
</div>
<script>
(function() {
// 要进行预加载的图片
const readyImage = {
src: 'http://img3.imgtn.bdimg.com/it/u=3870619358,3104020013&fm=26&gp=0.jpg',
width: '500',
height: '428' // 一般如果有预加载操作, 后台会给宽高,
}
// 页面入口函数
const init = () => {
createImage(readyImage);
}
// 预加载方法, 该方法接收三个参数
// imageDOM: 加载完成的图片元素
// proxyDOM: 在图片加载好之前的替代元素
// parentDOM: 图片元素要插入的父级
const loadingReady = (imageDOM, proxyDOM, parentDOM) => {
console.log(imageDOM);
console.log(document.querySelector('.box'))
parentDOM.appendChild(proxyDOM);
imageDOM.onload = () => {
// 当图片加载完成以后我们要把图片元素替换掉之前的遮罩元素
setTimeout(() => {
parentDOM.replaceChild(imageDOM, proxyDOM);
}, 1000)
}
}
// 创建图片
const createImage = (src) => {
const image = new Image(); //创建一个image元素
image.src = readyImage.src;
const mask = document.createElement('div');
mask.style.width = readyImage.width + 'px';
mask.style.height = readyImage.height + 'px';
mask.style.display = 'inline-block';
mask.innerText = '正在加载中';
mask.style.background = '#999';
loadingReady(image, mask, document.querySelector('.box'));
}
init();
})()
</script>
</body>
</html>
实现效果如下, 刚发现上面的代码都是用ES6箭头函数和let&const写的,let 和 const是新的声明变量的方式, 箭头函数是函数的简写但是与函数有一些区别, 可以百度一下箭头函数和let&const, 太晚了就懒得改了
我们发现, 当图片没有加载完成的时候页面始终显示的是一块灰色的底盘并且提示我们正在加载中, 这就是预加载。
Ajax
ajax作为元老级别的网络请求技术, 绝对值得新手探究和学习, ajax也是我们跟后台进行交互的第一步, 这里我默认大家已经懂了ajax的定义和一些基本常识, 我们直接从封装讲起
// ajax函数, 用来和后端进行交互
var ajax = (function() {
// url - 地址, method - 请求方法(POST or GET), callback - 请求的回调函数, data - 约定好传递的数据, flag - 是否异步
return function(url, method, callback, data, flag) {
// xhr就是用来存储请求与响应的信息
var xhr = null;
// 兼容性处理
if(window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
}else {
xhr = new ActiveXObject('Microsoft.XMLHttp');
}
// 监听此次数据请求的状态, 最好写在send方法之前, 原因可以自行百度, 注释难写,
// 跟网速有关可能会监听不到
xhr.onreadystatechange = function() {
// 只有readyState的值到了4了那么这个响应就结果也就来了, 1 2 3都是在请求中
if(xhr.readyState === 4) {
// http状态码 200一般为OK
if(xhr.status === 200) {
// 执行回调函数并且把 返回的数据xhr.responseText传进callback中
callback(xhr.responseText);
}else {
console.log('error');
}
}
}
// 统一一下格式, 无论调用者传进来的是get还是GET 都会被转化为GET
method = method.toUpperCase();
if(method === 'GET') {
var date = new Date();
var timer = date.getTime();
// 建立连接
xhr.open(method, url + '?' + data + '&timer=' + timer, flag);
// 发出请求
xhr.send();
}else if(method === 'POST'){
// 建立连接
xhr.open(method, url, flag);
// 设置请求头
// application/x-www-form-urlencoded 表示数据是以key=value的形式拼接的
// 还有一些其他的Content-Type比如application/json 表示数据以json格式存在,
// 更多可以了解一下网络知识
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
// 发出请求
xhr.send();
}
}
})()
上方封装了一个比较简单的ajax网络请求, 注释也已经打好, 如果对上面的函数有什么疑问或者觉得需要改进的地方可以私信, 到这我们的ajax网络请求就为止了
当上面三个知识点透彻以后,我们就万事俱备只欠东风了,下面我们来写我们上面样图中的瀑布流, 直接贴代码注释都已经写好
- HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>瀑布流</title>
<!-- 引入样式 -->
<link rel="stylesheet" href="./index.css">
</head>
<body>
<!-- 父级wrapper -->
<div class="wrapper">
<!-- 标题区域 -->
<div class="title">
<h2>民间制柴窝点</h2>
</div>
<!-- 内容进行瀑布流的区域 -->
<div class="content">
<ul class="list">
<!-- 分为四列, 竖向, 对应页面中的四列,并且四个li都等待元素进行填充 -->
<li class="item">
</li>
<li class="item">
</li>
<li class="item">
</li>
<li class="item">
</li>
</ul>
</div>
</div>
<!-- 引入之前封装好的ajax -->
<script src="./ajax.js"></script>
<!-- 引入实现功能的index.js -->
<script src="./index.js"></script>
</body>
</html>
- CSS
/* 初始化, 这样初始化有一丢丢的多余操作, 在企业中一般公司会给一个reset.css */
* {
margin: 0;
padding: 0;
list-style: none;
}
/* :root可以理解为html, */
:root, body {
width: 100%;
height: 100%;
/* css3的渐变, 就可以理解为有过渡效果的背景颜色 */
background-image: linear-gradient(to right, #ccc, #fff);
}
/* 父级wrapper基础样式 */
.wrapper {
margin: 20px auto;
width: 1190px;
background-color: #fff;
}
/* 给标题的容器样式 */
.wrapper .title {
background-image: linear-gradient(to right, #1e90ff, rebeccapurple);
padding: 10px 0;
/* 阴影, box-shadow: x偏移 y偏移 模糊距离 颜色; 当然还有一些属性, 我这里写的是我用到属性的含义 */
box-shadow: 18px 16px 20px #999;
}
.wrapper .title h2 {
/* margin: 20px 0; */
text-align: center;
font-size: 30px;
color: rgb(255, 255, 255);
/* 文字阴影 很 box-shadow一致 */
text-shadow: 0px 0px 2px rgb(233, 233, 233),
0px 0px 3px rgb(134, 133, 131),
0px 0px 6px rgb(54, 54, 53);
}
/* 给主体内容区的样式 */
.wrapper > .content {
width: 100%;
padding: 20px;
box-sizing: border-box;
}
/* overflow:hidden可以清除浮动 */
.wrapper > .content > .list {
overflow: hidden;
}
/* 给每个li的样式 */
.wrapper > .content > .list > .item {
width: 25%;
text-align: center;
padding: 10px;
box-sizing: border-box;
/* float: left; */
display: inline-block;
}
/* 渲染进li中div的样式 */
.wrapper > .content > .list > .item > div {
background-color: rgba(87, 88, 88, 0.05);
padding: 10px;
box-sizing: border-box;
cursor: pointer;
margin-top: 15px;
}
/* 当渲染进li的div被hover时候产生的卡片效果 */
.wrapper > .content > .list > .item > div:hover {
box-shadow: 2px 4px 10px rgb(155, 153, 153);
}
/* 每个li中后面会渲染进很多个div, 每个div中有一个img元素和一个
p元素 */
.wrapper > .content > .list > .item > div > p {
position: relative;
font-size: 20px;
line-height: 30px;
padding-bottom: 4px;
color: #616060;
/* border-bottom: 2px solid #ccc; */
}
/* 给p元素的下划线设置样式 */
.wrapper > .content > .list > .item > div > p::after {
content: '';
display: block;
position: absolute;
height: 2px;
left: 45px;
right: 45px;
bottom: 0;
background-image: linear-gradient(270deg, pink, gold, gold, pink);
}
.wrapper > .content > .list > .item > div > img {
width: 100%;
}
/* 提示框 最下面的没有更多数据了*/
.wrapper > .content > .toast {
width: 100%;
text-align: center;
font-size: 24px;
margin-top: 20px;
color: #ccc;
}
- JS(JS方法写的有点乱, 太晚了, 脑袋不是很清晰, 可以自己根据逻辑减少耦合, 也用上了ES5的书写方式, 更容易理解)
(function() {
var curPage = 1; // 页数, 因为没有后台, 所以我们是通过mock数据来进行数据请求, 所以这个页数必须由我们自己来控制了
var toast = null; // 全局访问的一个底层div用来显示没有更多数据了
// 初始化方法
function init() {
getImagesData('./data.json', 'GET', function(resp) {
var data = JSON.parse(resp);
var lastData = data.slice(0, curPage * 10);
renderLi(lastData);
}, curPage, true);
bindEvent();
}
// 获取数据
function getImagesData(url, method, callback, data, flag) {
ajax(url, method, callback, data, flag)
}
// 事件绑定
function bindEvent() {
// 监听鼠标滚轮的滑动
window.onscroll = function() {
var scrollTop = document.documentElement.scrollTop;
var cHeight = document.documentElement.offsetHeight;
var liMinHeight = getMinHeight().offsetHeight;
// 对高度的计算
if(liMinHeight + 140 < cHeight + scrollTop) {
curPage ++;
getImagesData('./data.json', 'GET', function(resp) {
var data = JSON.parse(resp);
var lastData = data.slice((curPage - 1) * 10, curPage * 10);
if(lastData.length === 0) {
if(toast) {
return toast;
}else {
toast = document.createElement('div');
toast.innerText = '没有更多数据了';
toast.className = 'toast'
document.querySelector('.content').appendChild(toast);
}
return false;
}else {
toast = null;
}
console.log(lastData);
renderLi(lastData);
}, curPage, true);
}
}
}
// 我们每次需要找到最小的li是哪个, 然后往最小的li进行数据的填充
function getMinHeight() {
var minIndex = 0; // 最小li的索引
var lis = document.querySelectorAll('.item');
var minHeight = lis[minIndex].offsetHeight;
for(var i = 0, len = lis.length; i < len; i++) {
if(minHeight > lis[i].offsetHeight) {
minHeight = lis[i].offsetHeight;
minIndex = i;
}
}
return lis[minIndex];
}
// 抽离预加载函数
function loadingReady(parentNode, imgNode, replaceNode) {
(function(parentNode, imgNode){
imgNode.onload = function() {
// console.log(parentNode);
// console.log(imgNode);
// console.log(replaceNode);
setTimeout(function() {
parentNode.replaceChild(imgNode,replaceNode);
}, 500)
}
})(parentNode, imgNode)
}
// 渲染li
function renderLi(lastData) {
for(var i = 0, len = lastData.length; i < len; i++) {
console.log(lastData[i]);
var h = lastData[i].height; // 取到高度
var url = lastData[i].url; // 取到url
var text = lastData[i].text; // 取到文本
var w = 247.5;
var liMinDom = getMinHeight();
var img = new Image();
var div = document.createElement('div'); // 预加载
var p = document.createElement('p'); // 放置文字的容器
var image = new Image(); // image标签
p.innerText = text;
var container = document.createElement('div');
container.className = 'container';
div.style.width = w + 'px';
div.style.height = h + 'px';
div.style.backgroundColor = '#ccc';
div.innerText = '资源正在加载中,请稍后';
div.style.textAlign = 'center';
div.style.lineHeight = h + 'px';
div.style.fontSize = '20px';
div.style.color = '#999';
container.appendChild(div);
container.appendChild(p);
liMinDom.appendChild(container);
image.height = h;
image.src = url;
loadingReady(container, image, container.querySelector('div'));
}
}
init();
})()
- 最后提供mock的数据格式
[
{
"url": "./images/pic4.jpg",
"height": 535.89,
"text": "巫师柴"
},
{
"url": "./images/pic2.jpg",
"height": 247.5,
"text": "狗粮吃饱了~"
},
{
"url": "./images/pic7.jpg",
"height": 247.5,
"text": "新年柴"
},
{
"url":"./images/pic10.jpg",
"height": 247.5,
"text": "我就吃一勺, 应该不会胖吧"
},
{
"url":"./images/pic12.jpg",
"height": 258.22,
"text": "抱鱼柴"
},
{
"url": "./images/pic8.jpg",
"height": 154.69,
"text": "仰望大佬"
},
{
"url":"./images/pic11.png",
"height": 280.42,
"text": "我是一只凶猛的大恐龙"
},
{
"url": "./images/pic3.jpg",
"height": 247.5,
"text": "木乃伊柴, 家里的卫生纸呢!!!?"
},
{
"url": "./images/pic6.jpg",
"height": 247.5,
"text": "小肥柴过生日啦"
},
{
"url":"./images/pic5.jpg",
"height": 535.91,
"text": "摸鱼又被抓了"
},
{
"url": "./images/pic9.jpg",
"height": 154.69,
"text": "瘦一百斤,肥"
},
{
"url": "./images/pic14.png",
"height": 240.94,
"text": "柴晚安~"
},
{
"url": "./images/pic1.jpg",
"height": 247.5,
"text": "点赞柴"
},
{
"url": "./images/pic15.png",
"height": 355.5,
"text": "call me Iron Dog"
},
{
"url": "./images/pic13.jpg",
"height": "247.5",
"text": "给大佬递茶"
},
{
"url": "./images/pic16.jpg",
"height": "231.41",
"text": "幻想成为亿万富翁柴~"
},
{
"url": "./images/pic17.jpeg",
"height": "247.5",
"text": "钱又花光了,555"
},
{
"url": "./images/pic18.jpg",
"height": "247.5",
"text": "复柴者联盟, 出动!!!"
},
{
"url": "./images/pic18.png",
"height": "236.55",
"text": "圣诞小柴, 礼物送来~"
},
{
"url": "./images/pic19.jpg",
"height": "536.01",
"text": "愚人节快乐,嘿嘿"
},
{
"url": "./images/pic20.png",
"height": "246.96",
"text": "给你花花"
},
{
"url": "./images/pic20.psng",
"height": "426.6",
"text": "逃课柴~溜了溜了"
},
{
"url": "./images/pic21.jpg",
"height": "247.5",
"text": "柴元宝~~"
},
{
"url": "./images/pic22.jpg",
"height": "536.01",
"text": "有钱就是这么任性"
},
{
"url": "./images/pic23.jpg",
"height": "247.5",
"text": "冬天来了, 多穿是福"
},
{
"url": "./images/pic24.jpg",
"height": "154.58",
"text": "小柴宝宝摸摸头"
},
{
"url": "./images/pic25.jpg",
"height": "247.5",
"text": "啥~啥是秀恩爱~~"
},
{
"url": "./images/pic26.jpg",
"height": "536.01",
"text": "该吃吃,该喝喝,啥事不往心里搁的柴"
},
{
"url": "./images/pic27.jpg",
"height": "247.5",
"text": "鸣人柴,看我螺旋鸡腿~"
},
{
"url": "./images/pic28.png",
"height": "358.63",
"text": "潮就一个字,柴只说一次"
},
{
"url": "./images/pic29.png",
"height": "155.56",
"text": "行走江湖, 打抱不平柴"
},
{
"url": "./images/pic31.png",
"height": "308.04",
"text": "柴社相声"
},
{
"url": "./images/pic40.png",
"height": "202.46",
"text": "狗团外卖, 生活不易, 客官别给差评哦~"
}
]