client-image-loader.vue 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. <template>
  2. <view class="img-wrap" :style="wrapStyle" @click="onClick">
  3. <image class="img" :src="src" :mode="mode" @load="onLoad" @error="onError" />
  4. <view v-if="loading && src" class="img-mask">
  5. <view class="spinner" />
  6. </view>
  7. </view>
  8. </template>
  9. <script setup>
  10. import { computed, ref, watch, onBeforeUnmount } from 'vue';
  11. const props = defineProps({
  12. src: {
  13. type: String,
  14. default: ''
  15. },
  16. mode: {
  17. type: String,
  18. default: 'aspectFill'
  19. },
  20. radius: {
  21. type: String,
  22. default: '0'
  23. }
  24. });
  25. const emit = defineEmits(['click', 'load', 'error']);
  26. const loading = ref(true);
  27. let loadingTimer = null;
  28. function clearLoadingTimer() {
  29. if (loadingTimer) {
  30. clearTimeout(loadingTimer);
  31. loadingTimer = null;
  32. }
  33. }
  34. function startLoadingFallback() {
  35. clearLoadingTimer();
  36. // H5 缓存命中场景偶发 load 事件未触发,超时后自动去除遮罩,避免图片“已加载但看不见”
  37. loadingTimer = setTimeout(() => {
  38. loading.value = false;
  39. loadingTimer = null;
  40. }, 1200);
  41. }
  42. watch(
  43. () => props.src,
  44. () => {
  45. loading.value = !!props.src;
  46. if (loading.value) {
  47. startLoadingFallback();
  48. } else {
  49. clearLoadingTimer();
  50. }
  51. },
  52. { immediate: true }
  53. );
  54. function onLoad(e) {
  55. clearLoadingTimer();
  56. loading.value = false;
  57. emit('load', e);
  58. }
  59. function onError(e) {
  60. clearLoadingTimer();
  61. loading.value = false;
  62. emit('error', e);
  63. }
  64. onBeforeUnmount(() => {
  65. clearLoadingTimer();
  66. });
  67. const wrapStyle = computed(() => ({
  68. borderRadius: props.radius
  69. }));
  70. function onClick(e) {
  71. emit('click', e);
  72. }
  73. </script>
  74. <style scoped lang="scss">
  75. .img-wrap {
  76. position: relative;
  77. width: 100%;
  78. height: 100%;
  79. overflow: hidden;
  80. }
  81. .img {
  82. display: block;
  83. width: 100%;
  84. height: 100%;
  85. }
  86. .img-mask {
  87. position: absolute;
  88. inset: 0;
  89. background: rgba(245, 248, 247, 0.82);
  90. display: flex;
  91. align-items: center;
  92. justify-content: center;
  93. }
  94. .spinner {
  95. width: 36rpx;
  96. height: 36rpx;
  97. border-radius: 50%;
  98. border: 4rpx solid rgba(10, 159, 109, 0.22);
  99. border-top-color: #0a9f6d;
  100. animation: img-spin 0.9s linear infinite;
  101. }
  102. @keyframes img-spin {
  103. to {
  104. transform: rotate(360deg);
  105. }
  106. }
  107. </style>