使用createDocumentFragment和requestAnimationFrame,渲染大量数据页面不卡顿

lxf2023-05-05 10:02:02

1、实现思路:渲染大量数据时,合理使用 createDocumentFragment 和 requestAnimationFrame,将操作且分为一小段一小段去执行,从而实现页面不卡顿。

2、createDocumentFragment()

是用来创建一个虚拟的节点对象(用来创建文档碎片节点),该节点是一个虚拟节点,不属于文档树。在将 DocumentFragment 节点插入文档树时,插入的并不是 DocumentFragment 节点本身,而是插入他的子节点(后代节点)。 当需要添加向页面添加很多DOM元素时(如添加很多个 li 标签),如果一个个createElement出来,然后再一个个appendChild上去,会频繁的操作DOM,很影响性能。因此可以可以使用 DocumentFragment 节点,先把 li 标签添加到 DocumentFragment,然后由 DocumentFragment 统一添加到页面上,会大大减少DOM操作,性能明显提升。(因为插入DocumentFragment节点的时候是插入他的后代节点,所以最后的实现效果是一样的,但性能有很大的提升。)

3、requestAnimationFrame() // 传入一个 回调函数/动画

方法功能类似于 setInterval,但 requestAnimationFrame 在性能方面会更好一些。它不需要设置时间间隔,它的时间间隔由系统定义(一般为16.67ms),保证最佳的绘制效率。 requestAnimationFrame() 浏览器没刷新一次,回调代码执行一次,不会造成丢帧或卡顿的现象,且当页面最小化或者隐藏时,requestAnimationFrame 会暂停渲染,当页面重新激活时,会从上次暂停的地方继续开始渲染,故性能方面会更好。

function renderOfNoStuck(total,parentNode,once){
// total:必选,需要渲染的数据总量(可根据需求自定义)
// parentNode:必选,需要将数据渲染到哪个节点下
// once:可选,分段渲染时一小段渲染的数据量,默认为 20

	if (!parentNode || parentNode.nodeType !== 1) {
	    // nodeType === 1 为元素节点 (2为属性节点,3为文本节点)
	    throw new TypeError('请传入正确的参数');
	}
	setTimeout(() => {
	    // 把大量数据切成一小段一小段进行渲染
	    const once = once || 20; // 一小段渲染的数据量(可根据需求自定义)
	    const loopCount = Math.ceil(total / once);  // 插入数据需要的次数
	    let countOfRender = 0; // 已渲染的数据量
	    // 添加数据的方法
	    function add() {
	        const fragment = document.createDocumentFragment();	// 创建一个 DocumentFragment节点
	        for (let i = 0; i < once; i++) {
	            const li = document.createElement('li');
	            li.innerText = Math.floor(Math.random() * total); // 给标签添加内容 (可根据需求自定义)
	            fragment.appendChild(li); // 把数据先添加到 DocumentFragment 节点上
	        }
	        parentNode.appendChild(fragment); // 然后由 DocumentFragment 节点统一渲染到页面
	        countOfRender += 1;
	        loop();
	    }
	    function loop() {
	        if (countOfRender < loopCount) {
	            window.requestAnimationFrame(add); // requestAnimationFrame 传入回调函数add,渲染数据
	        }
	    }
	    loop();
	}, 0)
}
// let parentNode = document.querySelector('ul');
// renderOfNoStuck(100000, parentNode)
// renderOfNoStuck(20, parentNode, 1) // 设置成 1 时有一个比较好看的效果