344 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Vue
		
	
	
	
		
		
			
		
	
	
			344 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Vue
		
	
	
	
|  | <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 --> | |||
|  | 				<!-- 此组件内部图标部分无法设置宽高,即使通过width和height配置了也无效 --> | |||
|  | 				<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> |