在实现图片懒加载案例之前,我们先来学习一下JavaScript视口宽高、元素位置、滚动高度、尺寸属性
一、window视图位置属性
1.1、window对象获取视口(浏览器窗口)宽高
console.log(window.innerHeight) // 939
console.log(window.innerWidth) // 809
console.log(window.outerHeight) // 1050 (包括菜单栏)
console.log(window.outerWidth) // 1680
属性名 | 描述 |
---|---|
window.innerHeight | 浏览器窗口可视区域高度 |
window.innerWidth | 浏览器窗口可视区域宽度 |
window.outerHeight | 浏览器窗口整个高度,包括窗口标题、工具栏、状态栏等 |
window.outerWidth | 浏览器窗口整个宽度,包括侧边栏和调正窗口大小的边框 |
以下方式效果一样都是获取浏览器视口高度
let width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth
let height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
document.documentElement.clientHeight和document.body.clientHeight的区别:
- document.documentElement.clientHeight:不包括整个文档的滚动条,但包括
<html>
元素的边框。 - document.body.clientHeight:不包括整个文档的滚动条,也不包括
<html>
元素的边框,也不包括<body>
的边框和滚动条。
1.3、window对象获取浏览器窗口在显示器中的位置
- window.screenX 浏览器窗口在显示器中的水平位置
- window.screenY 浏览器窗口在显示器中的垂直位置
- window.screenLeft 浏览器可用空间左边距离屏幕(系统桌面)左边界的距离
- window.screenTop 浏览器窗口在屏幕上的可占用空间上边距离屏幕上边界的距离
二、元素视图位置属性
关于元素大小位置等信息的一些属性有三个家族:
- client家族:clientLeft、clientTop、clientWidth、clientHeight、height、width
- offset家族:offsetLeft、offsetTop 、offsetWidth、offsetHeight、 offsetParent
- scroll家族:scrollLeft、scrollTop、scrollWidth 、 scrollHeight
2.1、client家族介绍
clientWidth : 显示内容区域的宽度
clientHeight : 显示内容区域的高度
2.2、offset家族介绍
<main style="position: relative" id="main">
<article>
<div id="example" style="position: absolute; left: 180px; top: 180px">...</div>
</article>
</main>
<script>
alert(example.offsetParent.id); // main
alert(example.offsetLeft); // 180
alert(example.offsetTop); // 180
</script>
offsetLeft : 相对于父级非static定位元素的左偏距离
offsetTop : 相对于父级非static定位元素的上偏距离
offsetWidth :整个元素的宽度(包括边框)
offsetHeight :整个元素的高度(包括边框)
offsetParent : 第一个祖定位元素
2.3、scroll家族介绍
document.documentElement.scrollTop 上边滚动高度
document.documentElement.scrollLeft 左侧滚动宽度
document.documentElement.scrollWidth 网页内容整体的宽度
document.documentElement.scrollHeight 网页内容整体的高度
总结:
- 网页可见区域宽: document.body.clientWidth;
- 网页可见区域高: document.body.clientHeight;
- 网页可见区域宽: document.body.offsetWidth (包括边线的宽);
- 网页可见区域高: document.body.offsetHeight (包括边线的宽);
- 网页正文全文宽: document.body.scrollWidth;
网页正文全文高: document.body.scrollHeight;
网页滚动的高度距离: document.documentElement.scrollTop;
网页滚动的左侧距离: document.documentElement.scrollLeft;
网页正文部分上: window.screenTop; (正文距离屏幕上边距离)
- 网页正文部分左: window.screenLeft;(正文距离屏幕左侧距离)
- 屏幕分辨率的高: window.screen.height;
- 屏幕分辨率的宽: window.screen.width;
- 屏幕可用工作区高度: window.screen.availHeight;
文档坐标:HTML文档,一旦渲染完成,整个页面的大小是固定的
视口坐标:用户看到的文档部分,窗口显示的文档部分可视区域
文档与视口之间的关系:
- 文档小于或等于视口
这是一屏就可以将整个文档显示出来,没有滚动条,例如百度首页 - 文档 大于 视口
这是常有的,文档内容一屏显示不完,会出现滚动条,通过拖动滚动条从而来查看更多内容,即滚动查看整个文档内容。
视口高度:显示网页内容的区域,不包括菜单栏等
图片懒加载(Lazyload)
对页面加载速度影响最大的就是图片,一张普通的图片可以达到几M的大小,而代码也许就只有几十KB。当页面图片很多时,页面的加载速度缓慢,几S钟内页面没有加载完成,也许会失去很多的用户。
所以,对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域内的图片先不做加载, 等到滚动到可视区域后再去加载。这样子对于页面加载性能上会有很大的提升,也提高了用户体验。
案例思路:
将页面中的img标签src指向一张小图片或者src为空,然后定义data-src(这个属性可以自定义命名为data-src)属性指向真实的图片。src指向一张默认的图片,否则当src为空时也会向服务器发送一次请求。可以指向loading的地址。<img src="default.jpg" data-src="http://ww4.sinaimg.cn/large/006y8mN6gw1fa5obmqrmvj305k05k3yh.jpg" />
当载入页面时,先把可视区域内的img标签的data-src属性值负给src,然后监听滚动事件,把用户即将看到的图片加载。这样便实现了懒加载。
那么如何知道该图片是用户即将看到的呢,需要通过计算,获取图片距离顶部高度小于滚动的距离和浏览器视口高度,这标志着该图片已经显示在视口中了。
通过计算来判断何时显示该图片
body {
background-image: url(https://dss0.bdstatic.com/k4oZeXSm1A5BphGlnYG/skin/759.jpg?2);
background-repeat: no-repeat;
background-attachment: fixed;
background-size: cover;
}
.container {
width: 80%;
margin: 10px auto;
display: grid;
grid-template-columns: repeat(6, 1fr);
grid-template-rows: repeat(auto, 1fr);
}
.container img {
width: 100%;
}
.container img:nth-child(6n-2) {
grid-area: span 2 / span 2;
}
<div class="container">
<img data-src="img/img-1.jpg" src="img/temp.jpg">
<img data-src="img/img-2.jpg" src="img/temp.jpg">
<img data-src="img/img-3.jpg" src="img/temp.jpg">
<img data-src="img/img-4.jpg" src="img/temp.jpg">
<img data-src="img/img-5.jpg" src="img/temp.jpg">
<img data-src="img/img-6.jpg" src="img/temp.jpg">
<img data-src="img/img-7.jpg" src="img/temp.jpg">
<img data-src="img/img-8.jpg" src="img/temp.jpg">
<img data-src="img/img-9.jpg" src="img/temp.jpg">
<img data-src="img/img-10.jpg" src="img/temp.jpg">
<img data-src="img/img-11.jpg" src="img/temp.jpg">
<img data-src="img/img-12.jpg" src="img/temp.jpg">
<img data-src="img/img-13.jpg" src="img/temp.jpg">
<img data-src="img/img-14.jpg" src="img/temp.jpg">
<img data-src="img/img-15.jpg" src="img/temp.jpg">
<img data-src="img/img-16.jpg" src="img/temp.jpg">
</div>
// 循环数据插入
function addDate() {
var oParent = document.querySelector(".container")
for (var i = 0; i < 10; i++) { //循环插入数据
var oImg = document.createElement('img');
oImg.dataset.src = 'img/img-' + Math.round(Math.random() * (70 - 1) + 1) + ".jpg";
oImg.src = "img/temp.jpg";
oParent.appendChild(oImg);
}
}
// 判断滚动条是否到底部
function checkscrollside() {
//获取最后一个瀑布流块的高度:距离网页顶部(实现未滚到底就开始加载)
var lastBoxH = document.querySelector(".container img:last-child").offsetTop;
var scrollTop = document.documentElement.scrollTop; //获取滚动条卷走的高度
var documentH = document.documentElement.clientHeight; //显示页面文档的高
return (lastBoxH < scrollTop + documentH) ? true : false; //到达指定高度后 返回true
}
// 监听浏览器的滚动事件
window.addEventListener('scroll', layzyload);
//页面载入完毕加载可是区域内的图片
window.addEventListener('load', layzyload);
function layzyload() {
// 当最后一个图片距离顶部的距离小于滚动的距离和可视窗口的高度的时候,返回true
if (checkscrollside()) {
addDate(); // 随机动态加载十张图片
}
// 获取所有图片
const imgs = document.images;
// 获取视口高度
const clientHeight = document.documentElement.clientHeight;
//获取滚动高度
let scrollTop = document.documentElement.scrollTop;
// 循环遍历每一个图片
[...imgs].forEach(img => {
// 当前图片的顶部距离他的父级的高度,是否小于视口高度+滚动距离之和,也就是图片在屏幕中出现了
if (img.offsetTop < clientHeight + scrollTop) {
setTimeout(() => {
img.src = img.dataset.src;
}, 600)
}
})
}
效果图:
浏览地址:http://easys.ltd/layzyload/
轮播图
基本实现功能:
- 自动切换图片
- 左右按钮控制滚动
- 点击圆点切换图片
- 鼠标悬停停止切换
功能实现思路:
- 通过js生成和图片对于的小圆点
- 获取当前激活的小圆点和图片元素
- 给当前激活的元素添加类样式
- 根据点击的小圆点索引更新图片
- 给左右切换按钮添加事件
- 通过函数获取当前激活的图片索引
- 给索引进行加和减从而来设置前后切换
- 通过三元运算符判断是否到达最后一个和第一个
- 将处理好的索引传入激活函数中进行按钮和图片的激活
- 自动切换,给右翻页按钮设置单击事件派发
- 通过定时器进行间隔时间派发点击事件
- 通过鼠标移入和移出时间进行定时器的删除与创建
- 页面加载完成将创建定时器
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
a {
text-decoration: none;
}
/* 轮播图的容器 */
.container {
width: 62.5em;
height: 22em;
margin: 1em auto;
/* 转为定位元素/定位父级 */
position: relative;
}
/* 图片组 */
.container>.imgs img {
width: 100%;
height: 100%;
/* 默认全部隐藏 */
display: none;
/* 将所有的图片进行绝对定位,确保每一次只看到一张,所有图片共享这个容器 */
position: absolute;
left: 0;
top: 0;
}
/* 设置默认显示的图片(第一张) */
.container>.imgs img.active {
display: block;
}
/* 按钮组(独立按钮) */
.container>.btns {
position: absolute;
left: 0;
right: 0;
bottom: 0;
/* 水平居中 */
text-align: center;
}
.container>.btns span {
/* 转成行内块元素: 即能水平排列,双支持宽度设置 */
display: inline-block;
padding: 0.5em;
margin: 0 0.2em;
background-color: #fff;
border-radius: 50%;
cursor: pointer;
}
.container>.btns span.active {
background-color: #000;
}
/* 翻页按钮 */
.container .skip a {
position: absolute;
width: 2.5rem;
height: 5rem;
line-height: 5rem;
text-align: center;
opacity: 0.3;
top: 9rem;
font-weight: lighter;
font-size: 3rem;
background-color: #ccc;
display: none;
}
.container:hover .skip a {
display: inline-block;
}
.container .skip .prev {
left: 0;
}
.container .skip .next {
right: 0;
}
.container .skip *:hover {
opacity: 0.6;
color: #666;
}
<div class="container">
<!-- 图片组 -->
<nav class="imgs">
<a href="#"><img src="banner/banner1.jpg" data-index="1" alt="" class="active"></a>
<a href="#"><img src="banner/banner2.jpg" data-index="2" alt=""></a>
<a href="#"><img src="banner/banner3.jpg" data-index="3" alt=""></a>
<a href="#"><img src="banner/banner4.jpg" data-index="4" alt=""></a>
</nav>
<!-- 图片下面小圆点 -->
<nav class="btns">
<!-- 注意这里的小圆点需要js添加,是因为小圆点的数量是由图片张数决定的 -->
<!--
<span data-index="1"></span>
<span data-index="2"></span>
<span data-index="3"></span>
<span data-index="4"></span> -->
</nav>
<!-- 左右切换按钮 -->
<nav class="skip">
<a href="#" class="prev"><</a>
<a href="#" class="next">></a>
</nav>
</div>
// 所以轮播图片
const imgs = document.querySelectorAll(".container > .imgs img");
// 底部小圆点
const btnGroup = document.querySelector(".container > .btns");
// 翻页按钮
const skip = document.querySelector('.container > .skip');
// 因为小圆点的数量是由图片张数决定的,所以需要根据图片的数量动态创建
function autoCreateBtns(ele, imglength) {
// 创建文档片段
const fragment = document.createDocumentFragment();
for (var i = 1; i <= imglength; i++) {
var span = document.createElement('span');
// 添加自定义属性data-index和图片进行对应关联
span.dataset.index = i;
// 默认给第一个小圆点添加激活类
i === 1 ? span.classList.add('active') : "";
// 将新建的span挂载到文档片段中
fragment.append(span);
}
// 将文档片段追加到小圆点父节点中
ele.append(fragment);
}
// 传入小圆点父节点,图片的个数
autoCreateBtns(btnGroup, imgs.length);
// 给所有小圆点添加点击事件,利用事件代理
btnGroup.addEventListener("click", function(ev) {
// 判断点击的是小圆点span
if (ev.target.tagName === "SPAN") {
setActiveEle(ev.target.dataset.index);
}
})
// 给左右切换按钮添加点击事件
document.querySelectorAll(".skip > a").forEach(item => {
item.addEventListener("click", function(ev) {
// 左翻页按钮
if (ev.target.classList.contains('prev')) {
// 获取当前激活按钮为其索引-1
let index = +getActiveEle(imgs).dataset.index;
// 判断是否在第一页
index = (index == 1) ? imgs.length : index - 1;
setActiveEle(index);
}
// 右翻页按钮
if (ev.target.classList.contains('next')) {
// 获取当前激活按钮为其索引+1
let index = +getActiveEle(imgs).dataset.index;
// 判断是否在最后一页
index = (index == imgs.length) ? 1 : index + 1;
setActiveEle(index);
}
})
})
/*自动循环播放*/
var timer;
function play(speed) {
timer = setInterval(function() {
// 利用事件派发dispatchEvent() 自动点击右翻页按钮
const ev = new Event("click");
skip.lastElementChild.dispatchEvent(ev);
}, speed)
}
// 鼠标移入停止播放
document.querySelector('.container').onmouseenter = function() {
clearInterval(timer);
}
// 鼠标移除重新开始
document.querySelector('.container').onmouseleave = function() {
play(1500);
}
// 页面加载完成开始播放
window.onload = () => {play(1500)};
// 获取激活的元素
function getActiveEle(eles) {
// let activeEles = [...eles].filter(item => item.classList.contains('active'));
return [...eles].filter(item => item.classList.contains('active'))[0];
}
// 获取哪个图片被激活
// console.log(getActiveEle(imgs));
// 获取哪个按钮被激活
// console.log(getActiveEle(btnGroup.children))
// 设置激活的元素,根据按钮索引更新正在显示的图片
function setActiveEle(btnIndex) {
// 参数为当前正在点击的按钮索引
// 1. 先将之前激活样式去掉
// getActiveEle(imgs).classList.remove('active');
// getActiveEle(btnGroup.children).classList.remove('active');
// 2. 在根据当前小圆点的索引设置需要激活的小圆点和图片
[imgs, btnGroup.children].forEach(arr => {
getActiveEle(arr).classList.remove('active');
[...arr].forEach(item => {
// 循环判断哪个索引和当前点击的小圆点索引一致
if (item.dataset.index == btnIndex) {
// 为其对应的索引小圆点和图片添加激活样式
item.classList.add('active')
}
})
})
}
效果图: