easy-loadimage.vue 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. <template>
  2. <view class="easy-loadimage" :id="uid">
  3. <image class="origin-img" :src="imageSrc" mode="aspectFill" 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. // console.log('success');
  78. this.showImg = true;
  79. // this.$nextTick(function(){
  80. // this.showTransition = true
  81. // })
  82. setTimeout(() => {
  83. this.showTransition = true
  84. }, 50)
  85. },
  86. handleImgError(e) {
  87. // console.log('fail');
  88. this.isLoadError = true;
  89. },
  90. onScroll() {
  91. this.scrollFn();
  92. },
  93. },
  94. mounted() {
  95. this.init()
  96. uni.$on('scroll', this.scrollFn);
  97. this.onScroll()
  98. },
  99. beforeDestroy() {
  100. uni.$off('scroll', this.scrollFn);
  101. }
  102. }
  103. </script>
  104. <style scoped>
  105. .easy-loadimage {
  106. position: relative;
  107. }
  108. .border-img {
  109. position: absolute;
  110. width: 100%;
  111. height: 100%;
  112. max-height: 360rpx;
  113. top: 0;
  114. left: 0;
  115. }
  116. /* 官方优化图片tips */
  117. image {
  118. will-change: transform
  119. }
  120. /* 渐变过渡效果处理 */
  121. image.origin-img {
  122. width: 100%;
  123. height: 100%;
  124. opacity: 0.3;
  125. max-height: 360rpx;
  126. }
  127. image.origin-img.show-transition {
  128. transition: opacity .5s;
  129. opacity: 1;
  130. }
  131. image.origin-img.no-transition {
  132. opacity: 1;
  133. }
  134. /* 渐变过渡效果处理 */
  135. image.border-img {
  136. width: 100%;
  137. height: 100%;
  138. opacity: 0.3;
  139. max-height: 360rpx;
  140. }
  141. image.border-img.show-transition {
  142. transition: opacity .5s;
  143. opacity: 1;
  144. }
  145. image.border-img.no-transition {
  146. opacity: 1;
  147. }
  148. /* 加载失败、加载中的占位图样式控制 */
  149. .loadfail-img {
  150. height: 100%;
  151. background: url('~@/static/easy-loadimage/loadfail.png') no-repeat center;
  152. background-size: 50%;
  153. }
  154. .loading-img {
  155. height: 100%;
  156. }
  157. /* 转圈 */
  158. .spin-circle {
  159. background: url('~@/static/easy-loadimage/loading.png') no-repeat center;
  160. background-size: 60%;
  161. }
  162. /* 动态灰色若隐若现 */
  163. .looming-gray {
  164. animation: looming-gray 1s infinite linear;
  165. background-color: #e3e3e3;
  166. }
  167. @keyframes looming-gray {
  168. 0% {
  169. background-color: #e3e3e3aa;
  170. }
  171. 50% {
  172. background-color: #e3e3e3;
  173. }
  174. 100% {
  175. background-color: #e3e3e3aa;
  176. }
  177. }
  178. /* 骨架屏1 */
  179. .skeleton-1 {
  180. background-color: #e3e3e3;
  181. background-image: linear-gradient(100deg, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0) 80%);
  182. background-size: 100rpx 100%;
  183. background-repeat: repeat-y;
  184. background-position: 0 0;
  185. animation: skeleton-1 .6s infinite;
  186. }
  187. @keyframes skeleton-1 {
  188. to {
  189. background-position: 200% 0;
  190. }
  191. }
  192. /* 骨架屏2 */
  193. .skeleton-2 {
  194. background-image: linear-gradient(-90deg, #fefefe 0%, #e6e6e6 50%, #fefefe 100%);
  195. background-size: 400% 400%;
  196. background-position: 0 0;
  197. animation: skeleton-2 1.2s ease-in-out infinite;
  198. }
  199. @keyframes skeleton-2 {
  200. to {
  201. background-position: -135% 0;
  202. }
  203. }
  204. </style>