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.
 
 
 

221 lines
5.7 KiB

  1. <template>
  2. <view
  3. class="u-image"
  4. @tap="onClick"
  5. :style="[wrapStyle, backgroundStyle]"
  6. >
  7. <image
  8. v-if="!isError"
  9. :src="src"
  10. :mode="mode"
  11. @error="onErrorHandler"
  12. @load="onLoadHandler"
  13. :lazy-load="lazyLoad"
  14. class="u-image__image"
  15. :style="{
  16. borderRadius: shape == 'circle' ? '50%' : $u.addUnit(borderRadius),
  17. }"
  18. ></image>
  19. <view v-if="showLoading && loading" class="u-image__loading" :style="{
  20. borderRadius: shape == 'circle' ? '50%' : $u.addUnit(borderRadius),
  21. backgroundColor: this.bgColor
  22. }">
  23. <slot v-if="$slots.loading" name="loading" />
  24. <u-icon v-else :name="loadingIcon"></u-icon>
  25. </view>
  26. <view v-if="showError && isError && !loading" class="u-image__error" :style="{
  27. borderRadius: shape == 'circle' ? '50%' : $u.addUnit(borderRadius),
  28. }">
  29. <slot v-if="$slots.error" name="error" />
  30. <u-icon v-else :name="errorIcon"></u-icon>
  31. </view>
  32. </view>
  33. </template>
  34. <script>
  35. export default {
  36. props: {
  37. // 图片地址
  38. src: {
  39. type: String,
  40. default: ''
  41. },
  42. // 裁剪模式
  43. mode: {
  44. type: String,
  45. default: 'aspectFill'
  46. },
  47. // 宽度,单位任意
  48. width: {
  49. type: [String, Number],
  50. default: '100%'
  51. },
  52. // 高度,单位任意
  53. height: {
  54. type: [String, Number],
  55. default: 'auto'
  56. },
  57. // 图片形状,circle-圆形,square-方形
  58. shape: {
  59. type: String,
  60. default: 'square'
  61. },
  62. // 圆角,单位任意
  63. borderRadius: {
  64. type: [String, Number],
  65. default: 0
  66. },
  67. // 是否懒加载,微信小程序、App、百度小程序、字节跳动小程序
  68. lazyLoad: {
  69. type: Boolean,
  70. default: true
  71. },
  72. // 开启长按图片显示识别微信小程序码菜单
  73. showMenuByLongpress: {
  74. type: Boolean,
  75. default: true
  76. },
  77. // 加载中的图标,或者小图片
  78. loadingIcon: {
  79. type: String,
  80. default: 'photo'
  81. },
  82. // 加载失败的图标,或者小图片
  83. errorIcon: {
  84. type: String,
  85. default: 'error-circle'
  86. },
  87. // 是否显示加载中的图标或者自定义的slot
  88. showLoading: {
  89. type: Boolean,
  90. default: true
  91. },
  92. // 是否显示加载错误的图标或者自定义的slot
  93. showError: {
  94. type: Boolean,
  95. default: true
  96. },
  97. // 是否需要淡入效果
  98. fade: {
  99. type: Boolean,
  100. default: true
  101. },
  102. // 只支持网络资源,只对微信小程序有效
  103. webp: {
  104. type: Boolean,
  105. default: false
  106. },
  107. // 过渡时间,单位ms
  108. duration: {
  109. type: [String, Number],
  110. default: 500
  111. },
  112. // 背景颜色,用于深色页面加载图片时,为了和背景色融合
  113. bgColor: {
  114. type: String,
  115. default: '#f3f4f6'
  116. }
  117. },
  118. data() {
  119. return {
  120. // 图片是否加载错误,如果是,则显示错误占位图
  121. isError: false,
  122. // 初始化组件时,默认为加载中状态
  123. loading: true,
  124. // 不透明度,为了实现淡入淡出的效果
  125. opacity: 1,
  126. // 过渡时间,因为props的值无法修改,故需要一个中间值
  127. durationTime: this.duration,
  128. // 图片加载完成时,去掉背景颜色,因为如果是png图片,就会显示灰色的背景
  129. backgroundStyle: {}
  130. };
  131. },
  132. computed: {
  133. wrapStyle() {
  134. let style = {};
  135. // 通过调用addUnit()方法,如果有单位,如百分比,px单位等,直接返回,如果是纯粹的数值,则加上rpx单位
  136. style.width = this.$u.addUnit(this.width);
  137. style.height = this.$u.addUnit(this.height);
  138. // 如果是配置了圆形,设置50%的圆角,否则按照默认的配置值
  139. style.borderRadius = this.shape == 'circle' ? '50%' : this.$u.addUnit(this.borderRadius);
  140. // 如果设置圆角,必须要有hidden,否则可能圆角无效
  141. style.overflow = this.borderRadius > 0 ? 'hidden' : 'visible';
  142. if(this.fade) {
  143. style.opacity = this.opacity;
  144. style.transition = `opacity ${Number(this.durationTime) / 1000}s ease-in-out`;
  145. }
  146. return style;
  147. }
  148. },
  149. methods: {
  150. // 点击图片
  151. onClick() {
  152. this.$emit('click');
  153. },
  154. // 图片加载失败
  155. onErrorHandler() {
  156. this.loading = false;
  157. this.isError = true;
  158. this.$emit('error');
  159. },
  160. // 图片加载完成,标记loading结束
  161. onLoadHandler() {
  162. this.loading = false;
  163. this.isError = false;
  164. this.$emit('load');
  165. // 如果不需要动画效果,就不执行下方代码,同时移除加载时的背景颜色
  166. // 否则无需fade效果时,png图片依然能看到下方的背景色
  167. if(!this.fade) return this.removeBgColor();
  168. // 原来opacity为1(不透明,是为了显示占位图),改成0(透明,意味着该元素显示的是背景颜色,默认的灰色),再改成1,是为了获得过渡效果
  169. this.opacity = 0;
  170. // 这里设置为0,是为了图片展示到背景全透明这个过程时间为0,延时之后延时之后重新设置为duration,是为了获得背景透明(灰色)
  171. // 到图片展示的过程中的淡入效果
  172. this.durationTime = 0;
  173. // 延时50ms,否则在浏览器H5,过渡效果无效
  174. setTimeout(() => {
  175. this.durationTime = this.duration;
  176. this.opacity = 1;
  177. setTimeout(() => {
  178. this.removeBgColor();
  179. }, this.durationTime)
  180. }, 50)
  181. },
  182. // 移除图片的背景色
  183. removeBgColor() {
  184. // 淡入动画过渡完成后,将背景设置为透明色,否则png图片会看到灰色的背景
  185. this.backgroundStyle = {
  186. backgroundColor: 'transparent'
  187. };
  188. }
  189. }
  190. };
  191. </script>
  192. <style scoped lang="scss">
  193. @import '../../libs/css/style.components.scss';
  194. .u-image {
  195. position: relative;
  196. transition: opacity 0.5s ease-in-out;
  197. &__image {
  198. width: 100%;
  199. height: 100%;
  200. }
  201. &__loading, &__error {
  202. position: absolute;
  203. top: 0;
  204. left: 0;
  205. width: 100%;
  206. height: 100%;
  207. display: flex;
  208. align-items: center;
  209. justify-content: center;
  210. background-color: $u-bg-color;
  211. color: $u-tips-color;
  212. font-size: 46rpx;
  213. }
  214. }
  215. </style>