preface
There was a new project recently where the UI guy somehow found a GIF and threw it into blue Lake,
Said as a global page loading, but I thought about it, or choose to draw one.
At first, I thought about using SVG and Canvas, but finally I chose CSS3 + JS to achieve this effect.
GIF has many disadvantages. As for why SVG and Canvas are excluded, it is because CSS3 + JS has stronger controllability.
Whether it is size or color, or responsive (my project goes VH, VW) that set to fit;
The effect
GIF provided by UI guy
Codesandbox preview
- Support ring color change and overall display size
- Support in
loading
Displays text at the bottom and controls its style
Implementation approach
This one mainly uses these few points to achieve the full effect;
flex
andposition
To layout- Color inheritance of pseudo-classes (
currentColor
) - Border combined with rounded corners to achieve the ring
- With the
transform
andanimation
To achieve the whole transition
Now that we know how to implement the effect, all that’s left is the function points we need to implement;
Because it is mobile oriented, these general things should also be considered
- Mask layer is controllable
- Prevent clicks from penetrating the scroll
body
- The component supports function method calls
The source code
Loading.vue
<template>
<div id="loading-wrapper">
<div class="loading-ring" :style="ringStyle">
<div class="outer" />
<div class="middle" />
<div class="inner" />
</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(e) {
// Disallow body scrolling
console.log(e);
e.preventDefault();
e.stopPropagation();
}
},
mounted() {
document
.querySelector("body")
.addEventListener("touchmove".this.preventDefault);
},
destroyed() {
document
.querySelector("body")
.removeEventListener("touchmove".this.preventDefault); }};</script>
<style lang="scss" scoped>#loading-wrapper { position: fixed; left: 0; top: 0; height: 100vh; width: 100vw; Background: rgba(0, 0, 0, 0.25); 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 * 0.6 (100%); &::after {animation: anticlockwise 1.5s infinite linear; }}. Middle {width: calc(100% * 0.8); Height: calc * 0.8 (100%); &:: After {animation: 1.5s infinite linear; } } } .text { color: #fff; font-size: 14px; padding: 30px; width: 250px; text-align: center; } } @keyframes clockwise { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } @keyframes anticlockwise { 0% { transform: rotate(0deg); } 100% { transform: rotate(-360deg); }}</style>
Copy the code
index.js
import Loading from "./Loading.vue";
// to maintain the instance, singleton mode
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;
Copy the code
Options and Usage
options
text: { // This is not empty but displays text under loading
type: String.default: ""
},
textStyle: { // Loading text style, color, and font size
type: Object.default: function() {
return {
fontSize: "14px".color: "#fff"}; }},ringStyle: { // The size of the outermost ring is proportional to the size of the inner ring.
type: Object.default: function() {
return {
width: "100px".height: "100px".color: "#407af3"}; }}Copy the code
usage
Use in the main entrance can be used globally
In addition to normal import usage, function calls are supported and a $loading is mounted.
this.$loading.show({
text: "loading".textStyle: {
fontSize: "18px".color: "#f00"}});let st = setTimeout((a)= > {
clearTimeout(st);
this.$loading.hide();
}, 1000);
Copy the code
conclusion
The props pass is not an incremental merge (recursive assignment for each key), but a shallow copy of the merge.
For the component function of the comprehensiveness, extensibility, size need to weigh;
At this point, we have a widget that our business needs, and all the functions we need.
If there is something wrong, please leave a message and we will correct it in time.