UNPKG

element-plus

Version:

A Component Library for Vue3.0

287 lines (280 loc) 8.87 kB
import { defineComponent, ref, computed, watch, onMounted, nextTick, onBeforeUnmount, resolveComponent, openBlock, createBlock, renderSlot, createVNode, toDisplayString, mergeProps, Teleport, 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 !== void 0; 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: { appendToBody: { type: Boolean, default: false }, 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: 2e3 } }, 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) => { if (key.toLowerCase() === "onload") return; 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); 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) ]) : (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, ["src"])), (openBlock(), createBlock(Teleport, { to: "body", disabled: !_ctx.appendToBody }, [ _ctx.preview ? (openBlock(), createBlock(Fragment, { key: 0 }, [ _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, ["z-index", "initial-index", "url-list", "hide-on-click-modal", "onClose"])) : createCommentVNode("v-if", true) ], 2112)) : createCommentVNode("v-if", true) ], 8, ["disabled"])) ], 6); } script.render = render; script.__file = "packages/image/src/index.vue"; script.install = (app) => { app.component(script.name, script); }; const _Image = script; export default _Image;