单页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​

参考地址: