easy-loadimage.vue 5.5 KB

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