小知识,大挑战!本文正在参与“程序员必备小知识”创作活动
TIP 👉 近水楼台先得月,向阳花木易为春。宋·俞文豹《清夜录》
前言
在我们日常项目开发中,我们经常会写一些旋转器,所以封装了这款旋转器组件组件。Loading 旋转器组件(IE9不支持)
属性
1. spinner
- 旋转器类型
- 值为字符串类型
- default: 默认类型,圆环上有一个旋转球
- circles: 多个圆点旋转
- bubbles: 由小到大的气泡旋转
- spiral: 圆弧旋转
- wavedots: 圆点波浪
2. theme
- 主题色
- 值为字符串类型
- default: 灰色(默认)
- white: 白色
- base: 主题色,与$base-color保持一致
3. size
- 大小
- 值为字符串类型
- s: 小(18px)
- m: 中(28px,默认)
- l: 大(38px)
示例
<template>
<div class="spinner-wrap">
<BaseSpinner spinner="circles"></BaseSpinner>
</div>
</template>
<script>
import BaseSpinner from '@/components/base/spinner'
export default {
name: 'SpinnerDemo',
components: {
BaseSpinner
}
}
</script>
<style lang="scss" scoped>
.spinner-wrap{
text-align: center;
}
</style>
实现spinner.vue
<template>
<component :is="spinnerView" :theme="theme" :size="size"></component>
</template>
<script>
const SPINNERS = {
// 默认类型,圆环上有一个旋转球
DEFAULT: {
render (createElement) {
let theme = this.$attrs.theme
let size = this.$attrs.size
return createElement('i', {
attrs: {
class: `spinner-loading loading-default ${theme ? 'theme-' + theme : ''} ${size ? 'size-' + size : ''}`
},
style: {
backgroundColor: this.$attrs.color
}
})
}
},
// 由小到大的气泡旋转
BUBBLES: {
render (createElement) {
let theme = this.$attrs.theme
let size = this.$attrs.size
return createElement('span', {
attrs: {
class: `spinner-loading loading-bubbles ${theme ? 'theme-' + theme : ''} ${size ? 'size-' + size : ''}`
}
}, Array.apply(Array, Array(8)).map(() => createElement('span', {
attrs: {
class: 'bubble-item'
}
})))
}
},
// 多个圆点旋转
CIRCLES: {
render (createElement) {
let theme = this.$attrs.theme
let size = this.$attrs.size
return createElement('span', {
attrs: {
class: `spinner-loading loading-circles ${theme ? 'theme-' + theme : ''} ${size ? 'size-' + size : ''}`
}
}, Array.apply(Array, Array(8)).map(() => createElement('span', {
attrs: {
class: 'circle-item'
}
})))
}
},
// 圆弧旋转
SPIRAL: {
render (createElement) {
let theme = this.$attrs.theme
let size = this.$attrs.size
return createElement('i', {
attrs: {
class: `spinner-loading loading-spiral ${theme ? 'theme-' + theme : ''} ${size ? 'size-' + size : ''}`
}
})
}
},
// 圆点波浪
WAVEDOTS: {
render (createElement) {
let theme = this.$attrs.theme
let size = this.$attrs.size
return createElement('span', {
attrs: {
class: `spinner-loading loading-wave-dots ${theme ? 'theme-' + theme : ''} ${size ? 'size-' + size : ''}`
}
}, Array.apply(Array, Array(5)).map(() => createElement('span', {
attrs: {
class: 'wave-item'
}
})))
}
}
}
export default {
name: 'Spinner',
props: {
spinner: {
type: String,
default: 'default'
},
// 主题样式,默认为灰色:white(白色)、base(基础颜色,即variables.scss中定义的$base-color)
theme: {
type: String,
default: 'default'
},
// 大小:m(28px,默认)、s(18px)、l(38px)
size: {
type: String,
default: 'm'
}
},
computed: {
spinnerView () {
return (SPINNERS[(this.spinner || '').toUpperCase()] || SPINNERS.DEFAULT)
}
}
}
</script>
<style lang="scss" scoped px2rem="false">
.spinner-loading {
display: inline-block;
margin: 5px 0;
width: 28px;
height: 28px;
font-size: 28px;
line-height: 28px;
border-radius: 50%;
&.size-s {
width: 18px;
height: 18px;
font-size: 18px;
line-height: 18px;
}
&.size-l {
width: 38px;
height: 38px;
font-size: 38px;
line-height: 38px;
}
}
/* default spinner */
.loading-default {
position: relative;
border: 1px solid #999;
animation: loading-rotating ease 1.5s infinite;
&:before {
$size: 6px;
content: '';
position: absolute;
display: block;
top: 0;
left: 50%;
margin-top: -$size/2;
margin-left: -$size/2;
width: $size;
height: $size;
background-color: #999;
border-radius: 50%;
}
&.theme-white {
border-color: #FFF;
&:before {
background-color: #FFF;
}
}
&.theme-base {
border-color: $base-color;
&:before {
background-color: $base-color;
}
}
&.size-s {
&:before {
$size: 4px;
margin-top: -$size/2;
margin-left: -$size/2;
width: $size;
height: $size;
}
}
&.size-l {
&:before {
$size: 8px;
margin-top: -$size/2;
margin-left: -$size/2;
width: $size;
height: $size;
}
}
}
// spiral spinner
.loading-spiral {
border: 2px solid #999;
border-right-color: transparent;
animation: loading-rotating linear .85s infinite;
&.theme-white {
border-color: #F5F5F5;
border-right-color: transparent;
}
&.theme-base {
border-color: $base-color;
border-right-color: transparent;
}
&.size-s {
border-width: 1px;
}
}
// rotate animation
@keyframes loading-rotating {
0%{
transform: rotate(0);
}
100%{
transform: rotate(360deg);
}
}
/* bubbles spinner */
.loading-bubbles {
position: relative;
@mixin bubbles($theme, $size) {
$c-basic: #999;
$bubble-size: 1px;
$radius: 12px;
$shallow: 3px;
@if $theme == white {
$c-basic: #FFF;
} @else if $theme == base {
$c-basic: $base-color;
}
@if $size == 's' {
$bubble-size: 1px;
$radius: 8px;
$shallow: 2px;
} @else if $size == 'l' {
$bubble-size: 2px;
$radius: 16px;
$shallow: 4px;
}
::v-deep .bubble-item {
background: $c-basic;
animation: loading-bubbles-#{$theme}-#{$size} linear .75s infinite;
&:first-child {
margin-top: -$bubble-size/2 + -$radius;
margin-left: -$bubble-size/2;
}
&:nth-child(2) {
margin-top: -$bubble-size/2 + -$radius * .73;
margin-left: -$bubble-size/2 + $radius * .73;
}
&:nth-child(3) {
margin-top: -$bubble-size/2;
margin-left: -$bubble-size/2 + $radius;
}
&:nth-child(4) {
margin-top: -$bubble-size/2 + $radius * .73;
margin-left: -$bubble-size/2 + $radius * .73;
}
&:nth-child(5) {
margin-top: -$bubble-size/2 + $radius;
margin-left: -$bubble-size/2;
}
&:nth-child(6) {
margin-top: -$bubble-size/2 + $radius * .73;
margin-left: -$bubble-size/2 + -$radius * .73;
}
&:nth-child(7) {
margin-top: -$bubble-size/2;
margin-left: -$bubble-size/2 + -$radius;
}
&:last-child {
margin-top: -$bubble-size/2 + -$radius * .73;
margin-left: -$bubble-size/2 + -$radius * .73;
}
$delay: .093s;
position: absolute;
top: 50%;
left: 50%;
display: inline-block;
border-radius: 50%;
&:nth-child(2) {
animation-delay: $delay;
}
&:nth-child(3) {
animation-delay: $delay * 2;
}
&:nth-child(4) {
animation-delay: $delay * 3;
}
&:nth-child(5) {
animation-delay: $delay * 4;
}
&:nth-child(6) {
animation-delay: $delay * 5;
}
&:nth-child(7) {
animation-delay: $delay * 6;
}
&:last-child {
animation-delay: $delay * 7;
}
@keyframes loading-bubbles-#{""+$theme}-#{$size} {
0% {
width: $bubble-size;
height: $bubble-size;
box-shadow: 0 0 0 $shallow $c-basic;
}
90% {
width: $bubble-size;
height: $bubble-size;
box-shadow: 0 0 0 0 $c-basic;
}
100% {
width: $bubble-size;
height: $bubble-size;
box-shadow: 0 0 0 $shallow $c-basic;
}
}
}
}
&.theme-default{
&.size-s {
@include bubbles(default, s);
}
&.size-m {
@include bubbles(default, m);
}
&.size-l {
@include bubbles(default, l);
}
}
&.theme-white {
&.size-s {
@include bubbles(white, s);
}
&.size-m {
@include bubbles(white, m);
}
&.size-l {
@include bubbles(white, l);
}
}
&.theme-base{
&.size-s {
@include bubbles(base, s);
}
&.size-m {
@include bubbles(base, m);
}
&.size-l {
@include bubbles(base, l);
}
}
}
/* circles spinner */
.loading-circles {
position: relative;
@mixin loading-circles-animate ($theme) {
$color1: #888;
$color2: lighten($color1, 42%);
@if $theme == white {
$color1: #FFF;
$color2: darken($color1, 36%);
} @else if $theme == base {
$color1: $base-color;
$color2: lighten($color1, 42%);
}
@keyframes loading-circles-#{""+$theme} {
0% {
background: $color2;
}
90% {
background: $color1;
}
100% {
background: $color2;
}
}
::v-deep .circle-item {
$delay: .093s;
position: absolute;
top: 50%;
left: 50%;
display: inline-block;
border-radius: 50%;
animation: loading-circles-#{$theme} linear .75s infinite;
&:nth-child(2) {
animation-delay: $delay;
}
&:nth-child(3) {
animation-delay: $delay * 2;
}
&:nth-child(4) {
animation-delay: $delay * 3;
}
&:nth-child(5) {
animation-delay: $delay * 4;
}
&:nth-child(6) {
animation-delay: $delay * 5;
}
&:nth-child(7) {
animation-delay: $delay * 6;
}
&:last-child {
animation-delay: $delay * 7;
}
}
}
@mixin circles-size($size, $radius) {
// $size: $size !global;
// $radius: $radius !global;
::v-deep .circle-item {
width: $size;
height: $size;
&:first-child {
margin-top: -$size/2 + -$radius;
margin-left: -$size/2;
}
&:nth-child(2) {
margin-top: -$size/2 + -$radius * .73;
margin-left: -$size/2 + $radius * .73;
}
&:nth-child(3) {
margin-top: -$size/2;
margin-left: -$size/2 + $radius;
}
&:nth-child(4) {
margin-top: -$size/2 + $radius * .73;
margin-left: -$size/2 + $radius * .73;
}
&:nth-child(5) {
margin-top: -$size/2 + $radius;
margin-left: -$size/2;
}
&:nth-child(6) {
margin-top: -$size/2 + $radius * .73;
margin-left: -$size/2 + -$radius * .73;
}
&:nth-child(7) {
margin-top: -$size/2;
margin-left: -$size/2 + -$radius;
}
&:last-child {
margin-top: -$size/2 + -$radius * .73;
margin-left: -$size/2 + -$radius * .73;
}
}
}
&.theme-default {
@include loading-circles-animate(default);
}
&.theme-white {
@include loading-circles-animate(white);
}
&.theme-base {
@include loading-circles-animate(base);
}
&.size-l {
@include circles-size(6px, 15px);
}
&.size-m {
@include circles-size(5px, 11px);
}
&.size-s {
@include circles-size(3px, 7px);
}
}
/* wavedots spinner */
.loading-wave-dots {
position: relative;
$delay: .14s;
@mixin wave-dots ($theme, $spinner-size) {
$color1: #bbb;
$color2: #999;
$size: 8px;
$wave: -6px;
@if $theme == white {
$color1: #FFF;
$color2: darken($color1, 7%);
} @else if $theme == base {
$color1: $base-color;
$color2: lighten($base-color, 20%);
}
@if $spinner-size == 's' {
$size: 6px;
$wave: -4px;
} @else if $spinner-size == 'l' {
$size: 10px;
$wave: -8px;
}
width: $size * 5 - $wave * 4;
border-radius: 0;
::v-deep .wave-item {
position: absolute;
top: 50%;
left: 50%;
display: inline-block;
margin-top: -$size/2;
width: $size;
height: $size;
border-radius: 50%;
animation: loading-wave-dots-#{$theme}-#{$spinner-size} linear 2.8s infinite;
&:first-child {
margin-left: -$size/2 + -$size * 4;
}
&:nth-child(2) {
margin-left: -$size/2 + -$size * 2;
animation-delay: $delay;
}
&:nth-child(3) {
margin-left: -$size/2;
animation-delay: $delay * 2;
}
&:nth-child(4) {
margin-left: -$size/2 + $size * 2;
animation-delay: $delay * 3;
}
&:last-child {
margin-left: -$size/2 + $size * 4;
animation-delay: $delay * 4;
}
}
@keyframes loading-wave-dots-#{""+$theme}-#{$spinner-size} {
0% {
transform: translateY(0);
background: $color1;
}
10% {
transform: translateY($wave);
background: $color2;
}
20% {
transform: translateY(0);
background: $color1;
}
100% {
transform: translateY(0);
background: $color1;
}
}
}
&.theme-default{
&.size-s {
@include wave-dots(default, s);
}
&.size-m {
@include wave-dots(default, m);
}
&.size-l {
@include wave-dots(default, l);
}
}
&.theme-white {
&.size-s {
@include wave-dots(white, s);
}
&.size-m {
@include wave-dots(white, m);
}
&.size-l {
@include wave-dots(white, l);
}
}
&.theme-base{
&.size-s {
@include wave-dots(base, s);
}
&.size-m {
@include wave-dots(base, m);
}
&.size-l {
@include wave-dots(base, l);
}
}
}
</style>
index.js
import Spinner from './Spinner.vue' export default Spinner
感谢评论区大佬的点拨。