client-image-loader.vue 2.3 KB

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