浅谈前端性能优化🚀🚀

326 阅读8分钟

前言

前几天约到了小鹅通的面试。开场还是先自我介绍,之后面试官问和别人做项目的时候有什么亮点。当时回答的时候感觉就是被面试官牵着走,一方面是自己表达能力太差了无法打动面试官~,另一方面还是自己实力太不行了。躺了一天后,决定好好写篇文章了梳理梳理扫掉自己玉玉的心情。

12344ee135453bf8fc93850ff896ed8.jpg

瀑布流

瀑布流布局(Masonry Layout)是一种常见的网页布局模式,它的特点是不同高度的项目按照从左到右、从上到下的顺序排列,形成类似于砖墙或瀑布的效果。这种布局方式特别适合展示图片、卡片式内容等,因为它能够有效地利用空间,并且提供了一种动态而吸引人的视觉效果。对于触屏设备非常友好,通过上滑动浏览,交互方式更符合直觉。话不多说我们看看小红书的页面来真实感受下瀑布流布局吧!!

759a3035f5e9f4f8d5c94ebad3ec5d4.jpg

纯css手写

我们以vue3为例来实现下瀑布流吧!!

<template>  
<div class="page-main">  
    <div class="card">  
      <div class="card-item" v-for="(item,index) in cardList" :key="index" :style="   [{background: item.color},{height: item.height},{lineHeight: item.height}]">  
      <p class="text">{{index}}</p>  
      </div>  
    </div>  
</div>  
</template>  
<script setup>  
import {ref} from 'vue'  
const cardList = ref([ // 模拟数据  
{ color: '#FCCF0A', height: '120px' },  
{ color: '#FF9F1C', height: '180px' },  
{ color: '#3A86FF', height: '150px' },  
{ color: '#8338EC', height: '100px' },  
{ color: '#38B000', height: '200px' },  
{ color: '#FF006E', height: '130px' },  
{ color: '#FFBE0B', height: '160px' },  
{ color: '#FB5607', height: '110px' },  
{ color: '#FF006E', height: '140px' },  
{ color: '#3A86FF', height: '170px' },  
{ color: '#8338EC', height: '90px' },  
{ color: '#38B000', height: '190px' },  
  
])  
</script>  
<style lang="scss" scoped>  
 .page-main{  
  background: #ffffff;  
  min-height:100vh;  
  padding: 0 30px;  
  .card{  
  column-count: 3; // 定义三列  
  column-gap: 20px; // 列与列的距离为20px  
    .card-item{  
      text-align: center;  
      width: 216px;  
      border-radius: 16px;  
      grid-row-start: auto;  
      margin-bottom: 20px;  
      break-inside: avoid; // 不被截断  
    }  
}  
}  
</style>

首先呢这段代码先是用v-for循环渲染列表数据,遍历数组或对象。话不多说看看下边的DOM结构吧!!

image.png

然后在js里边写了如下代码对cardList数组进行响应式定义

import {ref} from 'vue'  
const cardList = ref([ // 模拟数据  
{ color: '#FCCF0A', height: '120px' },  
{ color: '#FF9F1C', height: '180px' },  
{ color: '#3A86FF', height: '150px' },  
{ color: '#8338EC', height: '100px' },  
{ color: '#38B000', height: '200px' },  
{ color: '#FF006E', height: '130px' },  
{ color: '#FFBE0B', height: '160px' },  
{ color: '#FB5607', height: '110px' },  
{ color: '#FF006E', height: '140px' },  
{ color: '#3A86FF', height: '300px' },  
{ color: '#8338EC', height: '90px' },  
{ color: '#38B000', height: '190px' },  
])

想要实现瀑布流css是重头大戏!!我们来看看吧!

<style lang="scss" scoped>  
  .page-main{  
      background: #ffffff;  
      min-height:100vh;  
      padding: 0 300px;  
  .card{  
    column-count: 3; // 定义三列  
    column-gap: 20px; // 列与列的距离为20px  
  .card-item{  
    text-align: center;  
    width: 216px;  
    border-radius: 16px;  
    grid-row-start: auto;  
    margin-bottom: 20px;  
    break-inside: avoid; // 不被截断  
  }  
 }  
}  
</style>

从代码可以看出该样式是嵌套型的非常美观,Vue 支持多种预处理器,其中最常用的是 SCSS 和 Sass。你可以在 标签中指定 lang="scss" 或 lang="sass",然后使用嵌套语法。首先呢嵌套样式它允许你在父级选择器内部定义子级选择器的样式。这种方式不仅使代码更具可读性,还能提高开发效率和维护性。嵌套样式模仿了 HTML 的层级结构,使得样式规则与 DOM 结构更加一致。我们可以通过嵌套直观地看到哪些样式属于哪个元素,减少了查找和理解代码的时间。在原生 CSS 中,如果我们有多个嵌套的子元素,通常需要重复书写父级选择器。这不仅增加了代码量,还容易出错。使用嵌套样式可以避免这种冗余。 我们来解析下css代码吧!!

page-main background: #ffffff;:设置 page-main 的背景颜色为白色。这使得页面的主容器有一个干净的白色背景。

min-height: 100vh; :确保 page-main的最小高度为视口高度(即整个屏幕的高度)。这使得页面内容至少占满整个屏幕的高度,适用于需要全屏显示的页面布局。

padding: 0 300px, :为 page-main 设置左右内边距,使其内容与页面边缘保持 300px 的距离。注意,这个值非常大,可能会导致页面内容在较小的屏幕上显示不完全。建议根据实际需求调整这个值,例如改为 padding: 0 30px;以确保在不同设备上都能有合理的内边距。

对于 .card

column-count: 3; :使用 CSS 的 column-count 属性将 .card 内的内容分成三列。这个属性会将内容按顺序从上到下、从左到右填充每一列。适用于创建多列布局,如卡片列表或文章列表。 column-gap: 20px; :设置列与列之间的间距为 20px,确保每列之间有一定的间隔,避免内容过于拥挤。这个值可以根据设计需求进行调整,以达到最佳的视觉效果。

对于.card-item

text-align: center; :将 .card-item 内的文本居中对齐,使卡片内容在视觉上更加平衡。这对于标题、按钮等元素尤其有用,确保它们在卡片中居中显示。

width: 216px; :设置每个 .card-item 的宽度为 216px。这个宽度是固定的,确保每个卡片的大小一致。你可以根据设计需求调整这个值,或者使用百分比宽度来实现响应式设计。 border-radius: 16px; :为 .card-item 添加圆角边框,使其看起来更柔和,提升用户体验。圆角的半径为 16px,可以根据设计需求调整。

margin-bottom: 20px; :为每个 .card-item 设置底部外边距,使卡片之间有一定的垂直间距,避免内容过于紧凑。这个值可以根据设计需求进行调整,以达到最佳的视觉效果。

break-inside: avoid; :防止 .card-item在列中被分页或打断,确保每个卡片作为一个整体显示。这对于多列布局非常重要,避免卡片内容被拆分到不同的列中,保持卡片的完整性。

image.png

flex布局+计算元素高度

我们可以这样想一下,如果我们将页面元素摆放设置为4列,如果只有四个元素(高度不一样)那么正好一列一个元素。如果出现了第5个元素呢?那么就应该把第五个元素放在高度最低的元素下面。顺着这个思路我们就能实现瀑布流。话不多说看下代码!!

    <template>
  <div  class="page-main">
    <div class="card">
        <div class="coloum1" >
          <div class="card-item" v-for="(item,index) in cardList1" :key="index" :style="[{background: item.color},{height: item.height},{lineHeight: item.height}]" :class="{visibles: isVisibility}">
              <p class="text">{{item.num}}</p>
          </div>
        </div>
        <div class="coloum2">
           <div class="card-item" v-for="(item,index) in cardList2" :key="index" :style="[{background: item.color},{height: item.height},{lineHeight: item.height}]" :class="{visibles: isVisibility}">
              <p class="text">{{item.num}}</p>
          </div>
        </div>
         <div class="coloum3">
           <div class="card-item" v-for="(item,index) in cardList3" :key="index" :style="[{background: item.color},{height: item.height},{lineHeight: item.height}]" :class="{visibles: isVisibility}">
              <p class="text">{{item.num}}</p>
          </div>
        </div>
    </div>
  </div>
</template>
    
    <script setup>
import {ref,onMounted, reactive,nextTick} from 'vue'
const cardList = reactive([ // 测试数据
  {
    num: '0',
    color:  '#FCCF0A',
    height: '100px',

  }, 
  ...测试数据
])

const isVisibility = ref(true)
// 由于渲染时候对数据的两次赋值,则会出现一次闪现,需要防抖

onMounted(()=>{
  equallyCard()
  nextTick(()=>{
    caLFlex()
  }).then(()=>{
    isVisibility.value = true
  })
})

const cardList1 = ref([]) // 各列的数据
const cardList2 = ref([])
const cardList3 = ref([])

function equallyCard(){
  // 平分数据,确保页面上遍历卡片dom的真实顺序与平分的一致 document.querySelectorAll('.card-item'))
  let num = parseInt(cardList.length/3)
  cardList.forEach((item,index)=>{
    if(index<num){
       cardList1.value.push(item)
       return
    }
    if(index<2*num){
       cardList2.value.push(item)
        return
    }
    cardList3.value.push(item)
  })
}


function caLFlex(){
  let arr1 = [] // 第一列的值
  let arr2 = [] // 第二列的值
  let arr3 = [] // 第二列的值
  let heightArry_1 = [] // 第一列的卡片高度
  let heightArry_2 = [] // 第二列的卡片高度
  let heightArry_3 = [] // 第二列的卡片高度
  Array.from(document.querySelectorAll('.card-item')).forEach((item,index) =>{
      if(index === 0){ // 第一行中的元素无需判断,直接加到新的数组中
          heightArry_1.push(item.offsetHeight)
          arr1.push(cardList[index])
          return
      }
      if(index === 1){
          heightArry_2.push(item.offsetHeight)
           arr2.push(cardList[index])
           return
      }
      if(index === 2){
          heightArry_3.push(item.offsetHeight)
          arr3.push(cardList[index])
          return
      }
     const heightTotal_1 = heightArry_1.length ? Array.from(heightArry_1).reduce(( acc, cur ) => acc + cur) : 0 // 第一列的总高度
     const heightTotal_2 = heightArry_2.length ? Array.from(heightArry_2).reduce(( acc, cur ) => acc + cur) : 0 // 第二列的总高
     const heightTotal_3 = heightArry_3.length ? Array.from(heightArry_3).reduce(( acc, cur ) => acc + cur) : 0 // 第三列的总高度

     // 找到最小值
     let minNumber = Math.min(heightTotal_1,heightTotal_2,heightTotal_3)
     switch (minNumber) {
       case heightTotal_1:
          heightArry_1.push(item.offsetHeight)
          arr1.push(cardList[index])
         break
      case heightTotal_2:
         heightArry_2.push(item.offsetHeight)
          arr2.push(cardList[index])
         break
      case heightTotal_3:
         heightArry_3.push(item.offsetHeight)
          arr3.push(cardList[index])
         break
     }
  })

// 重新将数据赋值给各列数组
  cardList1.value = arr1
  cardList2.value = arr2
  cardList3.value = arr3
}

    
    
   <style lang="scss" scoped>
.page-main{
    background: #ffffff;
    height:100vh;
    overflow: hidden;
    padding: 0 30px;
    .card{
        display: flex;
        flex-direction: row;
        justify-content: space-around;
        .card-item{
          visibility: hidden;
          margin-bottom: 20px;
          text-align: center;
          width: 216px;
          border-radius: 16px;
        }
        .visibles {
          visibility: visible;
        }
    }
}
</style>
        

image.png

我们来解析下代码吧!! 从template的代码里边可以看到我们定义了三列,并且呢每一列都用v-for来遍历数组。 我们再来看看js代码这里边有一个equallyCard函数 这个函数通过forEach遍历将可以将cardList卡片分成三份(三个list...)值得注意的是并不是等分。本质就是先按照序列来排前1/3放在list1,1/3——2/3放在list2,最后2/3放在list3。但是这种排列也只是随机排。因此我们可以调用caLFlex函数。这个目的是通过计算每列的总高度,将卡片分配到最短的一列中,以实现视觉上的平衡布局。

好啦今天的分享就到这里啦!