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