easy-loadimage.vue 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. <template>
  2. <view class="easy-loadimage" :id="uid">
  3. <image class="origin-img" :src="imageSrc" mode="" v-if="loadImg&&!isLoadError" v-show="showImg"
  4. :class="{'no-transition':!openTransition,'show-transition':showTransition&&openTransition}"
  5. @load="handleImgLoad" @error="handleImgError">
  6. </image>
  7. <view class="loadfail-img" v-else-if="isLoadError"></view>
  8. <view :class="['loading-img','spin-circle',loadingMode]" v-show="!showImg&&!isLoadError"></view>
  9. </view>
  10. </template>
  11. <script>
  12. import {
  13. throttle
  14. } from '@/libs/uniApi';
  15. // 生成全局唯一id
  16. function generateUUID() {
  17. return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
  18. let r = Math.random() * 16 | 0,
  19. v = c == 'x' ? r : (r & 0x3 | 0x8);
  20. return v.toString(16);
  21. })
  22. }
  23. export default {
  24. props: {
  25. imageSrc: {
  26. type: String,
  27. default: ""
  28. },
  29. mode: {
  30. type: String,
  31. default: ""
  32. },
  33. loadingMode: {
  34. type: String,
  35. default: 'looming-gray'
  36. },
  37. openTransition: {
  38. type: Boolean,
  39. default: true,
  40. },
  41. viewHeight: {
  42. type: Number,
  43. default () {
  44. return uni.getSystemInfoSync().windowHeight;
  45. }
  46. }
  47. },
  48. data() {
  49. const that = this;
  50. return {
  51. // uid:'',
  52. uid: 'uid-' + generateUUID(),
  53. loadImg: false,
  54. showImg: false,
  55. isLoadError: false,
  56. showTransition: false,
  57. scrollFn: throttle(function() {
  58. // 加载img时才执行滚动监听判断是否可加载
  59. if (that.loadImg || that.isLoadError) return;
  60. const id = that.uid
  61. const query = uni.createSelectorQuery().in(that);
  62. query.select('#' + id).boundingClientRect(data => {
  63. if (!data) return;
  64. if (data.top - that.viewHeight < 0) {
  65. that.loadImg = !!that.imageSrc;
  66. that.isLoadError = !that.loadImg;
  67. }
  68. }).exec()
  69. }, 200)
  70. }
  71. },
  72. methods: {
  73. init() {
  74. this.$nextTick(this.onScroll)
  75. },
  76. handleImgLoad(e) {
  77. this.showImg = true;
  78. setTimeout(() => {
  79. this.showTransition = true
  80. }, 50)
  81. },
  82. handleImgError(e) {
  83. this.isLoadError = true;
  84. },
  85. onScroll() {
  86. this.scrollFn();
  87. },
  88. },
  89. mounted() {
  90. this.init()
  91. uni.$on('scroll', this.scrollFn);
  92. this.onScroll()
  93. },
  94. beforeDestroy() {
  95. uni.$off('scroll', this.scrollFn);
  96. }
  97. }
  98. </script>
  99. <style scoped>
  100. .easy-loadimage {
  101. position: relative;
  102. }
  103. .border-img {
  104. position: absolute;
  105. width: 100%;
  106. height: 100%;
  107. max-height: 360rpx;
  108. top: 0;
  109. left: 0;
  110. }
  111. /* 官方优化图片tips */
  112. image {
  113. will-change: transform
  114. }
  115. /* 渐变过渡效果处理 */
  116. image.origin-img {
  117. width: 100%;
  118. height: 100%;
  119. opacity: 0.3;
  120. max-height: 360rpx;
  121. }
  122. image.origin-img.show-transition {
  123. transition: opacity .5s;
  124. opacity: 1;
  125. }
  126. image.origin-img.no-transition {
  127. opacity: 1;
  128. }
  129. /* 渐变过渡效果处理 */
  130. image.border-img {
  131. width: 100%;
  132. height: 100%;
  133. opacity: 0.3;
  134. max-height: 360rpx;
  135. }
  136. image.border-img.show-transition {
  137. transition: opacity .5s;
  138. opacity: 1;
  139. }
  140. image.border-img.no-transition {
  141. opacity: 1;
  142. }
  143. /* 加载失败、加载中的占位图样式控制 */
  144. .loadfail-img {
  145. height: 100%;
  146. background: url('~@/static/easy-loadimage/loadfail.png') no-repeat center;
  147. background-size: 50%;
  148. }
  149. .loading-img {
  150. height: 100%;
  151. }
  152. /* 转圈 */
  153. .spin-circle {
  154. background: url('~@/static/easy-loadimage/loading.png') no-repeat center;
  155. background-size: 60%;
  156. }
  157. /* 动态灰色若隐若现 */
  158. .looming-gray {
  159. animation: looming-gray 1s infinite linear;
  160. background-color: #e3e3e3;
  161. }
  162. @keyframes looming-gray {
  163. 0% {
  164. background-color: #e3e3e3aa;
  165. }
  166. 50% {
  167. background-color: #e3e3e3;
  168. }
  169. 100% {
  170. background-color: #e3e3e3aa;
  171. }
  172. }
  173. /* 骨架屏1 */
  174. .skeleton-1 {
  175. background-color: #e3e3e3;
  176. background-image: linear-gradient(100deg, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0) 80%);
  177. background-size: 100rpx 100%;
  178. background-repeat: repeat-y;
  179. background-position: 0 0;
  180. animation: skeleton-1 .6s infinite;
  181. }
  182. @keyframes skeleton-1 {
  183. to {
  184. background-position: 200% 0;
  185. }
  186. }
  187. /* 骨架屏2 */
  188. .skeleton-2 {
  189. background-image: linear-gradient(-90deg, #fefefe 0%, #e6e6e6 50%, #fefefe 100%);
  190. background-size: 400% 400%;
  191. background-position: 0 0;
  192. animation: skeleton-2 1.2s ease-in-out infinite;
  193. }
  194. @keyframes skeleton-2 {
  195. to {
  196. background-position: -135% 0;
  197. }
  198. }
  199. </style>