批改状态:合格
老师批语:这是第一单元,最棒的作业之一, 以后, 尽可能不用或少用var, 在一些高级智能编辑器中可能会直接警告或报错了
在实现图片懒加载案例之前,我们先来学习一下JavaScript视口宽高、元素位置、滚动高度、尺寸属性
1.1、window对象获取视口(浏览器窗口)宽高
console.log(window.innerHeight) // 939console.log(window.innerWidth) // 809console.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.clientWidthlet height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight

document.documentElement.clientHeight和document.body.clientHeight的区别:
<html>元素的边框。<html>元素的边框,也不包括<body>的边框和滚动条。1.3、window对象获取浏览器窗口在显示器中的位置
关于元素大小位置等信息的一些属性有三个家族:
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); // mainalert(example.offsetLeft); // 180alert(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.scrollHeight;
网页滚动的高度距离: document.documentElement.scrollTop;
网页滚动的左侧距离: document.documentElement.scrollLeft;
网页正文部分上: window.screenTop; (正文距离屏幕上边距离)
文档坐标:HTML文档,一旦渲染完成,整个页面的大小是固定的
视口坐标:用户看到的文档部分,窗口显示的文档部分可视区域
文档与视口之间的关系:
视口高度:显示网页内容的区域,不包括菜单栏等

对页面加载速度影响最大的就是图片,一张普通的图片可以达到几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() {// 当最后一个图片距离顶部的距离小于滚动的距离和可视窗口的高度的时候,返回trueif (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/
基本实现功能:
功能实现思路:
* {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) {// 判断点击的是小圆点spanif (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')) {// 获取当前激活按钮为其索引-1let index = +getActiveEle(imgs).dataset.index;// 判断是否在第一页index = (index == 1) ? imgs.length : index - 1;setActiveEle(index);}// 右翻页按钮if (ev.target.classList.contains('next')) {// 获取当前激活按钮为其索引+1let 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')}})})}
效果图:
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号