vue 如何实现一个骨架屏(封装一个骨架屏组件)

309 阅读3分钟

如何实现一个骨架屏

总结骨架屏的思路:

  1. 比如一种场景,数据请求不及时,就显示骨架屏的内容。
  2. 骨架屏的本质就是一个div或者是其他标签,设置背景色和动画,让他进行位移,让用户产生“数据还在加载中”的这个印象

html+css实现

我们先用一个html文件+css来模拟实现,然后再去vue里面创建组件

第一步,创建div盒子结构下

  <div class="father">
    123
    <div class="box">
    </div>
  </div>

第二步,设置好基本的样式

    .father {
      width: 200px;
      height: 200px;
      background-color: gray;
    }
    .box {
      width: 200px;
      height: 200px;
    }

浏览器效果如图所示:

image-20220825180537620

第三步,我们给孩子添加如下属性:

   .box {
      width: 200px;
      height: 200px;
      background: linear-gradient(to left,
          rgba(255, 255, 255, 0) 0,
          rgba(255, 255, 255, 0.3) 50%,
          rgba(255, 255, 255, 0) 100%);
    }

如果你记不住background: linear-gradient是干嘛的,请参考我的这篇文章,

第四步,我们定义一个动画

  @keyframes shan {
      0% {
        left: -100%;
      }
​
      100% {
        left: 100%;
      }
    }

第五步,我们使用这个动画

    .box {
      
+      animation: shan 1.5s ease 0s infinite;
      
      width: 200px;
      height: 200px;
      background: linear-gradient(to left,
          rgba(255, 255, 255, 0) 0,
          rgba(255, 255, 255, 0.3) 50%,
          rgba(255, 255, 255, 0) 100%);
      
    }

到这一步,我们的动画还是没有反应,这是为什么呢?我们下一步,设置了就有了

第六步,我们设置子绝父相

.father {
+      position: relative;
+      width: 200px;
      height: 200px;
      background-color: gray;
    }
​
    .box {
+      position: absolute;
      top: 0;
      width: 200px;
      height: 200px;
      animation: shan 1.5s ease 0s infinite;
      background: linear-gradient(to left,
          rgba(255, 255, 255, 0) 0,
          rgba(255, 255, 255, 0.3) 50%,
          rgba(255, 255, 255, 0) 100%);
    }

设置完成后,我们发现动画成功了。

但是,为什么设置了定位以后,动画才能够生效呢?小编 也不知道,能不能帮俺分析一下呢?我感觉还是没能掌握 骨架屏的精髓

第七步,我们设置一个属性,让他进行倾斜,参数1是在x轴上面倾斜,参数2是在y轴上倾斜

    .box {
      position: absolute;
      top: 0;
      width: 200px;
      height: 200px;
      animation: shan 1.5s ease 0s infinite;
      background-color: pink;
      background: linear-gradient(to left,
          rgba(255, 255, 255, 0) 0,
          rgba(255, 255, 255, 0.3) 50%,
          rgba(255, 255, 255, 0) 100%);
+      transform: skew(-30deg);
    }

完整代码如下:

<!DOCTYPE html>
<html lang="en"><head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .father {
      position: relative;
      width: 200px;
      height: 200px;
      background-color: gray;
    }
    .box {
      position: absolute;
      top: 0;
      width: 200px;
      height: 200px;
      animation: shan 1.5s ease 0s infinite;
      background-color: pink;
      background: linear-gradient(to left,
          rgba(255, 255, 255, 0) 0,
          rgba(255, 255, 255, 0.3) 50%,
          rgba(255, 255, 255, 0) 100%);
      transform: skew(-30deg);
    }


    @keyframes shan {
      0% {
        left: -100%;
      }
​
      100% {
        left: 100%;
      }
    }
  </style>
</head>

<body>
  <div class="father">
    123
    <div class="box">
    </div>
  </div>
</body>

</html>

vue组件封装的实现

第一步,template模板,

  • 暴露width属性 和 height属性,骨架屏的宽高会变化
  • 暴露animate属性,用于“是否开启动画shan”
  • 暴露bg属性,用于父盒子的背景颜色
  <div
    class="xtx-skeleton"
    :style="{ width: width, height:height }"
    :class="{ shan: animated }"
  >
    <!-- 1 盒子-->
    <div class="block" :style="{ backgroundColor: bg }"></div>
    <!-- 2 闪效果 xtx-skeleton 伪元素 --->
  </div>

第二步,script部分

<script>
export default {
  name: 'XtxSkeleton',
  // 使用的时候需要动态设置 高度,宽度,背景颜色,是否闪下
  props: {
    bg: {
      type: String,
      default: '#efefef'
    },
    width: {
      type: String,
      default: '100px'
    },
    height: {
      type: String,
      default: '100px'
    },
    animated: {
      type: Boolean,
      default: false
    }
  }
}
</script>

第三步,样式部分

和之前的类似,只是,这里用after伪元素代替之前的子盒子

<style scoped lang="less">
.xtx-skeleton {
  display: inline-block;
  position: relative;
  overflow: hidden;
  vertical-align: middle;
  .block {
    width: 100%;
    height: 100%;
    border-radius: 2px;
  }
}
.shan {
  &::after {
    content: "";
    position: absolute;
    animation: shan 1.5s ease 0s infinite;
    top: 0;
    width: 50%;
    height: 100%;
    background: linear-gradient(
      to left,
      rgba(255, 255, 255, 0) 0,
      rgba(255, 255, 255, 0.3) 50%,
      rgba(255, 255, 255, 0) 100%
    );
    transform: skewX(-45deg);
  }
}
@keyframes shan {
  0% {
    left: -100%;
  }
  100% {
    left: 120%;
  }
}
</style>

使用这个组件 假设这里有一个组件使用,v-if判断,一定是有这个goods数据,才会渲染组件,没有数据,ul不会渲染,就显示skeleton骨架屏组件

+  <ul v-if="goods && goods.length" class="goods-list">
    <li v-for="item in goods" :key="item.id">
      <RouterLink :to="`/product/${item.id}`">
        <img v-lazy="item.picture" alt="">
        <p class="name ellipsis">{{item.title}}</p>
        <p class="price">&yen;100</p>
      </RouterLink>
    </li>
  </ul>
+  <home-skeleton v-else bg="#f0f9f4"/>