easy-loadimage.vue 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  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. this.showImg = true;
  122. // this.$nextTick(function(){
  123. // this.showTransition = true
  124. // })
  125. setTimeout(() => {
  126. this.showTransition = true
  127. }, 50)
  128. },
  129. handleImgError(e) {
  130. this.isLoadError = true;
  131. },
  132. onScroll() {
  133. this.scrollFn();
  134. },
  135. },
  136. mounted() {
  137. this.init()
  138. uni.$on('scroll', this.scrollFn);
  139. this.onScroll()
  140. },
  141. beforeDestroy() {
  142. uni.$off('scroll', this.scrollFn);
  143. }
  144. }
  145. </script>
  146. <style scoped>
  147. .easy-loadimage {
  148. position: relative;
  149. }
  150. .border-img {
  151. position: absolute;
  152. width: 100%;
  153. height: 100%;
  154. /* max-height: 360rpx; */
  155. top: 0;
  156. left: 0;
  157. }
  158. /* 官方优化图片tips */
  159. image {
  160. will-change: transform
  161. }
  162. /* 渐变过渡效果处理 */
  163. image.origin-img {
  164. width: 100%;
  165. height: 100%;
  166. opacity: 0.3;
  167. /* max-height: 360rpx; */
  168. }
  169. image.origin-img.show-transition {
  170. transition: opacity .5s;
  171. opacity: 1;
  172. }
  173. image.origin-img.no-transition {
  174. opacity: 1;
  175. }
  176. /* 渐变过渡效果处理 */
  177. image.border-img {
  178. width: 100%;
  179. height: 100%;
  180. opacity: 0.3;
  181. /* max-height: 360rpx; */
  182. }
  183. image.border-img.show-transition {
  184. transition: opacity .5s;
  185. opacity: 1;
  186. }
  187. image.border-img.no-transition {
  188. opacity: 1;
  189. }
  190. /* 加载失败、加载中的占位图样式控制 */
  191. .loadfail-img {
  192. height: 100%;
  193. background: url('~@/static/easy-loadimage/loadfail.png') no-repeat center;
  194. background-size: 50%;
  195. }
  196. .loading-img {
  197. height: 100%;
  198. }
  199. /* 转圈 */
  200. .spin-circle {
  201. background: url('~@/static/easy-loadimage/loading.png') no-repeat center;
  202. background-size: 60%;
  203. }
  204. /* 动态灰色若隐若现 */
  205. .looming-gray {
  206. animation: looming-gray 1s infinite linear;
  207. background-color: #e3e3e3;
  208. border-radius: 12rpx;
  209. }
  210. @keyframes looming-gray {
  211. 0% {
  212. background-color: #e3e3e3aa;
  213. }
  214. 50% {
  215. background-color: #e3e3e3;
  216. }
  217. 100% {
  218. background-color: #e3e3e3aa;
  219. }
  220. }
  221. /* 骨架屏1 */
  222. .skeleton-1 {
  223. background-color: #e3e3e3;
  224. background-image: linear-gradient(100deg, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0) 80%);
  225. background-size: 100rpx 100%;
  226. background-repeat: repeat-y;
  227. background-position: 0 0;
  228. animation: skeleton-1 .6s infinite;
  229. }
  230. @keyframes skeleton-1 {
  231. to {
  232. background-position: 200% 0;
  233. }
  234. }
  235. /* 骨架屏2 */
  236. .skeleton-2 {
  237. background-image: linear-gradient(-90deg, #fefefe 0%, #e6e6e6 50%, #fefefe 100%);
  238. background-size: 400% 400%;
  239. background-position: 0 0;
  240. animation: skeleton-2 1.2s ease-in-out infinite;
  241. }
  242. @keyframes skeleton-2 {
  243. to {
  244. background-position: -135% 0;
  245. }
  246. }
  247. </style>