单页应用优化--懒加载

单页Web应用(single page web application,SPA)会一次性载入页面资源,利用本地计算能力渲染页面,提高页面切换速度与用户体验。由此带来了首屏加载缓慢耗时的诟病,这也是困扰前端开发工程师的一重大难题。

最近查阅了一些帖子,发现了一个极其强大的方法,其兼容性有待提高~~(但已有相关的的Polyfill方式)

按需加载

// 全部加载
import 'ccharts'

// 按需加载 只加载需要使用的组件
import 'echarts/lib/component/title'
import 'echarts/lib/component/tooltip'
import 'echarts/lib/component/legend'
import 'echarts/lib/chart/bar'

可以减小组件加载的大小,节省网络带宽,从而提高响应速度!

异步加载组件

首先我们可以将应用拆成多个模块组件,然后异步加载组件。配合webpack代码分割使用,达到按需加载的效果(下述只简单陈述,不做详细讲解)。

补充,webpack有三种常用的代码分割方式:

  • 入口起点:使用 entry 配置手动地分离代码。
  • 防止重复:使用 CommonsChunkPlugin 去重和分离 chunk。
  • 动态导入:通过模块的内联函数调用来分离代码。
// 同步方式
import search from '@/views/search/search.vue'
// 异步方式
const search = (resolve) => require(['@/views/search.vue'], resolve)
// ES6异步方式(推荐)
const search = () => import('@/views/search.vue')

注意,webpack中需要配置相关信息

output: {
    path: '/dist',
    filename: 'js/[name].[chunkhash].js', 
    chunkFilename:'js/[id].[chunkhash].js' 
},

注意,filename决定了bundle的名称。但是此选项不会影响那些「按需加载 chunk」的输出文件。对于这些文件,请使用 output.chunkFilename选项来控制输出。通过 loader 创建的文件也不受影响。在这种情况下,你必须尝试 loader 特定的可用选项。

懒加载

通过监听滚动条来判断是否在可视区域进行加载处理,document.documentElement.clientHeight > dom.getBoundingClientRect().top

<div class="content"><span>1</span></div>
<div class="content"><span>2</span></div>
<div class="content"><span>3</span></div>
<div class="content"><span>4</span></div>
<div class="content"><span>5</span></div>
const imageAddress = '../images/'
const viewHeight = document.documentElement.clientHeight // 可视区域的高度

function $(selector) {
    return Array.from(document.querySelectorAll(selector))
}

function lazyload () {
    // 获取所有要进行懒加载的图片
    $('.content').forEach(item => {
        let rect, imgSrc
        let index = item.querySelector('span').innerHTML
        // 资源已加载,避免重复加载
        if (item.dataset.src !== '') return
        rect = item.getBoundingClientRect()
        // 图片一进入可视区,动态加载
        if (rect.bottom >= 0 && rect.top < viewHeight) {
            imgSrc = `${imageAddress}${index}.jpg`
            item.dataset.src = imgSrc
            let img = document.createElement('img')
            img.src = imgSrc
            item.appendChild(img)
        }
    })
}
lazyload()
document.addEventListener('scroll', lazyload)

注意:要对已加载的资源进行标识,防止重复加载!

该方式通过监听到scroll事件后,调用目标元素(绿色方块)的getBoundingClientRect()方法,得到它对应于视口信息,再判断是否在视口之内。这种方法的缺点是,由于scroll事件密集发生(当然可以使用节流函数进行相应处理),计算量很大,容易造成性能问题!

IntersectionObserver

IntersectionObserver接口为开发者提供了一种可以异步监听目标元素与其祖先或视窗(viewport)交叉状态的手段。该API 是异步的(降低了昂贵的DOM和样式查询开销、以及CPU、GPU能源成本),不随着目标元素的滚动同步触发,对于理解元素的可见性以及实现DOM内容的预加载和延迟加载非常有用。

IntersectionObserver((entries, observer) =>{}, options)
// 观察指定目标元素
observer.observe(target);
// 停止观察指定目标元素
observer.unobserve(target);
// 停止观察全部元素
observer.disconnect();

entries为IntersectionObserverEntry对象,包含如下属性:

  • time:可见性发生变化的时间,毫秒;
  • target:被观察的目标元素,DOM节点对象;
  • rootBounds:根元素的矩形区域的信息,getBoundingClientRect()方法的返回值,如果没有根元素(即直接相对于视口滚动),则返回null
  • boundingClientRect:目标元素的矩形区域的信息;
  • intersectionRect:目标元素与视口(或根元素)的交叉区域的信息;
  • intersecttionRatio:目标元素的可见比例;

options为IntersectionObserverInit 对象,包含如下属性:

  • root:指定目标元素所在的容器节点(即根元素);
  • rootMargin:用来扩展或缩小rootBounds这个矩形的大小,从而影响intersectionRect交叉区域的大小;
  • threshold:决定了什么时候触发回调函数
var io = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        let {target, intersectionRatio} = entry
        console.log(target.tagName, intersectionRatio)
    })
}, {
    threshold: [0, 0.25, 0.5, 0.75, 1]
})
// 监听
io.observe($('.target'))

class名称为‘target’的元素,在可见比例为[0, 0.25, 0.5, 0.75, 1]均会执行相关回调函数!

实现懒加载:

var io = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        let {target, intersectionRatio} = entry
        // 目标元素的可见比例大于0
        if (intersectionRatio) {
            let index = target.querySelector('span').innerHTML
            let img = document.createElement('img')
            img.src = `${imageAddress}${index}.jpg`
            target.appendChild(img)
            // 取消监听,防止重复加载
            io.unobserve(target)
        }
    })
}, {
    threshold: [0]
})
// 监听
$('.content').forEach(element => {
    io.observe(element)
})

实例地址:https://github.com/381510688/practice/blob/master/javascript_test/lazyLoad.html

兼容性

Github上提供了相关的Polyfill方式:IntersectionObserver polyfill

参考地址:

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券