easy-loadimage.vue 5.4 KB

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