u-loading-icon.vue 11.2 KB
<template>
	<view
		class="u-loading-icon"
		:style="[$u.addStyle(customStyle)]"
		:class="[vertical && 'u-loading-icon--vertical']"
		v-if="show"
	>
		<view
			v-if="!webviewHide"
			class="u-loading-icon__spinner"
			:class="[`u-loading-icon__spinner--${mode}`]"
			ref="ani"
			:style="{
				color: color,
				width: $u.addUnit(size),
				height: $u.addUnit(size),
				borderTopColor: color,
				borderBottomColor: otherBorderColor,
				borderLeftColor: otherBorderColor,
				borderRightColor: otherBorderColor,
				'animation-duration': `${duration}ms`,
				'animation-timing-function': mode === 'semicircle' || mode === 'circle' ? timingFunction : ''
			}"
		>
			<block v-if="mode === 'spinner'">
				<!-- #ifndef APP-NVUE -->
				<view
					v-for="(item, index) in array12"
					:key="index"
					class="u-loading-icon__dot"
				>
				</view>
				<!-- #endif -->
				<!-- #ifdef APP-NVUE -->
				<!-- 此组件内部图标部分无法设置宽高,即使通过widthheight配置了也无效 -->
				<loading-indicator
					v-if="!webviewHide"
					class="u-loading-indicator"
					:animating="true"
					:style="{
						color: color,
						width: $u.addUnit(size),
						height: $u.addUnit(size)
					}"
				/>
				<!-- #endif -->
			</block>
		</view>
		<text
			v-if="text"
			class="u-loading-icon__text"
			:style="{
				fontSize: $u.addUnit(textSize),
				color: textColor,
			}"
		>{{text}}</text>
	</view>
</template>

<script>
	import props from './props.js';
	// #ifdef APP-NVUE
	const animation = weex.requireModule('animation');
	// #endif
	/**
	 * loading 加载动画
	 * @description 警此组件为一个小动画,目前用在uView的loadmore加载更多和switch开关等组件的正在加载状态场景。
	 * @tutorial https://www.uviewui.com/components/loading.html
	 * @property {Boolean}			show			是否显示组件  (默认 true)
	 * @property {String}			color			动画活动区域的颜色,只对 mode = flower 模式有效(默认color['u-tips-color'])
	 * @property {String}			textColor		提示文本的颜色(默认color['u-tips-color'])
	 * @property {Boolean}			vertical		文字和图标是否垂直排列 (默认 false )
	 * @property {String}			mode			模式选择,见官网说明(默认 'circle' )
	 * @property {String | Number}	size			加载图标的大小,单位px (默认 24 )
	 * @property {String | Number}	textSize		文字大小(默认 15 )
	 * @property {String | Number}	text			文字内容 
	 * @property {String}			timingFunction	动画模式 (默认 'ease-in-out' )
	 * @property {String | Number}	duration		动画执行周期时间(默认 1200)
	 * @property {String}			inactiveColor	mode=circle时的暗边颜色 
	 * @property {Object}			customStyle		定义需要用到的外部样式
	 * @example <u-loading mode="circle"></u-loading>
	 */
	export default {
		name: 'u-loading-icon',
		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
		data() {
			return {
				// Array.form可以通过一个伪数组对象创建指定长度的数组
				// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/from
				array12: Array.from({
					length: 12
				}),
				// 这里需要设置默认值为360,否则在安卓nvue上,会延迟一个duration周期后才执行
				// 在iOS nvue上,则会一开始默认执行两个周期的动画
				aniAngel: 360, // 动画旋转角度
				webviewHide: false, // 监听webview的状态,如果隐藏了页面,则停止动画,以免性能消耗
				loading: false, // 是否运行中,针对nvue使用
			}
		},
		computed: {
			// 当为circle类型时,给其另外三边设置一个更轻一些的颜色
			// 之所以需要这么做的原因是,比如父组件传了color为红色,那么需要另外的三个边为浅红色
			// 而不能是固定的某一个其他颜色(因为这个固定的颜色可能浅蓝,导致效果没有那么细腻良好)
			otherBorderColor() {
				const lightColor = uni.$u.colorGradient(this.color, '#ffffff', 100)[80]
				if (this.mode === 'circle') {
					return this.inactiveColor ? this.inactiveColor : lightColor
				} else {
					return 'transparent'
				}
				// return this.mode === 'circle' ? this.inactiveColor ? this.inactiveColor : lightColor : 'transparent'
			}
		},
		watch: {
			show(n) {
				// nvue中,show为true,且为非loading状态,就重新执行动画模块
				// #ifdef APP-NVUE
				if (n && !this.loading) {
					setTimeout(() => {
						this.startAnimate()
					}, 30)
				}
				// #endif
			}
		},
		mounted() {
			this.init()
		},
		methods: {
			init() {
				setTimeout(() => {
					// #ifdef APP-NVUE
					this.show && this.nvueAnimate()
					// #endif
					// #ifdef APP-PLUS 
					this.show && this.addEventListenerToWebview()
					// #endif
				}, 20)
			},
			// 监听webview的显示与隐藏
			addEventListenerToWebview() {
				// webview的堆栈
				const pages = getCurrentPages()
				// 当前页面
				const page = pages[pages.length - 1]
				// 当前页面的webview实例
				const currentWebview = page.$getAppWebview()
				// 监听webview的显示与隐藏,从而停止或者开始动画(为了性能)
				currentWebview.addEventListener('hide', () => {
					this.webviewHide = true
				})
				currentWebview.addEventListener('show', () => {
					this.webviewHide = false
				})
			},
			// #ifdef APP-NVUE
			nvueAnimate() {
				// nvue下,非spinner类型时才需要旋转,因为nvue的spinner类型,使用了weex的
				// loading-indicator组件,自带旋转功能
				this.mode !== 'spinner' && this.startAnimate()
			},
			// 执行nvue的animate模块动画
			startAnimate() {
				this.loading = true
				const ani = this.$refs.ani
				if (!ani) return
				animation.transition(ani, {
					// 进行角度旋转
					styles: {
						transform: `rotate(${this.aniAngel}deg)`,
						transformOrigin: 'center center'
					},
					duration: this.duration,
					timingFunction: this.timingFunction,
					// delay: 10
				}, () => {
					// 每次增加360deg,为了让其重新旋转一周
					this.aniAngel += 360
					// 动画结束后,继续循环执行动画,需要同时判断webviewHide变量
					// nvue安卓,页面隐藏后依然会继续执行startAnimate方法
					this.show && !this.webviewHide ? this.startAnimate() : this.loading = false
				})
			}
			// #endif
		}
	}
</script>

<style lang="scss" scoped>
	@import "../../libs/css/components.scss";
	$u-loading-icon-color: #c8c9cc !default;
	$u-loading-icon-text-margin-left:4px !default;
	$u-loading-icon-text-color:$u-content-color !default;
	$u-loading-icon-text-font-size:14px !default;
	$u-loading-icon-text-line-height:20px !default;
	$u-loading-width:30px !default;
	$u-loading-height:30px !default;
	$u-loading-max-width:100% !default;
	$u-loading-max-height:100% !default;
	$u-loading-semicircle-border-width: 2px !default;
	$u-loading-semicircle-border-color:transparent !default;
	$u-loading-semicircle-border-top-right-radius: 100px !default;
	$u-loading-semicircle-border-top-left-radius: 100px !default;
	$u-loading-semicircle-border-bottom-left-radius: 100px !default;
	$u-loading-semicircle-border-bottom-right-radiu: 100px !default;
	$u-loading-semicircle-border-style: solid !default;
	$u-loading-circle-border-top-right-radius: 100px !default;
	$u-loading-circle-border-top-left-radius: 100px !default;
	$u-loading-circle-border-bottom-left-radius: 100px !default;
	$u-loading-circle-border-bottom-right-radiu: 100px !default;
	$u-loading-circle-border-width:2px !default;
	$u-loading-circle-border-top-color:#e5e5e5 !default;
	$u-loading-circle-border-right-color:$u-loading-circle-border-top-color !default;
	$u-loading-circle-border-bottom-color:$u-loading-circle-border-top-color !default;
	$u-loading-circle-border-left-color:$u-loading-circle-border-top-color !default;
	$u-loading-circle-border-style:solid !default;
	$u-loading-icon-host-font-size:0px !default;
	$u-loading-icon-host-line-height:1 !default;
	$u-loading-icon-vertical-margin:6px 0 0 !default;
	$u-loading-icon-dot-top:0 !default;
	$u-loading-icon-dot-left:0 !default;
	$u-loading-icon-dot-width:100% !default;
	$u-loading-icon-dot-height:100% !default;
	$u-loading-icon-dot-before-width:2px !default;
	$u-loading-icon-dot-before-height:25% !default;
	$u-loading-icon-dot-before-margin:0 auto !default;
	$u-loading-icon-dot-before-background-color:currentColor !default;
	$u-loading-icon-dot-before-border-radius:40% !default;

	.u-loading-icon {
		/* #ifndef APP-NVUE */
		// display: inline-flex;
		/* #endif */
		flex-direction: row;
		align-items: center;
		justify-content: center;
		color: $u-loading-icon-color;

		&__text {
			margin-left: $u-loading-icon-text-margin-left;
			color: $u-loading-icon-text-color;
			font-size: $u-loading-icon-text-font-size;
			line-height: $u-loading-icon-text-line-height;
		}

		&__spinner {
			width: $u-loading-width;
			height: $u-loading-height;
			position: relative;
			/* #ifndef APP-NVUE */
			box-sizing: border-box;
			max-width: $u-loading-max-width;
			max-height: $u-loading-max-height;
			animation: u-rotate 1s linear infinite;
			/* #endif */
		}

		&__spinner--semicircle {
			border-width: $u-loading-semicircle-border-width;
			border-color: $u-loading-semicircle-border-color;
			border-top-right-radius: $u-loading-semicircle-border-top-right-radius;
			border-top-left-radius: $u-loading-semicircle-border-top-left-radius;
			border-bottom-left-radius: $u-loading-semicircle-border-bottom-left-radius;
			border-bottom-right-radius: $u-loading-semicircle-border-bottom-right-radiu;
			border-style: $u-loading-semicircle-border-style;
		}

		&__spinner--circle {
			border-top-right-radius: $u-loading-circle-border-top-right-radius;
			border-top-left-radius: $u-loading-circle-border-top-left-radius;
			border-bottom-left-radius: $u-loading-circle-border-bottom-left-radius;
			border-bottom-right-radius: $u-loading-circle-border-bottom-right-radiu;
			border-width: $u-loading-circle-border-width;
			border-top-color: $u-loading-circle-border-top-color;
			border-right-color: $u-loading-circle-border-right-color;
			border-bottom-color: $u-loading-circle-border-bottom-color;
			border-left-color: $u-loading-circle-border-left-color;
			border-style: $u-loading-circle-border-style;
		}

		&--vertical {
			flex-direction: column
		}
	}

	/* #ifndef APP-NVUE */
	:host {
		font-size: $u-loading-icon-host-font-size;
		line-height: $u-loading-icon-host-line-height;
	}

	.u-loading-icon {
		&__spinner--spinner {
			animation-timing-function: steps(12)
		}

		&__text:empty {
			display: none
		}

		&--vertical &__text {
			margin: $u-loading-icon-vertical-margin;
			color: $u-content-color;
		}

		&__dot {
			position: absolute;
			top: $u-loading-icon-dot-top;
			left: $u-loading-icon-dot-left;
			width: $u-loading-icon-dot-width;
			height: $u-loading-icon-dot-height;

			&:before {
				display: block;
				width: $u-loading-icon-dot-before-width;
				height: $u-loading-icon-dot-before-height;
				margin: $u-loading-icon-dot-before-margin;
				background-color: $u-loading-icon-dot-before-background-color;
				border-radius: $u-loading-icon-dot-before-border-radius;
				content: " "
			}
		}
	}

	@for $i from 1 through 12 {
		.u-loading-icon__dot:nth-of-type(#{$i}) {
			transform: rotate($i * 30deg);
			opacity: 1 - 0.0625 * ($i - 1);
		}
	}

	@keyframes u-rotate {
		0% {
			transform: rotate(0deg)
		}

		to {
			transform: rotate(1turn)
		}
	}

	/* #endif */
</style>