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.
247 lines
6.7 KiB
247 lines
6.7 KiB
<template>
|
|
<view class="s-image" :class="c_class" :style="c_style" @click="handleClick">
|
|
<view :id="$sUid" class="s-image__wrap">
|
|
<image
|
|
v-if="startLoad"
|
|
class="s-image__img"
|
|
:src="loadSrc"
|
|
:mode="mode"
|
|
:lazy-load="false"
|
|
:fade-show="false"
|
|
:webp="webp"
|
|
:show-menu-by-longpress="showMenuByLongpress"
|
|
:style="image_style"
|
|
@load="handleLoad"
|
|
@error="handleError"
|
|
/>
|
|
<view v-if="showLoading && isLoading" class="s-image__loading">
|
|
<slot name="loading">
|
|
<s-icon
|
|
v-if="showIcon && loadingIcon"
|
|
custom-class="s-image__loading-icon"
|
|
:class-prefix="iconPrefix"
|
|
:name="loadingIcon"
|
|
:size="iconSize"
|
|
/>
|
|
</slot>
|
|
</view>
|
|
<view v-else-if="showError && isError" class="s-image__error">
|
|
<slot name="error">
|
|
<s-icon
|
|
v-if="showIcon && errorIcon"
|
|
custom-class="s-image__error-icon"
|
|
:class-prefix="iconPrefix"
|
|
:name="errorIcon"
|
|
:size="iconSize"
|
|
/>
|
|
</slot>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import componentMixin from '../../mixins/componentMixin';
|
|
|
|
/**
|
|
* s-image 图片
|
|
* @description 图片组件
|
|
* @property {String} src 图片路径
|
|
* @property {String} mode = [scaleToFill|aspectFill|widthFix] 图片裁剪、缩放的模式,默认 scaleToFill
|
|
* @property {Number|String} width 图片宽度
|
|
* @property {Number|String} height 图片高度
|
|
* @property {Number|String} radius 图片圆角
|
|
* @property {Boolean} round 是否显示为圆形
|
|
* @property {Boolean} block 是否为块级元素,消除间隙,默认false
|
|
* @property {Boolean} lazyLoad 是否开启懒加载
|
|
* @property {Number|String} threshold 触发加载时的位置
|
|
* @property {Boolean} fadeShow 图片加载成功后显示时fade效果
|
|
* @property {Number|String} fadeDuration fade效果过渡时间,单位ms
|
|
* @property {Boolean} showLoading 是否显示加载状态
|
|
* @property {Boolean} showError 是否显示加载失败状态
|
|
* @property {String} loadingIcon 加载时提示的图标名称或图片链接
|
|
* @property {String} errorIcon 失败时提示的图标名称或图片链接
|
|
* @property {String} iconPrefix 图标类名前缀,同 Icon 组件的 class-prefix 属性
|
|
* @property {Number|String} iconSize 图标大小
|
|
* @property {Boolean} webp image webp属性 默认为false
|
|
* @property {Boolean} showMenuByLongpress image show-menu-by-longpress属性 默认为false
|
|
* @property {Boolean} showIcon 是否显示加载中或加载失败图标
|
|
* @event {Function} click 点击事件
|
|
* @event {Function} load 加载成功事件
|
|
* @event {Function} error 加载失败事件
|
|
* @example <s-image src="" width="100" height="100" />
|
|
*/
|
|
export default {
|
|
name: 'SImage',
|
|
mixins: [componentMixin],
|
|
props: {
|
|
src: {
|
|
default: '',
|
|
},
|
|
mode: {
|
|
type: String,
|
|
default: 'scaleToFill',
|
|
},
|
|
width: [Number, String],
|
|
height: [Number, String],
|
|
radius: [Number, String],
|
|
round: Boolean,
|
|
block: Boolean,
|
|
showLoading: Boolean,
|
|
showError: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
loadingIcon: {
|
|
type: String,
|
|
default: 'photo',
|
|
},
|
|
errorIcon: {
|
|
type: String,
|
|
default: 'photo-fail',
|
|
},
|
|
iconPrefix: String,
|
|
iconSize: [Number, String],
|
|
lazyLoad: Boolean,
|
|
threshold: {
|
|
type: [Number, String],
|
|
default: 100,
|
|
},
|
|
fadeShow: Boolean,
|
|
fadeDuration: {
|
|
type: [Number, String],
|
|
default: 300,
|
|
},
|
|
loading: Boolean,
|
|
error: Boolean,
|
|
showIcon: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
webp: Boolean,
|
|
showMenuByLongpress: Boolean,
|
|
},
|
|
data: () => ({
|
|
loadSrc: '',
|
|
startLoad: false,
|
|
isLoading: true,
|
|
isError: false,
|
|
}),
|
|
computed: {
|
|
c_class() {
|
|
return this.$mergeClass({
|
|
's-image--round': this.round,
|
|
}, this.custom_class);
|
|
},
|
|
c_style() {
|
|
return this.$mergeStyle({
|
|
width: this.$addUnit(this.width),
|
|
height: this.$addUnit(this.height),
|
|
borderRadius: this.$addUnit(this.radius),
|
|
display: this.block ? 'block' : '',
|
|
}, this.custom_style);
|
|
},
|
|
image_style() {
|
|
const style = {};
|
|
if (this.fadeShow) {
|
|
const showImage = !(this.isLoading || this.isError);
|
|
if (showImage) {
|
|
style.opacity = 1;
|
|
style.transition = `opacity ${this.fadeDuration}ms ease-in-out`;
|
|
} else {
|
|
style.opacity = 0;
|
|
}
|
|
}
|
|
return this.$mergeStyle(style);
|
|
},
|
|
lazyState() {
|
|
return [
|
|
this.lazyLoad,
|
|
this.startLoad,
|
|
this.threshold,
|
|
];
|
|
},
|
|
},
|
|
watch: {
|
|
loading(bool) {
|
|
this.updateLoadingStatus(bool);
|
|
},
|
|
error(bool) {
|
|
this.updateErrorStatus(bool);
|
|
},
|
|
src: {
|
|
immediate: true,
|
|
handler(src) {
|
|
this.loadSrc = this.lazyLoad ? '' : src;
|
|
this.startLoad = !this.lazyLoad;
|
|
if (src) {
|
|
this.updateLoadingStatus(true);
|
|
this.updateErrorStatus(false);
|
|
} else {
|
|
this.updateLoadingStatus(false);
|
|
this.updateErrorStatus(true);
|
|
}
|
|
},
|
|
},
|
|
lazyState() {
|
|
this.startObserver();
|
|
},
|
|
},
|
|
mounted() {
|
|
this.startObserver();
|
|
},
|
|
beforeDestroy() {
|
|
this.stopObserver();
|
|
},
|
|
methods: {
|
|
updateLoadingStatus(bool) {
|
|
this.isLoading = this.loading || bool;
|
|
},
|
|
updateErrorStatus(bool) {
|
|
this.isError = this.error || bool;
|
|
},
|
|
handleClick(e) {
|
|
this.$emit('click', e);
|
|
},
|
|
handleLoad(e) {
|
|
if (this.startLoad) {
|
|
this.updateLoadingStatus(false);
|
|
this.updateErrorStatus(false);
|
|
this.$emit('load', e);
|
|
}
|
|
},
|
|
handleError(e) {
|
|
if (this.startLoad) {
|
|
this.updateLoadingStatus(false);
|
|
this.updateErrorStatus(true);
|
|
this.$emit('error', e);
|
|
}
|
|
},
|
|
startObserver() {
|
|
this.stopObserver();
|
|
if (this._isMounted && !this._isDestroyed && this.lazyLoad && !this.startLoad) {
|
|
const threshold = this.$toPx(this.threshold);
|
|
this.observe = uni.createIntersectionObserver(this);
|
|
this.observe.relativeToViewport({
|
|
top: threshold,
|
|
bottom: threshold,
|
|
}).observe(`#${this.$sUid}`, res => {
|
|
if (res.intersectionRatio > 0) {
|
|
this.loadSrc = this.src;
|
|
this.startLoad = true;
|
|
this.stopObserver();
|
|
}
|
|
});
|
|
}
|
|
},
|
|
stopObserver() {
|
|
if (this.observe) {
|
|
this.observe.disconnect();
|
|
this.observe = null;
|
|
}
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss" src="./index.scss"></style>
|
|
|