vant加载组件不够用,那就手写一个loading组件

3,603 阅读8分钟

本文正在参加「金石计划 . 瓜分6万现金大奖」 

前言

  • 常网IT戳我呀!
  • 常网IT源码上线啦!
  • 如果是海迷,RED红发剧场版有需要可关注我主页公众号回“海贼王red”领取。
  • 已有专栏Vue吊打面试官,各位看官感兴趣可移步🚶。
  • 最近在开发过程中,发现vant官网提供的组件不足以满足需求,所以手写一个Loading组件。

每个组件都有自己的出场机会。

1.jpg

一、存在问题

在实战开发中,vant作为移动端UI的一把手,想必我们都很熟悉。

1.1 需求

最近开发过程中,想实现接口请求中,展示全屏loading,接口请求回来,关闭全屏loading。

  • 覆盖全屏的loading加载

  • 通过JavaScript控制显示与否

1.2 Loading 加载

于是,我们翻了一下官方组件,发现vant提供了Loading加载组件。

组件似乎只适用于上拉下拉刷新,展示的加载状态❌。

而我们想要的是全屏的Loading,所以不太适用。

2.gif

1.3 Toast 轻提示

官方还提供Toast组件。

这个效果正是我们所想要的,通过JavaScript让其创建显示。

// 自定义加载图标 
Toast.loading({
    message: '加载中...', 
    forbidClick: true, 
    loadingType: 'spinner', 
});

很可惜的一点是:官方并没有提供手动隐藏的API,只提供xxx毫秒后自动关闭。

也有可能有,但我没找到。

3.gif

既然官方提供的组件不合我意,那就手写一个Loading加载组件吧!

二、Loading组件诞生

html结构如下:

<template>
  <div>
    <div class="load">
      <!-- 中间的图案动效加载 -->
      <div class="sk-chase"></div>
      <!-- 文字加载 -->
      <span>加载中...</span>
    </div>
    <!-- 全屏遮罩层 -->
    <div class="full-screen"></div>
  </div>
</template>

我们的class:full-screen作为全屏的遮罩层,定位应是fixed固定定位,防止页面滑动上下滚动而下层无法遮罩。

class:load作为中间的动效加载,定位也应是fixed固定定位,屏幕居中,我们设置 left: 50% 和 top: 50% 现将子元素左上角移到父元素中心位置,然后再通过 translate 来调整子元素的中心点到父元素的中心。该方法可以不定宽高

class:sk-chase作为中间的图案动效加载,既然是动效,那离不开我们的animation,设置为infinite无限。

4.gif

加载是转圈的,那么可利用rotate(360deg)

而且我们的加载过程中大小会时大时小,可利用scale(0.4) -> scale(1)

三、代码如下

Loading.vue

<!--
 * @Description: Load加载 -- 组件
-->
<template>
  <div>
    <div class="load">
      <!-- 中间的图案动效加载 -->
      <div class="sk-chase">
        <div class="sk-chase-dot" v-for="(item, key) in 6" :key="key"></div>
      </div>
      <!-- 文字加载 -->
      <span>{{ title }}</span>
    </div>
    <!-- 全屏遮罩层 -->
    <div class="full-screen"></div>
  </div>
</template>

<script>
export default {
  name: "loading",
  props: {
    title: {
      type: String,
      default: "加载中...",
    },
  },
  data() {
    return {};
  },
};
</script>

<style scoped="scoped" lang="scss">
.full-screen {
  position: fixed;
  overflow: hidden;
  left: 0;
  top: 0;
  background: rgba(255, 255, 255, 0.3);
  width: 100%;
  height: 100%;
  z-index: 1;
}
.load {
  color: #dfdfdf;
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 120px;
  height: 120px;
  border-radius: 8px;
  background: rgba(74, 74, 74, 0.9);
  z-index: 2;
  span {
    position: absolute;
    bottom: 15%;
    left: 25%;
  }
}
.sk-chase {
  width: 40px;
  height: 40px;
  position: absolute;
  top: 20px;
  left: 35%;
  animation: sk-chase 2.5s infinite linear both;
}

.sk-chase-dot {
  width: 100%;
  height: 100%;
  position: absolute;
  left: 0;
  top: 0;
  animation: sk-chase-dot 2s infinite ease-in-out both;
}

.sk-chase-dot:before {
  content: "";
  display: block;
  width: 25%;
  height: 25%;
  background-color: #dfdfdf;
  border-radius: 100%;
  animation: sk-chase-dot-before 2s infinite ease-in-out both;
}

.sk-chase-dot:nth-child(1) {
  animation-delay: -1.1s;
}
.sk-chase-dot:nth-child(2) {
  animation-delay: -1s;
}
.sk-chase-dot:nth-child(3) {
  animation-delay: -0.9s;
}
.sk-chase-dot:nth-child(4) {
  animation-delay: -0.8s;
}
.sk-chase-dot:nth-child(5) {
  animation-delay: -0.7s;
}
.sk-chase-dot:nth-child(6) {
  animation-delay: -0.6s;
}
.sk-chase-dot:nth-child(1):before {
  animation-delay: -1.1s;
}
.sk-chase-dot:nth-child(2):before {
  animation-delay: -1s;
}
.sk-chase-dot:nth-child(3):before {
  animation-delay: -0.9s;
}
.sk-chase-dot:nth-child(4):before {
  animation-delay: -0.8s;
}
.sk-chase-dot:nth-child(5):before {
  animation-delay: -0.7s;
}
.sk-chase-dot:nth-child(6):before {
  animation-delay: -0.6s;
}

@keyframes sk-chase {
  100% {
    transform: rotate(360deg);
  }
}

@keyframes sk-chase-dot {
  80%,
  100% {
    transform: rotate(360deg);
  }
}

@keyframes sk-chase-dot-before {
  50% {
    transform: scale(0.4);
  }
  100%,
  0% {
    transform: scale(1);
  }
}
</style>

于是我们在外层引入Loading.vue组件。

<Loading v-show="fullscreenLoading" title="提交中..." />

async getData(){
    this.fullscreenLoading = true
    const res = await get()
    this.fullscreenLoading = false
}

看下效果。

5.png

一个简简单单的Loading组件便大功告成。

Loading组件已放在Github,点我下载👈

四、水平垂直居中

我们在上面有说到加载效果要在屏幕的居中显示。

那顺便来道经典的面试题,如何实现水平垂直居中?

4.1 绝对定位(left、top50%、translate)

利用绝对定位,设置 left: 50% 和 top: 50% 现将子元素左上角移到父元素中心位置,然后再通过 translate 来调整子元素的中心点到父元素的中心。该方法可以不定宽高

注意这里启动了3D硬件加速哦 会增加耗电量的。

.father {
  position: relative;
}
.son {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%); 
  /*
    自身宽度一半,等同于margin-left: -50px; 等同于margin-top: -50px;
    想想如不设置transform偏移的话,son元素整体会向右下
    因为没有减掉son元素自身的宽高呀
  */
}

父相re子绝ab。

4.2 绝对定位(全部方向0、margin: auto)

利用绝对定位,子元素所有方向都为 0 ,将 margin 设置为 auto ,由于宽高固定,对应方向实现平分,该方法必须盒子有宽高

.father {
  position: relative;
}
.son {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0px;
  margin: auto;
  height: 100px;
  width: 100px;
}

4.3 绝对定位(l、t50%、m-t-l宽高一半)

利用绝对定位,设置 left: 50% 和 top: 50% 现将子元素左上角移到父元素中心位置,然后再通过 margin-left 和 margin-top 以子元素自己的一半宽高进行负值赋值。该方法必须定宽高

感觉看了圣杯模式的视频,现在看margin负值觉得很简单、好理解了😁。

关于圣杯模式、两栏布局后期出篇文章。

.father {
  position: relative;
}
.son {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 200px;
  height: 200px;
  margin-left: -100px;
  margin-top: -100px;
}

4.4 flex横扫千军

利用 flex ,最经典最方便的一种了,不用解释,定不定宽高无所谓的。

其实还有很多方法,比如 display: grid 或 display: table-cell 来做,有兴趣点击下面这篇文章可以了解下:你能实现多少种水平垂直居中的布局(定宽高和不定宽高)

.father {
  display: flex;
  justify-content: center;
  align-items: center;
}

4.5 总结

4.5.1 水平居中

  • 对于水平居中,我们应该先考虑,哪些元素有自带的居中效果,最先想到的应该就是 text-align:center 了,但是这个只对行内内容有效,所以我们要使用 text-align:center 就必须将子元素设置为 display: inline; 或者 display: inline-block; ;
  • 其次就是考虑能不能用margin: 0 auto;因为这都是一两句代码能搞定的事,实在不行就是用绝对定位去实现了。
  • 移动端能用flex就用flex,简单方便,灵活并且功能强大,无愧为网页布局的一大利器!

4.5.2 垂直居中

  • 对于垂直居中,最先想到的应该就是 line-height 了,但是这个只能用于行内内容;
  • 其次就是考虑能不能用vertical-align: middle;不过这个一定要熟知原理才能用得顺手,建议看下vertical-align和line-height的基友关系
  • 然后便是绝对定位,虽然代码多了点,但是胜在适用于不同情况;
  • 移动端兼容性允许的情况下能用flex就用flex

4.5.3 水平垂直居中

  • 一般情况下,水平垂直居中,我们最常用的就是绝对定位加负边距了,缺点就是需要知道宽高,使用transform倒是可以不需要,但是兼容性不好(ie9+);
  • 其次就是绝对居中,绝对定位设置top、left、right、bottom为0,然后margin:auto; 让浏览器自动平分边距以达到水平垂直居中的目的;
  • 如果是行内/行内块级/图片这些内容,可以优先考虑line-height和vertical-align 结合使用,不要忘了还有text-align ,这个方法代码其实不多,就是理解原理有点困难,想要熟练应对各种情况还需好好研究;
  • 移动端兼容性允许的情况下能用flex就用flex。

还记得刚毕业那会,死记硬背下垂直居中的答案,如今,已经能够靠平时实践而回答。
背而不理解,无用。(但面试救救急该背还是得背🉑,日后慢慢实践理解即可)
唯有实践出真理。

五、其他加载效果

能不能再来亿点点,让项目经理选选。

16.jpeg

5.1

定义一个 div元素,设置边长为 40px 背景白色的正方形,然后设置循环翻转动画实现该加载效果动画。

perspective 属性定义 3D 元素距视图的距离。

6.gif

5.2

两个子元素 div 实现半透明的圆形,设置绝对定位重叠在一起,然后设置相同的动画通过不同的延迟时间交替放大缩小。

7.gif

5.3

在类名为 spinner元素下有五个 div 实现的长方形元素,设置Y轴的缩放,通过不同的延迟时间来达到依次变化的效果。

8.gif

5.4

两个子元素实现白色的方块,添加动画属性,在X轴和Y轴分别设置移动距离和缩放,通过不同的延迟时间来分离他们,rorate实现围绕中心旋转。

9.gif

5.5

两个实心圆形围绕中心做循环的缩放旋转运动,因为不同的延迟时间来达到它们同一时间呈现相反的表现。

10.gif

5.6

三个实色圆形横向排列,线性动画 scale从0到1,通过给它们不同的延迟时间,呈现依次交替的效果。

11.gif

5.7

整个加载效果由12个圆心组成,设置不同的旋转让它们呈现圆形环绕,然后设置不同的延迟时间,让它们做缩放运动。

12.gif

5.8

九个方块通过 grid布局,形成横向纵向分别三块,设置不同的延迟时间让它们做线性 3D 缩放运动。

13.gif

5.9

四个等边方块形成一个正方形,整个旋转45度,然后每个方块通过不同的延迟时间,沿X轴做翻转动画。设置perspective是为了有3D的效果。

14.gif

以上例子都放在Github,点我下载👈

后记

以上的这些案例原理都是通过不同的延迟时间,给元素或子元素设置包括X、Y和Z轴上的移动旋转,以及缩放来达到动画的效果。

17.jpeg

虽然我们看到正在loading中会很烦,我这暴脾气,但却是交互中必不可少的良好体验。

👍 如果对您有帮助,您的点赞是我前进的润滑剂。

以往推荐

Vue3的响应式到底比Vue2优雅在哪

靓仔,说一下keep-alive缓存组件后怎么更新及原理?

面试官问我watch和computed的区别以及选择?

面试官问我new Vue阶段做了什么?

前端仔,快把dist部署到Nginx上

多图详解,一次性啃懂原型链(上万字)

Vue-Cli3搭建组件库

Vue实现动态路由(和面试官吹项目亮点)

项目中你不知道的Axios骚操作(手写核心原理、兼容性)

VuePress搭建项目组件文档

原文链接

juejin.cn/post/716901…