图片延迟加载的3种实现方案与瀑布流的实现

145 阅读3分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

图片延迟加载的3种实现方案与瀑布流的实现

  • 基于JS 盒模型实现的懒加载方案 imglazyLoad.png
  <div class="container clearfix">
		<div class="column">
			<!-- <div class="card">
				<a href="#">
					<div class="lazyImageBox">
						<img src="" alt="" data-img="images/1.jpg">
					</div>
					<p>泰勒·斯威夫特(Taylor Swift),1989年12月13日出生于美国宾州,美国歌手、演员。2006年出道,同年发行专辑《泰勒·斯威夫特》,该专辑获得美国唱片业协会的白金唱片认证</p>
				</a>
			</div> -->
		</div>
		<div class="column"></div>
		<div class="column"></div>
	</div>
	<div class="loadingBox"></div>


	<!-- IMPORT JS -->
	<script src="lib/utils.js"></script>
	<script src="index.js"></script>
let imageModule = (function(){
  let columns = Array.from(document.querySelectorAll('.column')),lazyImageBoxs
  // 数据绑定
  const bindHTML = function bindHTML(data){
    //计算真实渲染的高度
    data = data.map(item => {
      let w = item.width, h = item.height;
      h = h / ( w/230 )
      item.width = 230
      item.height = h
      return item;
    })

    // 按照三个为一组进行循环(最后一组可能不到三项)
    for(let i=0; i<data.length; i += 3){
      // 把三组数据按照高度排序(升序)
      let group = data.slice(i,i+3);
      group.sort((a,b) => a.height - b.height)

      // 把三个列按照现在的高度进行排序(降序)
      columns.sort((a,b) => b.offsetHeight - a.offsetHeight)

      // 循环三个数据中的每一项:每循环一项,创建一个CARD,把创建的CARD放到对应的列中即可
      group.forEach((item, index) => {
        let {pic, link, title, height,} = item
        let card = document.createElement('div');
        card.className = "card";
                card.innerHTML = `<a href="${link}">
					<div class="lazyImageBox" style="height:${height}px">
						<img src="" alt="" data-img="${pic}">
					</div>
					<p>${title}</p>
        </a>`;
        columns[index].appendChild(card);
      })
    }
    // 获取所有需要延迟加载的盒子
    lazyImageBoxs = Array.from(document.querySelectorAll('.lazyImageBox'));
  }
  // 单张图片加载「为了防止加载的地址有错误,我们都是临时创建一个IMG去加载地址,能加载成功,才把地址赋值给真正的IMG」
  const singleImgLoading = function singleImgLoading(imgBox){
    let img = imgBox.querySelector('img'),
        trueImg = img.getAttribute('data-img'),
        tempImg = new Image();
    tempImg.src = trueImg
    tempImg.onload = function(){
      img.src = trueImg;
      img.style.opacity = 1;
      tempImg = null
    }
    imgBox.isLoad = true;
  }
  // 根据条件规则,控制哪些延迟加载
  const lazyImgsFunc = function lazyImgsFunc () {
    lazyImageBoxs.forEach(imgBox => {
      if(imgBox.isLoad) return //处理过就不在处理

      //方案一
      let A = imgBox.offsetHeight + utils.offset(imgBox).top,
          B = document.documentElement.clientHeight + document.documentElement.scrollTop
          if(A <= B) singleImgLoading(imgBox) //盒子完全出现在视口中
    })
  } 
  return{
    // 模块入口:管控执行的逻辑
    async init(){
      let data = await utils.ajax('./data.json')
      bindHTML(data);
      setTimeout(lazyImgsFunc);
      window.addEventListener('scroll', utils.throttle(lazyImgsFunc, 500));
    }
  }
})()

imageModule.init();
  • 基于getBoundingClientRect的进阶方案
  // 改变lazyImgFunc函数中的条件
  // 解决方案二:getBoundingClientRect
  let A = imgBox.getBoundingClientRect().bottom,
      B = document.documentElement.clientHeight;
  if (A <= B) singleImgLoading(imgBox);
  • 终极方案:IntersectionObserver
let imageModule = (function(){
  let columns = Array.from(document.querySelectorAll('.column')),
      loadingBox = document.querySelector('.loadingBox'),
      lazyImageBoxs
  
  // 创建一个监听器:处理图片的延迟加载
  let config = {
    threshold:[1] //0 一露头  0.5 一半  1完全出现
  }

  let ob = new IntersectionObserver(changes => {
      changes.forEach(item => {
        let {
          isIntersecting,//完全出现在视口中会变为true
          target
        } = item;
        if(isIntersecting){
          // 完全出现在视口中
          singleImgLoading(target);
          // 移除对当前盒子的监听
          ob.unobserve(target)
        }
      })
  },config)

  // 监听到达底部
  const watchBottom = function watchBottom() {
    let obLoading = new IntersectionObserver(async changes => {
        let item = changes[0];
        if (item.isIntersecting) {
            // 到底部了:加载更多的数据
            let data = await utils.ajax('./data.json');
            bindHTML(data);
            lazyImgsFunc();
        }
    });
    obLoading.observe(loadingBox);
};
  
  // 数据绑定
  const bindHTML = function bindHTML(data){
    //计算真实渲染的高度
    data = data.map(item => {
      let w = item.width, h = item.height;
      h = h / ( w/230 )
      item.width = 230
      item.height = h
      return item;
    })

    // 按照三个为一组进行循环(最后一组可能不到三项)
    for(let i=0; i<data.length; i += 3){
      // 把三组数据按照高度排序(升序)
      let group = data.slice(i,i+3);
      group.sort((a,b) => a.height - b.height)

      // 把三个列按照现在的高度进行排序(降序)
      columns.sort((a,b) => b.offsetHeight - a.offsetHeight)

      // 循环三个数据中的每一项:每循环一项,创建一个CARD,把创建的CARD放到对应的列中即可
      group.forEach((item, index) => {
        let {pic, link, title, height,} = item
        let card = document.createElement('div');
        card.className = "card";
                card.innerHTML = `<a href="${link}">
					<div class="lazyImageBox" style="height:${height}px">
						<img src="" alt="" data-img="${pic}">
					</div>
					<p>${title}</p>
        </a>`;
        columns[index].appendChild(card);
      })
    }
    // 获取所有需要延迟加载的盒子
    lazyImageBoxs = Array.from(document.querySelectorAll('.lazyImageBox'));
  }
  // 单张图片加载「为了防止加载的地址有错误,我们都是临时创建一个IMG去加载地址,能加载成功,才把地址赋值给真正的IMG」
  const singleImgLoading = function singleImgLoading(imgBox){
    let img = imgBox.querySelector('img'),
        trueImg = img.getAttribute('data-img'),
        tempImg = new Image();
    tempImg.src = trueImg
    tempImg.onload = function(){
      img.src = trueImg;
      img.style.opacity = 1;
      tempImg = null
    }
    imgBox.isLoad = true;
  }
  // 根据条件规则,控制哪些延迟加载
  const lazyImgsFunc = function lazyImgsFunc () {
    lazyImageBoxs.filter(imgBox =>  imgBox.getAttribute('isWatch') !== "TRUE"
    ).forEach(imgBox => {

      ob.observe(imgBox)
      imgBox.setAttribute('isWatch', 'TRUE')
      // if(imgBox.isLoad) return //处理过就不在处理
      // let A = imgBox.getBoundingClientRect().bottom,
      // B = document.documentElement.clientHeight;
      // if (A <= B) singleImgLoading(imgBox);  
      //方案一
      // let A = imgBox.offsetHeight + utils.offset(imgBox).top,
      //     B = document.documentElement.clientHeight + document.documentElement.scrollTop
      //     if(A <= B) singleImgLoading(imgBox) //盒子完全出现在视口中
    })
  } 
  return{
    // 模块入口:管控执行的逻辑
    async init(){
      let data = await utils.ajax('./data.json')
      bindHTML(data);
      lazyImgsFunc()
      watchBottom()
    }
  }
})()

imageModule.init();
  • 效果图

imglazy.png