element-plus
Version:
A Component Library for Vue3.0
289 lines (280 loc) • 10.3 kB
JavaScript
import { defineComponent, ref, computed, watch, onMounted, nextTick, onBeforeUnmount, resolveComponent, openBlock, createBlock, renderSlot, createVNode, toDisplayString, mergeProps, Fragment, createCommentVNode } from 'vue';
import throttle from 'lodash/throttle';
import { useAttrs } from '../hooks';
import isServer from '../utils/isServer';
import { getScrollContainer, on, off, isInContainer } from '../utils/dom';
import { t } from '../locale';
import ImageViewer from '../el-image-viewer';
/**
* Make a map and return a function for checking if a key
* is in that map.
* IMPORTANT: all calls of this function must be prefixed with
* \/\*#\_\_PURE\_\_\*\/
* So that rollup can tree-shake them if necessary.
*/
const EMPTY_OBJ = (process.env.NODE_ENV !== 'production')
? Object.freeze({})
: {};
const EMPTY_ARR = (process.env.NODE_ENV !== 'production') ? Object.freeze([]) : [];
const isString = (val) => typeof val === 'string';
const isSupportObjectFit = () => document.documentElement.style.objectFit !== undefined;
const isHtmlEle = e => e && e.nodeType === 1;
const ObjectFit = {
NONE: 'none',
CONTAIN: 'contain',
COVER: 'cover',
FILL: 'fill',
SCALE_DOWN: 'scale-down',
};
let prevOverflow = '';
var script = defineComponent({
name: 'ElImage',
components: {
ImageViewer,
},
inheritAttrs: false,
props: {
hideOnClickModal: {
type: Boolean,
default: false,
},
src: {
type: String,
default: '',
},
fit: {
type: String,
default: '',
},
lazy: {
type: Boolean,
default: false,
},
scrollContainer: {
type: [String, Object],
default: null,
},
previewSrcList: {
type: Array,
default: () => [],
},
zIndex: {
type: Number,
default: 2000,
},
},
emits: ['error'],
setup(props, { emit }) {
const attrs = useAttrs();
const hasLoadError = ref(false);
const loading = ref(true);
const imgWidth = ref(0);
const imgHeight = ref(0);
const showViewer = ref(false);
const container = ref(null);
let _scrollContainer = null;
let _lazyLoadHandler = null;
const imageStyle = computed(() => {
const { fit } = props;
if (!isServer && fit) {
return isSupportObjectFit()
? { 'object-fit': fit }
: getImageStyle(fit);
}
return {};
});
const alignCenter = computed(() => {
const { fit } = props;
return !isServer && !isSupportObjectFit() && fit !== ObjectFit.FILL;
});
const preview = computed(() => {
const { previewSrcList } = props;
return Array.isArray(previewSrcList) && previewSrcList.length > 0;
});
const imageIndex = computed(() => {
const { src, previewSrcList } = props;
let previewIndex = 0;
const srcIndex = previewSrcList.indexOf(src);
if (srcIndex >= 0) {
previewIndex = srcIndex;
}
return previewIndex;
});
function getImageStyle(fit) {
const imageWidth = imgWidth.value;
const imageHeight = imgHeight.value;
if (!container.value)
return {};
const { clientWidth: containerWidth, clientHeight: containerHeight, } = container.value;
if (!imageWidth || !imageHeight || !containerWidth || !containerHeight)
return {};
const imageAspectRatio = imageWidth / imageHeight;
const containerAspectRatio = containerWidth / containerHeight;
if (fit === ObjectFit.SCALE_DOWN) {
const isSmaller = imageWidth < containerWidth && imageHeight < containerHeight;
fit = isSmaller ? ObjectFit.NONE : ObjectFit.CONTAIN;
}
switch (fit) {
case ObjectFit.NONE:
return { width: 'auto', height: 'auto' };
case ObjectFit.CONTAIN:
return (imageAspectRatio < containerAspectRatio) ? { width: 'auto' } : { height: 'auto' };
case ObjectFit.COVER:
return (imageAspectRatio < containerAspectRatio) ? { height: 'auto' } : { width: 'auto' };
default:
return {};
}
}
const loadImage = () => {
if (isServer)
return;
const attributes = attrs.value;
loading.value = true;
hasLoadError.value = false;
const img = new Image();
img.onload = e => handleLoad(e, img);
img.onerror = handleError;
Object.keys(attributes)
.forEach(key => {
const value = attributes[key];
img.setAttribute(key, value);
});
img.src = props.src;
};
function handleLoad(e, img) {
imgWidth.value = img.width;
imgHeight.value = img.height;
loading.value = false;
hasLoadError.value = false;
}
function handleError(e) {
loading.value = false;
hasLoadError.value = true;
emit('error', e);
}
function handleLazyLoad() {
if (isInContainer(container.value, _scrollContainer)) {
loadImage();
removeLazyLoadListener();
}
}
function addLazyLoadListener() {
if (isServer)
return;
const { scrollContainer } = props;
if (isHtmlEle(scrollContainer)) {
_scrollContainer = scrollContainer;
}
else if (isString(scrollContainer) && scrollContainer !== '') {
_scrollContainer = document.querySelector(scrollContainer);
}
else {
_scrollContainer = getScrollContainer(container.value);
}
if (_scrollContainer) {
_lazyLoadHandler = throttle(handleLazyLoad, 200);
on(_scrollContainer, 'scroll', _lazyLoadHandler);
setTimeout(() => handleLazyLoad(), 100);
}
}
function removeLazyLoadListener() {
if (isServer || !_scrollContainer || !_lazyLoadHandler)
return;
off(_scrollContainer, 'scroll', _lazyLoadHandler);
_scrollContainer = null;
_lazyLoadHandler = null;
}
function clickHandler() {
if (!preview.value) {
return;
}
prevOverflow = document.body.style.overflow;
document.body.style.overflow = 'hidden';
showViewer.value = true;
}
function closeViewer() {
document.body.style.overflow = prevOverflow;
showViewer.value = false;
}
watch(() => props.src, () => {
loadImage();
});
onMounted(() => {
if (props.lazy) {
nextTick(addLazyLoadListener);
}
else {
loadImage();
}
});
onBeforeUnmount(() => {
props.lazy && removeLazyLoadListener();
});
return {
attrs,
loading,
hasLoadError,
showViewer,
imgWidth,
imgHeight,
imageStyle,
alignCenter,
preview,
imageIndex,
clickHandler,
closeViewer,
container,
handleError,
t,
};
},
});
const _hoisted_1 = /*#__PURE__*/createVNode("div", { class: "el-image__placeholder" }, null, -1 /* HOISTED */);
const _hoisted_2 = { class: "el-image__error" };
function render(_ctx, _cache, $props, $setup, $data, $options) {
const _component_image_viewer = resolveComponent("image-viewer");
return (openBlock(), createBlock("div", {
ref: "container",
class: ['el-image', _ctx.$attrs.class],
style: _ctx.$attrs.style
}, [
(_ctx.loading)
? renderSlot(_ctx.$slots, "placeholder", { key: 0 }, () => [
_hoisted_1
])
: (_ctx.hasLoadError)
? renderSlot(_ctx.$slots, "error", { key: 1 }, () => [
createVNode("div", _hoisted_2, toDisplayString(_ctx.t('el.image.error')), 1 /* TEXT */)
])
: (openBlock(), createBlock("img", mergeProps({
key: 2,
class: "el-image__inner"
}, _ctx.attrs, {
src: _ctx.src,
style: _ctx.imageStyle,
class: { 'el-image__inner--center': _ctx.alignCenter, 'el-image__preview': _ctx.preview },
onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.clickHandler && _ctx.clickHandler(...args)))
}), null, 16 /* FULL_PROPS */, ["src"])),
(_ctx.preview)
? (openBlock(), createBlock(Fragment, { key: 3 }, [
(_ctx.showViewer)
? (openBlock(), createBlock(_component_image_viewer, {
key: 0,
"z-index": _ctx.zIndex,
"initial-index": _ctx.imageIndex,
"url-list": _ctx.previewSrcList,
"hide-on-click-modal": _ctx.hideOnClickModal,
onClose: _ctx.closeViewer
}, null, 8 /* PROPS */, ["z-index", "initial-index", "url-list", "hide-on-click-modal", "onClose"]))
: createCommentVNode("v-if", true)
], 64 /* STABLE_FRAGMENT */))
: createCommentVNode("v-if", true)
], 6 /* CLASS, STYLE */))
}
script.render = render;
script.__file = "packages/image/src/index.vue";
script.install = (app) => {
app.component(script.name, script);
};
const _Image = script;
export default _Image;