当前位置: 首页 > news >正文

前端性能优化之同时插入100000个元素页面不卡顿

面试官:同时插入100000个元素怎么让页面不卡顿

优化前写法

首先我们来看下面的一段,点击按钮后,循环100000次,每次都插入一个元素,并且插入区域上方还有一个小球在滚动,在插入的过程中我们可以观察小球的动画是否出现卡顿

<template>
    <h3>小球左右上下循环移动动画</h3>
    <div class="container">
      <div id="ball" class="ball"></div>
    </div>

    <h3>前端性能优化之</h3>
    <el-button type="primary" @click='addBall'>加载10000个小球</el-button>
    <div id='ballBox' class='ballBox'></div>
</template>

<script setup>
 onMounted(() => {
  const ball = document.getElementById('ball')
  const container = document.querySelector('.container')

  // 获取容器的宽高
  const containerWidth = container.offsetWidth
  const containerHeight = container.offsetHeight

  // 小球的初始位置和速度
  let ballX = 0
  let ballY = 0
  let speedX = 2 // 水平速度
  let speedY = 2 // 垂直速度

  // 动画函数
  function moveBall() {
    // 更新小球的位置
    ballX += speedX
    ballY += speedY

    // 检测边界碰撞并反转方向
    if (ballX <= 0 || ballX + ball.offsetWidth >= containerWidth) {
      speedX = -speedX
    }
    if (ballY <= 0 || ballY + ball.offsetHeight >= containerHeight) {
      speedY = -speedY
    }

    // 应用新的位置
    ball.style.left = `${ballX}px`
    ball.style.top = `${ballY}px`

    // 循环调用动画
    requestAnimationFrame(moveBall)
  }

  // 启动动画
  moveBall()
})

function addBall(){
  let ballBox = document.getElementById('ballBox')
  const tasks = Array.from({ length: 100000 }, (_, i) => {
    return () => {
      let ball = document.createElement('div')
      ball.innerText = i + 1
      ballBox.appendChild(ball)
    }
  })
  for (let i = 0; i < tasks.length; i++) {
    tasks[i]()
  }
}
</script>

<style scoped>
.container {
  position: relative;
  width: 400px;
  height: 300px;
  border: 2px solid #ccc;
  background-color: #f5f5f5;
  overflow: hidden; /* 防止小球超出容器 */
}

.ball {
  position: absolute;
  width: 30px;
  height: 30px;
  background-color: red;
  border-radius: 50%; /* 圆形 */
}
</style>

目前的效果

gifimg2

可以看到点击后小球明显的出现了一段时间的卡顿才恢复正常

使用DocumentFragment减少DOM操作

DocumentFragment 是一个轻量级的文档对象,可以作为一个临时的容器来存储一组节点。它的主要优点是可以减少对 DOM 的多次操作,从而提高性能。我们可以将多个节点添加到 DocumentFragment 中,然后一次性将 DocumentFragment 添加到 DOM 中。从而较少浏览器的重绘和回流

改写代码如下

function addBall() {
  // 获取要添加小球的容器元素
  let ballBox = document.getElementById('ballBox')
  
  // 创建一个 DocumentFragment,用于批量添加小球
  const fragment = document.createDocumentFragment()
  
  // 创建一个包含 100000 个任务的数组,每个任务用于创建一个小球并添加到 fragment 中
  const tasks = Array.from({ length: 100000 }, (_, i) => {
    return () => {
      // 创建一个 div 元素表示小球
      let ball = document.createElement('div')
      // 设置小球的文本内容为其序号
      ball.innerText = i + 1
      // 将小球添加到 fragment 中
      fragment.appendChild(ball)
    }
  })
  
  // 执行所有任务,将小球添加到 fragment 中
  for (let i = 0; i < tasks.length; i++) {
    tasks[i]()
  }
  
  // 将 fragment 添加到 ballBox 中
  ballBox.appendChild(fragment)
}

但是效果依然不是很理想

使用requestIdleCallback利用空闲时间渲染

requestIdleCallback 是一个浏览器 API,它允许你在浏览器空闲时执行一些低优先级的任务。我们可以使用它来优化 addBall 函数中的任务调度,以避免阻塞主线程。

另外结合DocumentFragment减少重绘共同作用优化渲染问题,下面的优化后的代码

function addBall() {
  // 获取要添加小球的容器元素
  let ballBox = document.getElementById('ballBox')
  
  // 创建一个 DocumentFragment,用于批量添加小球
  const fragment = document.createDocumentFragment()
  
  // 创建一个包含 10000 个任务的数组,每个任务用于创建一个小球并添加到 fragment 中
  const tasks = Array.from({ length: 10000 }, (_, i) => {
    return () => {
      // 创建一个 div 元素表示小球
      let ball = document.createElement('div')
      // 设置小球的文本内容为其序号
      ball.innerText = i + 1
      // 将小球添加到 fragment 中
      fragment.appendChild(ball)
    }
  })

  // 定义一个函数用于执行任务
  function performTasks(tasks) {
    // 如果任务数组为空,则将 fragment 添加到 ballBox 中
    if (tasks.length === 0) {
      ballBox.appendChild(fragment)
      return
    }

    // 使用 requestIdleCallback 在浏览器空闲时执行任务
    requestIdleCallback((deadline) => {
      // 当浏览器有空闲时间且任务数组不为空时,执行任务
      while (deadline.timeRemaining() > 0 && tasks.length > 0) {
        // 从任务数组中取出一个任务并执行
        const task = tasks.shift()
        task()
      }
      // 递归调用 performTasks 以继续执行剩余的任务
      performTasks(tasks)
    })
  }

  // 开始执行任务
  performTasks(tasks)
}

deadline.timeRemaining()requestIdleCallback 回调函数的参数 deadline 提供的方法之一。它返回当前空闲周期中剩余的毫秒数。这个方法允许你检查在当前空闲周期中还剩下多少时间可以用来执行任务

deadline.timeRemaining() 大于0表示还有空闲时间,利用这个时间来渲染小球,从而不会阻塞主线程,从而保持页面的响应性。

优化后的效果

gifimg3

可以看到,小球轻微的卡顿一下就接着继续运行

相关文章:

  • my学习网址
  • 2025-3-5 leetcode刷题情况(贪心算法--简单题目)
  • 【监督学习】XGBoost 步骤及matlab实现
  • AI人工智能与实验室应用场景分析
  • 烟花燃放安全管控:智能分析网关V4烟火检测技术保障安全
  • jsp使用+返回or使用数据+日志输出
  • 从0开始的操作系统手搓教程21:进程子系统的一个核心功能——简单的进程切换
  • 数据库监控工具——PMM
  • 宠物医疗对接DeepSeek详细方案
  • 【Linux】进程间通信 续
  • FormData获取表单,发现有些字段没有获取到,
  • 鸿蒙Android4个脚有脚线
  • Gitlab配置personal access token
  • 从零开发基于Qt6的TCP/UDP网络调试助手:技术架构与实现细节
  • (二 十 三)趣学设计模式 之 解释器模式!
  • C++第一节:类与对象
  • Spring Cloud Alibaba 实战:轻松实现 Nacos 服务发现与动态配置管理
  • 华为OD-E卷 - 最大矩阵和 100分(java)
  • 三参数水质在线分析仪:从源头保障饮用水安全
  • 《2025年软件测试工程师面试》消息队列面试题
  • 苏州网站建设 公司/谷歌google官网下载
  • 互动交流平台/兰州seo关键词优化
  • 做展馆好的设计网站/西安百度网站快速优化
  • 周口网站建设zkweb/网络安全有名的培训学校
  • 软件生成器/郑州网站制作选择乐云seo
  • 网站做网络营销/文职培训机构前十名