u-loading-icon.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. <template>
  2. <view class="u-loading-icon" :style="[$u.addStyle(customStyle)]" :class="[vertical && 'u-loading-icon--vertical']" v-if="show">
  3. <view
  4. v-if="!webviewHide"
  5. class="u-loading-icon__spinner"
  6. :class="[`u-loading-icon__spinner--${mode}`]"
  7. ref="ani"
  8. :style="{
  9. color: color,
  10. width: $u.addUnit(size),
  11. height: $u.addUnit(size),
  12. borderTopColor: color,
  13. borderBottomColor: otherBorderColor,
  14. borderLeftColor: otherBorderColor,
  15. borderRightColor: otherBorderColor,
  16. 'animation-duration': `${duration}ms`,
  17. 'animation-timing-function': mode === 'semicircle' || mode === 'circle' ? timingFunction : ''
  18. }">
  19. <block v-if="mode === 'spinner'">
  20. <!-- #ifndef APP-NVUE -->
  21. <view v-for="(item, index) in array12" :key="index" class="u-loading-icon__dot"></view>
  22. <!-- #endif -->
  23. <!-- #ifdef APP-NVUE -->
  24. <!-- 此组件内部图标部分无法设置宽高,即使通过width和height配置了也无效 -->
  25. <loading-indicator
  26. v-if="!webviewHide"
  27. class="u-loading-indicator"
  28. :animating="true"
  29. :style="{
  30. color: color,
  31. width: $u.addUnit(size),
  32. height: $u.addUnit(size)
  33. }"
  34. />
  35. <!-- #endif -->
  36. </block>
  37. </view>
  38. <text
  39. v-if="text"
  40. class="u-loading-icon__text"
  41. :style="{
  42. fontSize: $u.addUnit(textSize),
  43. color: textColor
  44. }"
  45. >
  46. {{ text }}
  47. </text>
  48. </view>
  49. </template>
  50. <script>
  51. import props from './props.js';
  52. import mpMixin from '../../libs/mixin/mpMixin.js';
  53. import mixin from '../../libs/mixin/mixin.js';
  54. // #ifdef APP-NVUE
  55. const animation = weex.requireModule('animation');
  56. // #endif
  57. /**
  58. * loading 加载动画
  59. * @description 警此组件为一个小动画,目前用在uView的loadmore加载更多和switch开关等组件的正在加载状态场景。
  60. * @tutorial https://ijry.github.io/uview-plus/components/loading.html
  61. * @property {Boolean} show 是否显示组件 (默认 true)
  62. * @property {String} color 动画活动区域的颜色,只对 mode = flower 模式有效(默认color['u-tips-color'])
  63. * @property {String} textColor 提示文本的颜色(默认color['u-tips-color'])
  64. * @property {Boolean} vertical 文字和图标是否垂直排列 (默认 false )
  65. * @property {String} mode 模式选择,见官网说明(默认 'circle' )
  66. * @property {String | Number} size 加载图标的大小,单位px (默认 24 )
  67. * @property {String | Number} textSize 文字大小(默认 15 )
  68. * @property {String | Number} text 文字内容
  69. * @property {String} timingFunction 动画模式 (默认 'ease-in-out' )
  70. * @property {String | Number} duration 动画执行周期时间(默认 1200)
  71. * @property {String} inactiveColor mode=circle时的暗边颜色
  72. * @property {Object} customStyle 定义需要用到的外部样式
  73. * @example <u-loading mode="circle"></u-loading>
  74. */
  75. export default {
  76. name: 'u-loading-icon',
  77. mixins: [mpMixin, mixin, props],
  78. data() {
  79. return {
  80. // Array.form可以通过一个伪数组对象创建指定长度的数组
  81. // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/from
  82. array12: Array.from({
  83. length: 12
  84. }),
  85. // 这里需要设置默认值为360,否则在安卓nvue上,会延迟一个duration周期后才执行
  86. // 在iOS nvue上,则会一开始默认执行两个周期的动画
  87. aniAngel: 360, // 动画旋转角度
  88. webviewHide: false, // 监听webview的状态,如果隐藏了页面,则停止动画,以免性能消耗
  89. loading: false // 是否运行中,针对nvue使用
  90. };
  91. },
  92. computed: {
  93. // 当为circle类型时,给其另外三边设置一个更轻一些的颜色
  94. // 之所以需要这么做的原因是,比如父组件传了color为红色,那么需要另外的三个边为浅红色
  95. // 而不能是固定的某一个其他颜色(因为这个固定的颜色可能浅蓝,导致效果没有那么细腻良好)
  96. otherBorderColor() {
  97. const lightColor = uni.$u.colorGradient(this.color, '#ffffff', 100)[80];
  98. if (this.mode === 'circle') {
  99. return this.inactiveColor ? this.inactiveColor : lightColor;
  100. } else {
  101. return 'transparent';
  102. }
  103. // return this.mode === 'circle' ? this.inactiveColor ? this.inactiveColor : lightColor : 'transparent'
  104. }
  105. },
  106. watch: {
  107. show(n) {
  108. // nvue中,show为true,且为非loading状态,就重新执行动画模块
  109. // #ifdef APP-NVUE
  110. if (n && !this.loading) {
  111. setTimeout(() => {
  112. this.startAnimate();
  113. }, 30);
  114. }
  115. // #endif
  116. }
  117. },
  118. mounted() {
  119. this.init();
  120. },
  121. methods: {
  122. init() {
  123. setTimeout(() => {
  124. // #ifdef APP-NVUE
  125. this.show && this.nvueAnimate();
  126. // #endif
  127. // #ifdef APP-PLUS
  128. this.show && this.addEventListenerToWebview();
  129. // #endif
  130. }, 20);
  131. },
  132. // 监听webview的显示与隐藏
  133. addEventListenerToWebview() {
  134. // webview的堆栈
  135. const pages = getCurrentPages();
  136. // 当前页面
  137. const page = pages[pages.length - 1];
  138. // 当前页面的webview实例
  139. const currentWebview = page.$getAppWebview();
  140. // 监听webview的显示与隐藏,从而停止或者开始动画(为了性能)
  141. currentWebview.addEventListener('hide', () => {
  142. this.webviewHide = true;
  143. });
  144. currentWebview.addEventListener('show', () => {
  145. this.webviewHide = false;
  146. });
  147. },
  148. // #ifdef APP-NVUE
  149. nvueAnimate() {
  150. // nvue下,非spinner类型时才需要旋转,因为nvue的spinner类型,使用了weex的
  151. // loading-indicator组件,自带旋转功能
  152. this.mode !== 'spinner' && this.startAnimate();
  153. },
  154. // 执行nvue的animate模块动画
  155. startAnimate() {
  156. this.loading = true;
  157. const ani = this.$refs.ani;
  158. if (!ani) return;
  159. animation.transition(
  160. ani,
  161. {
  162. // 进行角度旋转
  163. styles: {
  164. transform: `rotate(${this.aniAngel}deg)`,
  165. transformOrigin: 'center center'
  166. },
  167. duration: this.duration,
  168. timingFunction: this.timingFunction
  169. // delay: 10
  170. },
  171. () => {
  172. // 每次增加360deg,为了让其重新旋转一周
  173. this.aniAngel += 360;
  174. // 动画结束后,继续循环执行动画,需要同时判断webviewHide变量
  175. // nvue安卓,页面隐藏后依然会继续执行startAnimate方法
  176. this.show && !this.webviewHide ? this.startAnimate() : (this.loading = false);
  177. }
  178. );
  179. }
  180. // #endif
  181. }
  182. };
  183. </script>
  184. <style lang="scss" scoped>
  185. @import '../../libs/css/components.scss';
  186. $u-loading-icon-color: #c8c9cc !default;
  187. $u-loading-icon-text-margin-left: 4px !default;
  188. $u-loading-icon-text-color: $u-content-color !default;
  189. $u-loading-icon-text-font-size: 14px !default;
  190. $u-loading-icon-text-line-height: 20px !default;
  191. $u-loading-width: 30px !default;
  192. $u-loading-height: 30px !default;
  193. $u-loading-max-width: 100% !default;
  194. $u-loading-max-height: 100% !default;
  195. $u-loading-semicircle-border-width: 2px !default;
  196. $u-loading-semicircle-border-color: transparent !default;
  197. $u-loading-semicircle-border-top-right-radius: 100px !default;
  198. $u-loading-semicircle-border-top-left-radius: 100px !default;
  199. $u-loading-semicircle-border-bottom-left-radius: 100px !default;
  200. $u-loading-semicircle-border-bottom-right-radiu: 100px !default;
  201. $u-loading-semicircle-border-style: solid !default;
  202. $u-loading-circle-border-top-right-radius: 100px !default;
  203. $u-loading-circle-border-top-left-radius: 100px !default;
  204. $u-loading-circle-border-bottom-left-radius: 100px !default;
  205. $u-loading-circle-border-bottom-right-radiu: 100px !default;
  206. $u-loading-circle-border-width: 2px !default;
  207. $u-loading-circle-border-top-color: #e5e5e5 !default;
  208. $u-loading-circle-border-right-color: $u-loading-circle-border-top-color !default;
  209. $u-loading-circle-border-bottom-color: $u-loading-circle-border-top-color !default;
  210. $u-loading-circle-border-left-color: $u-loading-circle-border-top-color !default;
  211. $u-loading-circle-border-style: solid !default;
  212. $u-loading-icon-host-font-size: 0px !default;
  213. $u-loading-icon-host-line-height: 1 !default;
  214. $u-loading-icon-vertical-margin: 6px 0 0 !default;
  215. $u-loading-icon-dot-top: 0 !default;
  216. $u-loading-icon-dot-left: 0 !default;
  217. $u-loading-icon-dot-width: 100% !default;
  218. $u-loading-icon-dot-height: 100% !default;
  219. $u-loading-icon-dot-before-width: 2px !default;
  220. $u-loading-icon-dot-before-height: 25% !default;
  221. $u-loading-icon-dot-before-margin: 0 auto !default;
  222. $u-loading-icon-dot-before-background-color: currentColor !default;
  223. $u-loading-icon-dot-before-border-radius: 40% !default;
  224. .u-loading-icon {
  225. /* #ifndef APP-NVUE */
  226. // display: inline-flex;
  227. /* #endif */
  228. flex-direction: row;
  229. align-items: center;
  230. justify-content: center;
  231. color: $u-loading-icon-color;
  232. &__text {
  233. margin-left: $u-loading-icon-text-margin-left;
  234. color: $u-loading-icon-text-color;
  235. font-size: $u-loading-icon-text-font-size;
  236. line-height: $u-loading-icon-text-line-height;
  237. }
  238. &__spinner {
  239. width: $u-loading-width;
  240. height: $u-loading-height;
  241. position: relative;
  242. /* #ifndef APP-NVUE */
  243. box-sizing: border-box;
  244. max-width: $u-loading-max-width;
  245. max-height: $u-loading-max-height;
  246. animation: u-rotate 1s linear infinite;
  247. /* #endif */
  248. }
  249. &__spinner--semicircle {
  250. border-width: $u-loading-semicircle-border-width;
  251. border-color: $u-loading-semicircle-border-color;
  252. border-top-right-radius: $u-loading-semicircle-border-top-right-radius;
  253. border-top-left-radius: $u-loading-semicircle-border-top-left-radius;
  254. border-bottom-left-radius: $u-loading-semicircle-border-bottom-left-radius;
  255. border-bottom-right-radius: $u-loading-semicircle-border-bottom-right-radiu;
  256. border-style: $u-loading-semicircle-border-style;
  257. }
  258. &__spinner--circle {
  259. border-top-right-radius: $u-loading-circle-border-top-right-radius;
  260. border-top-left-radius: $u-loading-circle-border-top-left-radius;
  261. border-bottom-left-radius: $u-loading-circle-border-bottom-left-radius;
  262. border-bottom-right-radius: $u-loading-circle-border-bottom-right-radiu;
  263. border-width: $u-loading-circle-border-width;
  264. border-top-color: $u-loading-circle-border-top-color;
  265. border-right-color: $u-loading-circle-border-right-color;
  266. border-bottom-color: $u-loading-circle-border-bottom-color;
  267. border-left-color: $u-loading-circle-border-left-color;
  268. border-style: $u-loading-circle-border-style;
  269. }
  270. &--vertical {
  271. flex-direction: column;
  272. }
  273. }
  274. /* #ifndef APP-NVUE */
  275. :host {
  276. font-size: $u-loading-icon-host-font-size;
  277. line-height: $u-loading-icon-host-line-height;
  278. }
  279. .u-loading-icon {
  280. &__spinner--spinner {
  281. animation-timing-function: steps(12);
  282. }
  283. &__text:empty {
  284. display: none;
  285. }
  286. &--vertical &__text {
  287. margin: $u-loading-icon-vertical-margin;
  288. color: $u-content-color;
  289. }
  290. &__dot {
  291. position: absolute;
  292. top: $u-loading-icon-dot-top;
  293. left: $u-loading-icon-dot-left;
  294. width: $u-loading-icon-dot-width;
  295. height: $u-loading-icon-dot-height;
  296. &:before {
  297. display: block;
  298. width: $u-loading-icon-dot-before-width;
  299. height: $u-loading-icon-dot-before-height;
  300. margin: $u-loading-icon-dot-before-margin;
  301. background-color: $u-loading-icon-dot-before-background-color;
  302. border-radius: $u-loading-icon-dot-before-border-radius;
  303. content: ' ';
  304. }
  305. }
  306. }
  307. @for $i from 1 through 12 {
  308. .u-loading-icon__dot:nth-of-type(#{$i}) {
  309. transform: rotate($i * 30deg);
  310. opacity: 1 - 0.0625 * ($i - 1);
  311. }
  312. }
  313. @keyframes u-rotate {
  314. 0% {
  315. transform: rotate(0deg);
  316. }
  317. to {
  318. transform: rotate(1turn);
  319. }
  320. }
  321. /* #endif */
  322. </style>