优化网页性能指标:提升用户体验的关键

性能指标

交互到下一次绘制 Interaction to Next Paint (INP)

定义:交互到下一次绘制(Interaction to Next Paint),衡量用户与页面交互后,浏览器响应并渲染下一个帧的时间。

关键点

  • 测量范围:从用户触发交互事件开始,到浏览器完成下一次绘制为止
  • 目标值:小于200毫秒为优秀,200-500毫秒为可接受,大于500毫秒为需要优化
  • 影响因素:JavaScript执行时间、CSS计算、布局重排、绘制等

优化建议

  1. 减少长时间运行的JavaScript任务
  2. 优化CSS选择器和样式计算
  3. 避免布局重排和重绘
  4. 使用requestIdleCallback处理非紧急任务
  5. 增加Web Workers分担主线程压力

首次内容绘制 FCP (First Contentful Paint)

  • 定义:首次内容绘制,衡量页面首次渲染任何文本、图片或非空白内容的时间
  • 目标值:小于1.8秒为优秀,1.8-3秒为可接受,大于3秒为需要优化

首次与页面交互时的响应时间 FID (First Input Delay)

  • 定义:首次输入延迟,衡量用户首次与页面交互时的响应时间
  • 目标值:小于100毫秒为优秀,100-300毫秒为可接受,大于300毫秒为需要优化

页面主要内容可见的时间 Largest Contentful Paint (LCP)

  • 定义:最大内容绘制,衡量页面主要内容可见的时间
  • 目标值:小于2.5秒为优秀,2.5-4秒为可接受,大于4秒为需要优化

布局偏移 Cumulative Layout Shift (CLS)

主要布局偏移问题

表格高度不固定

分页组件高度变化

骨架屏到表格的切换

在表格加载过程中,由于表格的高度不确定,可能会导致布局偏移(CLS)

分页组件在数据加载前后也会因为总条数的不确定而出现高度变化,导致布局偏移

为了减少布局偏移,我们可以采取以下措施:

  1. 为表格容器设置一个最小高度,这样在加载数据时,表格区域的高度不会从0变成有数据的高度,从而减少布局偏移。
  2. 为分页组件设置一个固定高度,并在数据加载前就预留出空间,这样分页组件的位置不会因为数据加载而移动。

另外,我们还可以考虑使用骨架屏来完全模拟表格的布局,这样在加载过程中,骨架屏的高度和表格高度一致,就不会有布局偏移。

由于我们表格每页显示10条数据,我们可以让骨架屏也生成10行,每行的高度与表格行高相同。

修改骨架屏的代码,使其有10行,每行高度与表格行高相同(55px左右),并且每行之间有间距。

表格相关

改进表格容器样式
css 复制代码
.table-container {
  min-height: 400px; /* 更合理的最小高度 */
  position: relative;
}

/* 确保骨架屏和表格高度一致 */
.skeleton-placeholder {
  min-height: 400px;
}
固定表格容器高度
表格列宽优化
  • 为所有列设置了固定宽度
    • 为所有列设置了固定宽度:80+120+130+110+110+150+120+220 = 1040px
    • 使用 min-width: 1040px 确保表格最小宽度
  • 使用 table-layout="fixed" 确保布局稳定
  • 强制表头和表体使用相同的列宽计算方式
css 复制代码
::v-deep .el-table__header,
::v-deep .el-table__body {
width: 100% !important;
table-layout: fixed !important;
}
预计算表格高度
js 复制代码
// 在组件挂载时计算并设置表格高度
mounted() {
  this.calculateTableHeight()
  window.addEventListener('resize', this.calculateTableHeight)
},
beforeDestroy() {
  window.removeEventListener('resize', this.calculateTableHeight)
},
methods: {
  calculateTableHeight() {
    // 根据窗口高度动态计算表格容器高度
    const windowHeight = window.innerHeight
    const tableContainer = document.querySelector('.table-container')
    if (tableContainer) {
      const rect = tableContainer.getBoundingClientRect()
      const newHeight = windowHeight - rect.top - 120 // 减去表头、分页等高度
      tableContainer.style.height = `${Math.max(newHeight, 400)}px`
    }
  }
}

分页容器相关

1. 固定分页容器尺寸
css 复制代码
.pagination-container {
  height: 60px; /* 固定高度 */
  min-width: 400px; /* 预留足够宽度 */
  display: flex;
  align-items: center;
  justify-content: flex-end;
  position: relative;
}
2. 预分配分页组件空间
css 复制代码
/* 确保分页组件有稳定的容器 */
.el-pagination {
  min-width: 350px; /* 预留足够空间 */
  text-align: center;
}

/* 防止按钮数量变化导致的宽度变化 */
.el-pagination .btn-prev,
.el-pagination .btn-next,
.el-pagination .number {
  width: 32px;
  min-width: 32px;
}
3. 优化分页数据加载
javascript 复制代码
// 在获取员工列表时预计算分页宽度
async getEmployeeList() {
  this.tableLoading = true
  // 保持分页组件可见,避免重新渲染时的布局偏移
  await this.$nextTick()
  
  try {
    const [err, data] = await to(getEmployeeList(this.queryParams))
    if (err) {
      this.$message.error(err.message || '获取员工列表失败')
      return
    }
    this.employeeList = data.rows
    this.total = data.total
    
    // 数据加载后确保分页布局稳定
    await this.$nextTick()
  } finally {
    this.tableLoading = false
  }
}
4. 添加过渡效果
css 复制代码
/* 分页组件过渡效果 */
.pagination-container {
  transition: all 0.2s ease-in-out;
}

.el-pagination {
  transition: opacity 0.2s ease;
}

/* 防止突然的布局变化 */
.el-pagination * {
  box-sizing: border-box;
}
5. 预渲染分页占位符
vue 复制代码
<template>
  <el-row class="pagination-container" type="flex" justify="end">
    <!-- 在加载时显示占位符,保持布局稳定 -->
    <div v-if="tableLoading" class="pagination-placeholder">
      <span class="total-placeholder">共 100 条</span>
      <div class="pager-placeholder">
        <span v-for="i in 5" :key="i" class="page-btn-placeholder"></span>
      </div>
    </div>
    <el-pagination
      v-else
      background
      layout="total, prev, pager, next"
      :total="total"
      :current-page="queryParams.page"
      @current-change="handleCurrentChange"
    />
  </el-row>
</template>

<style>
.pagination-placeholder {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 350px;
  justify-content: flex-end;
}

.total-placeholder {
  color: #909399;
  font-size: 13px;
}

.pager-placeholder {
  display: flex;
  gap: 4px;
}

.page-btn-placeholder {
  width: 32px;
  height: 32px;
  background: #f4f4f5;
  border-radius: 4px;
}
</style>
6. 优化分页组件配置
vue 复制代码
<el-pagination
  background
  :pager-count="5" <!-- 固定显示的页码按钮数量 -->
  layout="total, prev, pager, next"
  :total="total"
  :current-page="queryParams.page"
  :page-size="queryParams.pagesize"
  @current-change="handleCurrentChange"
/>
7.固定分页组件位置
css 复制代码
.pagination-container {
  height: 60px;
  min-height: 60px;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  position: sticky;
  bottom: 0;
  background: white;
  z-index: 10;
  border-top: 1px solid #ebeef5; /* 添加分割线 */
  margin-top: 0 !important; /* 确保没有外边距影响 */
}

预分配空间

css 复制代码
/* 为动态内容预分配空间 */
.el-table {
  min-height: 400px;
}

/* 确保头像列宽度固定 */
.el-table__header-wrapper th:first-child,
.el-table__body-wrapper td:first-child {
  width: 80px !important;
  min-width: 80px;
}

优化数据加载逻辑

javascript 复制代码
// 在获取数据前先设置加载状态
async getEmployeeList() {
  this.tableLoading = true
  // 强制重绘以确保骨架屏显示
  await this.$nextTick()
  
  try {
    const [err, data] = await to(getEmployeeList(this.queryParams))
    if (err) {
      this.$message.error(err.message || '获取员工列表失败')
      return
    }
    this.employeeList = data.rows
    this.total = data.total
  } finally {
    // 添加短暂延迟确保平滑过渡
    setTimeout(() => {
      this.tableLoading = false
    }, 100)
  }
}

添加过渡效果

vue 复制代码
<template>
  <transition name="fade" mode="out-in">
    <div v-if="tableLoading" key="skeleton" class="skeleton-placeholder">
      <!-- 骨架屏内容 -->
    </div>
    <el-table v-else key="table" :data="employeeList">
      <!-- 表格内容 -->
    </el-table>
  </transition>
</template>

<style>
.fade-enter-active, .fade-leave-active {
  transition: opacity 0.3s ease;
}
.fade-enter, .fade-leave-to {
  opacity: 0;
}
</style>
相关推荐
剑小麟5 小时前
maven中properties和dependencys标签的区别
java·前端·maven
掘金一周5 小时前
第一台 Andriod XR 设备发布,Jetpack Compose XR 有什么不同?对原生开发有何影响? | 掘金一周 10.30
前端·人工智能·后端
_大学牲5 小时前
Flutter 之魂 Dio🔥:四两拨千斤的网络库
前端·数据库·flutter
一枚前端小能手5 小时前
🌐 HTML DOM API全攻略(上篇)- 从基础操作到高级技巧的完整指南
前端·javascript·html
黄毛火烧雪下5 小时前
使用 Ant Design Pro CLI 快速创建前端中台项目
前端
呼叫69455 小时前
AggregateError:JavaScript 中的聚合错误处理
前端·javascript
一枚前端小能手5 小时前
🌐 HTML DOM API全攻略(下篇)- 高级接口与现代Web开发实践
前端·javascript·html
IT_陈寒5 小时前
React性能翻倍!3个90%开发者不知道的Hooks优化技巧 🚀
前端·人工智能·后端
CC码码5 小时前
前端2D地图和3D场景中的坐标系
前端·3d·js