单页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>1span>div>
<div class="content"><span>2span>div>
<div class="content"><span>3span>div>
<div class="content"><span>4span>div>
<div class="content"><span>5span>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接口为开发者提供了一种可以异步监听目标元素与其祖先或视窗(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
参考地址: