You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

244 lines
5.4 KiB

3 months ago
  1. <template>
  2. <view class="easy-loadimage" :style="[boxStyle]" :id="uid">
  3. <image class="origin-img" :style="[imageRadius]" :src="imageSrc" mode="scaleToFill" 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',loadingMode]" 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. },
  45. loadingMode: {
  46. type: String,
  47. default: 'looming-gray'
  48. },
  49. openTransition: {
  50. type: Boolean,
  51. default: true,
  52. },
  53. viewHeight: {
  54. type: Number,
  55. default () {
  56. return uni.getSystemInfoSync().windowHeight;
  57. }
  58. },
  59. width: {
  60. type: String,
  61. default: ''
  62. },
  63. height: {
  64. type: String,
  65. default: ''
  66. },
  67. borderRadius: {
  68. type: String,
  69. default: ''
  70. },
  71. radius: {
  72. type: Number,
  73. default: 0
  74. },
  75. },
  76. data() {
  77. const that = this;
  78. return {
  79. urlDomain: this.$Cache.get("imgHost"),
  80. uid: 'uid-' + generateUUID(),
  81. loadImg: false,
  82. showImg: false,
  83. isLoadError: false,
  84. borderLoaded: 0,
  85. showTransition: false,
  86. scrollFn: throttle(function() {
  87. // 加载img时才执行滚动监听判断是否可加载
  88. if (that.loadImg || that.isLoadError) return;
  89. const id = that.uid
  90. const query = uni.createSelectorQuery().in(that);
  91. query.select('#' + id).boundingClientRect(data => {
  92. if (!data) return;
  93. if (data.top - that.viewHeight < 0) {
  94. that.loadImg = !!that.imageSrc;
  95. that.isLoadError = !that.loadImg;
  96. }
  97. }).exec()
  98. }, 200)
  99. }
  100. },
  101. computed: {
  102. boxStyle() {
  103. return {
  104. width: this.width,
  105. height: this.height,
  106. borderRadius: this.radius * 2 + 'rpx'
  107. }
  108. },
  109. imageRadius() {
  110. if (this.radius && this.radius > 0) {
  111. return {
  112. 'border-radius': this.radius * 2 + 'rpx'
  113. }
  114. }
  115. }
  116. },
  117. methods: {
  118. init() {
  119. this.$nextTick(this.onScroll)
  120. },
  121. handleBorderLoad() {
  122. this.borderLoaded = 1;
  123. },
  124. handleBorderError() {
  125. this.borderLoaded = 2;
  126. },
  127. handleImgLoad(e) {
  128. this.showImg = true;
  129. setTimeout(() => {
  130. this.showTransition = true
  131. }, 50)
  132. },
  133. handleImgError(e) {
  134. this.isLoadError = true;
  135. },
  136. onScroll() {
  137. this.scrollFn();
  138. },
  139. },
  140. mounted() {
  141. this.init()
  142. uni.$on('scroll', this.scrollFn);
  143. this.onScroll()
  144. },
  145. beforeDestroy() {
  146. uni.$off('scroll', this.scrollFn);
  147. }
  148. }
  149. </script>
  150. <style scoped lang="scss">
  151. .easy-loadimage {
  152. width: 100%;
  153. height: 100%;
  154. overflow: hidden;
  155. }
  156. /* 官方优化图片tips */
  157. image {
  158. will-change: transform;
  159. overflow: hidden;
  160. object-fit: cover;
  161. }
  162. /* 渐变过渡效果处理 */
  163. image.origin-img {
  164. width: 100%;
  165. height: 100%;
  166. opacity: 0.3;
  167. /* max-height: 360rpx; */
  168. /* border-radius: 14rpx;
  169. overflow: hidden; */
  170. /* min-height: 360rpx; */
  171. }
  172. image.origin-img.show-transition {
  173. transition: opacity .5s;
  174. opacity: 1;
  175. }
  176. image.origin-img.no-transition {
  177. opacity: 1;
  178. }
  179. /* 加载失败、加载中的占位图样式控制 */
  180. .loadfail-img {
  181. height: 100%;
  182. background-size: 50%;
  183. }
  184. .loading-img {
  185. height: 100%;
  186. }
  187. /* 动态灰色若隐若现 */
  188. .looming-gray {
  189. animation: looming-gray 1s infinite linear;
  190. background-color: #e3e3e3;
  191. }
  192. @keyframes looming-gray {
  193. 0% {
  194. background-color: #e3e3e3aa;
  195. }
  196. 50% {
  197. background-color: #e3e3e3;
  198. }
  199. 100% {
  200. background-color: #e3e3e3aa;
  201. }
  202. }
  203. /* 骨架屏1 */
  204. .skeleton-1 {
  205. background-color: #e3e3e3;
  206. background-image: linear-gradient(100deg, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0) 80%);
  207. background-size: 100rpx 100%;
  208. background-repeat: repeat-y;
  209. background-position: 0 0;
  210. animation: skeleton-1 .6s infinite;
  211. }
  212. @keyframes skeleton-1 {
  213. to {
  214. background-position: 200% 0;
  215. }
  216. }
  217. /* 骨架屏2 */
  218. .skeleton-2 {
  219. background-image: linear-gradient(-90deg, #fefefe 0%, #e6e6e6 50%, #fefefe 100%);
  220. background-size: 400% 400%;
  221. background-position: 0 0;
  222. animation: skeleton-2 1.2s ease-in-out infinite;
  223. }
  224. @keyframes skeleton-2 {
  225. to {
  226. background-position: -135% 0;
  227. }
  228. }
  229. </style>