import { ref, watch, computed, PropType, CSSProperties, onBeforeUnmount, getCurrentInstance, } from 'vue'; import { isDef, addUnit, inBrowser, createNamespace } from '../utils'; import Icon from '../icon'; const [createComponent, bem] = createNamespace('image'); export type ImageFit = 'contain' | 'cover' | 'fill' | 'none' | 'scale-down'; export default createComponent({ props: { src: String, alt: String, fit: String as PropType, round: Boolean, width: [Number, String], height: [Number, String], radius: [Number, String], lazyLoad: Boolean, iconPrefix: String, showError: { type: Boolean, default: true, }, showLoading: { type: Boolean, default: true, }, errorIcon: { type: String, default: 'photo-fail', }, loadingIcon: { type: String, default: 'photo', }, }, emits: ['load', 'error'], setup(props, { emit, slots }) { const error = ref(false); const loading = ref(true); const imageRef = ref(); // TODO: types const { $Lazyload } = getCurrentInstance()!.proxy as any; const style = computed(() => { const style: CSSProperties = {}; if (isDef(props.width)) { style.width = addUnit(props.width); } if (isDef(props.height)) { style.height = addUnit(props.height); } if (isDef(props.radius)) { style.overflow = 'hidden'; style.borderRadius = addUnit(props.radius); } return style; }); watch( () => props.src, () => { error.value = false; loading.value = true; } ); const onLoad = (event?: Event) => { loading.value = false; emit('load', event); }; const onError = (event?: Event) => { error.value = true; loading.value = false; emit('error', event); }; const renderLoadingIcon = () => { if (slots.loading) { return slots.loading(); } return ( ); }; const renderErrorIcon = () => { if (slots.error) { return slots.error(); } return ( ); }; const renderPlaceholder = () => { if (loading.value && props.showLoading && inBrowser) { return
{renderLoadingIcon()}
; } if (error.value && props.showError) { return
{renderErrorIcon()}
; } }; const renderImage = () => { if (error.value || !props.src) { return; } const attrs = { alt: props.alt, class: bem('img'), style: { objectFit: props.fit, }, }; if (props.lazyLoad) { return ; } return ( ); }; const onLazyLoaded = ({ el }: { el: HTMLElement }) => { if (el === imageRef.value && loading.value) { onLoad(); } }; const onLazyLoadError = ({ el }: { el: HTMLElement }) => { if (el === imageRef.value && !error.value) { onError(); } }; if ($Lazyload && inBrowser) { $Lazyload.$on('loaded', onLazyLoaded); $Lazyload.$on('error', onLazyLoadError); onBeforeUnmount(() => { $Lazyload.$off('loaded', onLazyLoaded); $Lazyload.$off('error', onLazyLoadError); }); } return () => (
{renderImage()} {renderPlaceholder()} {slots.default?.()}
); }, });