tn-skeleton.vue 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. <template>
  2. <view
  3. v-if="show"
  4. class="tn-skeleton-class tn-skeleton"
  5. :class="[backgroundColorClass]"
  6. :style="[skeletonStyle]"
  7. @touchmove.stop.prevent
  8. >
  9. <view
  10. v-for="(item, index) in rectNodes"
  11. :key="$t.uuid()"
  12. class="tn-skeleton__item tn-skeleton__item--rect"
  13. :class="[elBackgroundColorClass, {'tn-skeleton__item--fade': animation}]"
  14. :style="[itemStyle('rect', item)]"
  15. ></view>
  16. <view
  17. v-for="(item, index) in circleNodes"
  18. :key="$t.uuid()"
  19. class="tn-skeleton__item tn-skeleton__item--circle"
  20. :class="[elBackgroundColorClass, {'tn-skeleton__item--fade': animation}]"
  21. :style="[itemStyle('circle', item)]"
  22. ></view>
  23. <view
  24. v-for="(item, index) in filletNodes"
  25. :key="$t.uuid()"
  26. class="tn-skeleton__item tn-skeleton__item--fillet"
  27. :class="[elBackgroundColorClass, {'tn-skeleton__item--fade': animation}]"
  28. :style="[itemStyle('fillet', item)]"
  29. ></view>
  30. </view>
  31. </template>
  32. <script>
  33. import componentsColorMixin from '../../libs/mixin/components_color.js'
  34. export default {
  35. name: 'tn-skeleton',
  36. mixins: [ componentsColorMixin ],
  37. props: {
  38. // 显示骨架屏
  39. show: {
  40. type: Boolean,
  41. default: false
  42. },
  43. // 需要渲染的元素背景颜色
  44. elBackgroundColor: {
  45. type: String,
  46. default: ''
  47. },
  48. // 开启加载动画
  49. animation: {
  50. type: Boolean,
  51. default: true
  52. },
  53. // 矩形元素自定义样式
  54. rectCustomStyle: {
  55. type: Object,
  56. default() {
  57. return {}
  58. }
  59. },
  60. // 圆形元素自定义样式
  61. circleCustomStyle: {
  62. type: Object,
  63. default() {
  64. return {}
  65. }
  66. },
  67. // 圆角元素自定义样式
  68. filletCustomStyle: {
  69. type: Object,
  70. default() {
  71. return {}
  72. }
  73. }
  74. },
  75. computed: {
  76. elBackgroundColorStyle() {
  77. return this.$t.color.getBackgroundColorStyle(this.elBackgroundColor)
  78. },
  79. elBackgroundColorClass() {
  80. return this.$t.color.getBackgroundColorInternalClass(this.elBackgroundColor)
  81. },
  82. // 骨架屏样式
  83. skeletonStyle() {
  84. let style = {}
  85. style.width = this.skeletonWidth + 'px'
  86. style.height = this.skeletonHeight + 'px'
  87. if (this.backgroundColorStyle) {
  88. style.backgroundColor = this.backgroundColorStyle
  89. }
  90. style.left = this.left + 'px'
  91. style.top = this.top + 'px'
  92. return style
  93. },
  94. // 元素样式
  95. itemStyle() {
  96. return (type, item) => {
  97. let style = {}
  98. style.width = item.width + 'px'
  99. style.height = item.height + 'px'
  100. if (this.elBackgroundColorStyle) {
  101. style.backgroundColor = this.elBackgroundColorStyle
  102. }
  103. style.left = (item.left - this.left) + 'px'
  104. style.top = (item.top - this.top) + 'px'
  105. if (type === 'rect') {
  106. Object.assign(style, this.rectCustomStyle)
  107. } else if (type === 'circle') {
  108. style.borderRadius = (item.width / 2) + 'px'
  109. Object.assign(style, this.circleCustomStyle)
  110. } else if (type === 'fillet') {
  111. Object.assign(style, this.filletCustomStyle)
  112. }
  113. return style
  114. }
  115. }
  116. },
  117. data() {
  118. return {
  119. // 骨架屏宽度
  120. skeletonWidth: 750,
  121. // 骨架屏高度
  122. skeletonHeight: 1500,
  123. // 圆角元素
  124. filletNodes: [],
  125. // 圆形元素
  126. circleNodes: [],
  127. // 矩形元素
  128. rectNodes: [],
  129. // 元素偏移位置
  130. top: 0,
  131. left: 0
  132. }
  133. },
  134. mounted() {
  135. this.$nextTick(() => {
  136. // 获取系统信息
  137. const systemInfo = uni.getSystemInfoSync()
  138. this.skeletonWidth = systemInfo.safeArea.width
  139. this.skeletonHeight = systemInfo.safeArea.height
  140. this.selectQueryInfo()
  141. })
  142. },
  143. methods: {
  144. // 查询节点信息
  145. selectQueryInfo() {
  146. // 获取整个父容器的宽高作为骨架屏的宽高
  147. // 在微信小程序中,如果把骨架屏放入组件使用,需要in(this)上下文为父组件才有效
  148. let query = null
  149. // 在微信小程序中,如果把骨架屏放入组件使用,需要in(this)上下文为父组件才有效
  150. // #ifdef MP-WEIXIN
  151. query = uni.createSelectorQuery().in(this.$parent)
  152. // #endif
  153. // #ifndef MP-WEIXIN
  154. query = uni.createSelectorQuery()
  155. // #endif
  156. query.selectAll('.tn-skeleton').boundingClientRect().exec((res) => {
  157. console.log(res);
  158. this.skeletonWidth = res[0][0].width
  159. this.skeletonHeight = res[0][0].height
  160. this.top = res[0][0].bottom - res[0][0].height
  161. this.left = res[0][0].left
  162. })
  163. // 获取元素列表
  164. this.getRectElements()
  165. this.getCircleElements()
  166. this.getFillteElements()
  167. },
  168. // 矩形元素列表
  169. getRectElements() {
  170. let query = null
  171. // 在微信小程序中,如果把骨架屏放入组件使用,需要in(this)上下文为父组件才有效
  172. // #ifdef MP-WEIXIN
  173. query = uni.createSelectorQuery().in(this.$parent)
  174. // #endif
  175. // #ifndef MP-WEIXIN
  176. query = uni.createSelectorQuery()
  177. // #endif
  178. query.selectAll('.tn-skeleton-rect').boundingClientRect().exec((res) => {
  179. this.rectNodes = res[0]
  180. })
  181. },
  182. // 圆形元素列表
  183. getCircleElements() {
  184. let query = null
  185. // 在微信小程序中,如果把骨架屏放入组件使用,需要in(this)上下文为父组件才有效
  186. // #ifdef MP-WEIXIN
  187. query = uni.createSelectorQuery().in(this.$parent)
  188. // #endif
  189. // #ifndef MP-WEIXIN
  190. query = uni.createSelectorQuery()
  191. // #endif
  192. query.selectAll('.tn-skeleton-circle').boundingClientRect().exec((res) => {
  193. this.circleNodes = res[0]
  194. })
  195. },
  196. // 圆角元素列表
  197. getFillteElements() {
  198. let query = null
  199. // 在微信小程序中,如果把骨架屏放入组件使用,需要in(this)上下文为父组件才有效
  200. // #ifdef MP-WEIXIN
  201. query = uni.createSelectorQuery().in(this.$parent)
  202. // #endif
  203. // #ifndef MP-WEIXIN
  204. query = uni.createSelectorQuery()
  205. // #endif
  206. query.selectAll('.tn-skeleton-fillet').boundingClientRect().exec((res) => {
  207. this.filletNodes = res[0]
  208. })
  209. }
  210. }
  211. }
  212. </script>
  213. <style lang="scss" scoped>
  214. .tn-skeleton {
  215. position: absolute;
  216. z-index: 9998;
  217. overflow: hidden;
  218. background-color: #FFFFFF;
  219. &__item {
  220. position: absolute;
  221. background-color: #F0F0F0;
  222. &--fillet {
  223. border-radius: 10rpx;
  224. }
  225. &--fade {
  226. width: 100%;
  227. height: 100%;
  228. background-color: #E6E6E6;
  229. animation-duration: 1.5s;
  230. animation-name: blink;
  231. animation-timing-function: ease-in-out;
  232. animation-iteration-count: infinite;
  233. }
  234. }
  235. }
  236. @keyframes blink {
  237. 0% {
  238. opacity: 1;
  239. }
  240. 50% {
  241. opacity: 0.4;
  242. }
  243. 100% {
  244. opacity: 1;
  245. }
  246. }
  247. </style>