Vue 折騰記 - (15) 搗鼓一箇中規中矩loading元件

CRPER發表於2019-04-01

前言

最近有一個新的專案,UI大佬不知道從哪裡找來了一張GIF丟到藍湖,

說作為全域性的頁面loading ,但是想了想,還是還是自己畫一個。

一開始想過用svg,canvas;最終還是選擇了css3+js來實現這個效果;

gif的缺點挺多,至於為什麼又排除了svgcanvas

是因為css3+js可控性更強,不管是大小還是顏色,還是響應式(我的專案走的vh,vw)那套來適配;

可以藉助打包外掛,達到loading的大小適配;


效果

UI大佬提供的GIF

Vue 折騰記 - (15) 搗鼓一箇中規中矩loading元件

實現的效果【線上codesandbox預覽】

Vue 折騰記 - (15) 搗鼓一箇中規中矩loading元件

  • 支援環的顏色改變
  • 支援在loading底部顯示文字並控制其樣式

實現思路

這個東東主要用了這麼幾個要點來實現完整的效果;

flexposition來佈局

  • 偽類的顏色繼承(currentColor)
  • 邊框結合圓角實現環
  • 用了transformanimation來實現了整個過渡

效果知道怎麼實現了,剩下的就是我們需要實現的功能點了;

因為是面向移動端的,所以這些常規的東東也要考慮下

  • 遮罩層可控
  • 防止點選穿透滾動body
  • 支援函式方法呼叫

原始碼

  • Loading.vue
<template>
  <div id="loading-wrapper">
    <div class="loading-ring" :style="ringStyle">
      <div class="outer"></div>
      <div class="middle"></div>
      <div class="inner"></div>
    </div>
    <div class="text" :style="textStyle" v-if="text">{{ text }}</div>
  </div>
</template>

<script>
export default {
  name: "Loading",
  props: {
    text: {
      type: String,
      default: ""
    },
    textStyle: {
      type: Object,
      default: function() {
        return {
          fontSize: "14px",
          color: "#fff"
        };
      }
    },
    ringStyle: {
      type: Object,
      default: function() {
        return {
          width: "100px",
          height: "100px",
          color: "#407af3"
        };
      }
    }
  },
  methods: {
    preventDefault() {
      // 禁止body的滾動
      document.querySelector("body").addEventListener("touchmove", function(e) {
        e.preventDefault();
        e.stopPropagation();
      });
    }
  },
  mounted() {
    this.preventDefault();
  },
  destroyed() {
    document
      .querySelector("body")
      .removeEventListener("touchmove", function(e) {
        e.preventDefault();
        e.stopPropagation();
      });
  }
};
</script>

<style lang="scss" scoped>
#loading-wrapper {
  position: fixed;
  left: 0;
  top: 0;
  height: 100vh;
  width: 100vw;
  background-color: rgba(0, 0, 0, 1);
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  .loading-ring {
    position: relative;
    width: 200px;
    height: 200px;
    .outer,
    .inner,
    .middle {
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
      color: currentColor;
      &::after {
        content: "";
        display: block;
        width: 100%;
        height: 100%;
        border-radius: 100%;
        border-left: 10px solid currentColor;
        border-right: 10px solid currentColor;
        border-top: 10px solid currentColor;
        border-bottom: 10px solid transparent;
      }
    }

    .outer {
      width: 100%;
      height: 100%;
      &::after {
        animation: anticlockwise 1.5s infinite linear;
      }
    }
    .inner {
      width: calc(100% * 0.6);
      height: calc(100% * 0.6);
      &::after {
        animation: anticlockwise 1.5s infinite linear;
      }
    }
    .middle {
      width: calc(100% * 0.8);
      height: calc(100% * 0.8);
      &::after {
        animation: clockwise 1.5s infinite linear;
      }
    }
  }

  .text {
    color: #fff;
    font-size: 14px;
    padding: 20px;
  }
}

@keyframes clockwise {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}
@keyframes anticlockwise {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(-360deg);
  }
}
</style>

複製程式碼
  • index.js
import Loading from "./Loading.vue";
// 來保持例項,單例模式
let instance;
let el;

Loading.install = function(Vue, options = {}) {
  const defaultOptions = {
    text: "",
    textStyle: {
      fontSize: "14px",
      color: "#fff"
    },
    ringStyle: {
      width: "100px",
      height: "100px",
      color: "#407af3"
    },
    ...options
  };
  Vue.prototype.$loading = {
    show(options = {}) {
      if (!instance) {
        let LoadingInstance = Vue.extend(Loading);
        el = document.createElement("div");
        document.body.appendChild(el);
        instance = new LoadingInstance({
          propsData: { defaultOptions, ...options }
        }).$mount(el);
      } else {
        return instance;
      }
    },
    hide() {
      if (instance) {
        document.body.removeChild(document.getElementById("loading-wrapper"));
        instance = undefined;
      }
    }
  };
};

export default Loading;


複製程式碼

選項及用法

選項

    text: {  // 這個不為空就在loading下面顯示文字
      type: String,
      default: ""
    },
    textStyle: {  // loading text 的樣式,顏色及字型大小
      type: Object,
      default: function() {
        return {
          fontSize: "14px",
          color: "#fff"
        };
      }
    },
    ringStyle: {  // 最外環的大小,內二環是比例換算的(百分比)
      type: Object,
      default: function() {
        return {
          width: "100px",
          height: "100px",
          color: "#407af3"
        };
      }
    }

複製程式碼

用法

在主入口use一下便可全域性使用

除了常規的引入使用,還支援函式呼叫,掛載了一個$loading


this.$loading.show({
        text: "loading",
        textStyle: {
          fontSize: "18px",
          color: "#f00"
        }
      });
      
let st = setTimeout(() => {
        clearTimeout(st);
        this.$loading.hide();
    }, 1000);

複製程式碼

總結

props的傳遞沒有做增量合併(遞迴每個key賦值),直接淺複製過的

對於元件功能的概而全,擴充性,大小需要自己權衡;

到這裡,我們業務需要的一個小元件,該有的功能都有了。

有不對之處請留言,會及時修正。。。

相關文章