hongluan-ui
Version:
Hongluan Component Library for Vue 3
1 lines • 14.7 kB
Source Map (JSON)
{"version":3,"file":"image-viewer2.mjs","sources":["../../../../../../packages/components/image-viewer/src/image-viewer.vue"],"sourcesContent":["<template>\n <hl-teleport to=\"body\" :disabled=\"!teleported\">\n <transition name=\"viewer-fade\">\n <div\n ref=\"wrapper\"\n :tabindex=\"-1\"\n :class=\"namespace\"\n :style=\"{ zIndex }\"\n >\n <div\n class=\"image-viewer-mask\"\n @click.self=\"hideOnClickModal && hide()\"\n ></div>\n\n <!-- ACTIONS -->\n <hl-group gap=\"var(--sm)\" align=\"items-middle items-around\" class=\"image-viewer-tools\">\n <hl-group v-if=\"$slots['tools-before']\" class=\"tools-group\">\n <slot name=\"tools-before\" :index=\"activeIndex\"></slot>\n </hl-group>\n <hl-group gap=\"var(--xs)\" class=\"tools-group\">\n <hl-icon @click=\"handleActions('zoomOut')\">\n <system-zoom-out />\n </hl-icon>\n <hl-icon @click=\"handleActions('zoomIn')\">\n <system-zoom-in />\n </hl-icon>\n <hl-icon name=\"Refresh\" @click=\"handleActions('anticlockwise')\">\n <system-refresh />\n </hl-icon>\n <hl-icon @click=\"toggleMode\">\n <system-maximize v-if=\"mode.icon === 'Maximize'\" />\n <system-minimize v-if=\"mode.icon === 'Minimize'\" />\n </hl-icon>\n </hl-group>\n <hl-group v-if=\"!isSingle\" gap=\"var(--xs)\" class=\"tools-group\">\n <hl-icon :class=\"{ 'is-disabled': !infinite && isFirst }\" @click=\"prev\">\n <system-arrow-left />\n </hl-icon>\n <hl-icon :class=\"{ 'is-disabled': !infinite && isLast }\" @click=\"next\">\n <system-arrow-right />\n </hl-icon>\n </hl-group>\n <hl-group class=\"tools-group\">\n <hl-icon @click=\"hide\">\n <system-close />\n </hl-icon>\n </hl-group>\n <hl-group v-if=\"$slots['tools-after']\" class=\"tools-group\">\n <slot name=\"tools-after\" :index=\"activeIndex\"></slot>\n </hl-group>\n </hl-group>\n <!-- CANVAS -->\n <div class=\"image-viewer-canvas\">\n <img\n v-for=\"(url, i) in urlList\"\n v-show=\"i === activeIndex\"\n :ref=\"(el) => (imgRefs[i] = el)\"\n :key=\"url\"\n :src=\"url\"\n :style=\"imgStyle\"\n class=\"image-viewer-img\"\n :crossorigin=\"crossorigin\"\n @load=\"handleImgLoad\"\n @error=\"handleImgError\"\n @mousedown=\"handleMouseDown\"\n >\n </div>\n <slot></slot>\n </div>\n </transition>\n </hl-teleport>\n</template>\n\n<script lang='ts'>\nimport { defineComponent, computed, ref, onMounted, watch, nextTick, effectScope, shallowRef } from 'vue'\nimport { useEventListener } from '@vueuse/core'\nimport { throttle } from 'lodash-unified'\nimport { keysOf } from '@hongluan-ui/utils'\nimport { EVENT_CODE } from '@hongluan-ui/constants'\nimport { useLocale, useNamespace, useZIndex } from '@hongluan-ui/hooks'\nimport HlIcon from '@hongluan-ui/components/icon'\nimport HlGroup from '@hongluan-ui/components/group'\nimport HlTeleport from '@hongluan-ui/components/teleport'\nimport {\n SystemClose,\n SystemArrowLeft,\n SystemArrowRight,\n SystemZoomIn,\n SystemZoomOut,\n SystemRefresh,\n SystemMaximize,\n SystemMinimize,\n} from '@hongluan-ui/components/system-icon'\nimport { imageViewerProps } from './image-viewer'\n\nimport type { CSSProperties } from 'vue'\nimport type { ImageViewerAction, ImageViewerMode } from './image-viewer'\n\nconst modes: Record<'CONTAIN' | 'ORIGINAL', ImageViewerMode> = {\n CONTAIN: {\n name: 'contain',\n icon: 'Maximize',\n },\n ORIGINAL: {\n name: 'original',\n icon: 'Minimize',\n },\n}\n\nexport default defineComponent({\n name: 'ImageViewer',\n components: {\n HlIcon,\n HlGroup,\n HlTeleport,\n SystemClose,\n SystemArrowLeft,\n SystemArrowRight,\n SystemZoomIn,\n SystemZoomOut,\n SystemRefresh,\n SystemMaximize,\n SystemMinimize,\n },\n props: imageViewerProps,\n emits: ['close', 'switch', 'rotate'],\n\n setup(props, { emit }) {\n const { namespace } = useNamespace('image-viewer')\n const { nextZIndex } = useZIndex()\n\n const { t } = useLocale()\n const wrapper = ref<HTMLDivElement>()\n const imgRefs = ref<HTMLImageElement[]>([])\n\n const scopeEventListener = effectScope()\n\n const loading = ref(true)\n const activeIndex = ref(props.initialIndex)\n const mode = shallowRef<ImageViewerMode>(modes.CONTAIN)\n\n const transform = ref({\n scale: 1,\n deg: 0,\n offsetX: 0,\n offsetY: 0,\n enableTransition: false,\n })\n const zIndex = ref(props.zIndex ?? nextZIndex())\n\n const isSingle = computed(() => {\n const { urlList } = props\n return urlList.length <= 1\n })\n\n const isFirst = computed(() => {\n return activeIndex.value === 0\n })\n\n const isLast = computed(() => {\n return activeIndex.value === props.urlList.length - 1\n })\n\n const currentImg = computed(() => {\n return props.urlList[activeIndex.value]\n })\n\n const imgStyle = computed(() => {\n const { scale, deg, offsetX, offsetY, enableTransition } = transform.value\n let translateX = offsetX / scale\n let translateY = offsetY / scale\n\n const radian = (deg * Math.PI) / 180\n const cosRadian = Math.cos(radian)\n const sinRadian = Math.sin(radian)\n translateX = translateX * cosRadian + translateY * sinRadian\n translateY = translateY * cosRadian - (offsetX / scale) * sinRadian\n\n const style: CSSProperties = {\n transform: `scale(${scale}) rotate(${deg}deg) translate(${translateX}px, ${translateY}px)`,\n transition: enableTransition ? 'transform .3s' : '',\n }\n if (mode.value.name === modes.CONTAIN.name) {\n style.maxWidth = style.maxHeight = '100%'\n }\n return style\n })\n\n function hide() {\n unregisterEventListener()\n emit('close')\n }\n\n function registerEventListener() {\n const keydownHandler = throttle((e: KeyboardEvent) => {\n switch (e.code) {\n // ESC\n case EVENT_CODE.esc:\n hide()\n break\n // SPACE\n case EVENT_CODE.space:\n toggleMode()\n break\n // LEFT_ARROW\n case EVENT_CODE.left:\n prev()\n break\n // UP_ARROW\n case EVENT_CODE.up:\n handleActions('zoomIn')\n break\n // RIGHT_ARROW\n case EVENT_CODE.right:\n next()\n break\n // DOWN_ARROW\n case EVENT_CODE.down:\n handleActions('zoomOut')\n break\n }\n })\n\n const mousewheelHandler = throttle((e: WheelEvent) => {\n const delta = e.deltaY || e.deltaX\n handleActions(delta < 0 ? 'zoomIn' : 'zoomOut', {\n zoomRate: props.zoomRate,\n enableTransition: false,\n })\n })\n\n\n scopeEventListener.run(() => {\n useEventListener(document, 'keydown', keydownHandler)\n useEventListener(document, 'wheel', mousewheelHandler)\n })\n }\n\n function unregisterEventListener() {\n scopeEventListener.stop()\n }\n\n function handleImgLoad() {\n loading.value = false\n }\n\n function handleImgError(e: Event) {\n loading.value = false\n ;(e.target as HTMLImageElement).alt = t('hl.image.error')\n }\n\n function handleMouseDown(e: MouseEvent) {\n if (loading.value || e.button !== 0 || !wrapper.value) return\n transform.value.enableTransition = false\n\n const { offsetX, offsetY } = transform.value\n const startX = e.pageX\n const startY = e.pageY\n\n const dragHandler = throttle((ev: MouseEvent) => {\n transform.value = {\n ...transform.value,\n offsetX: offsetX + ev.pageX - startX,\n offsetY: offsetY + ev.pageY - startY,\n }\n })\n const removeMousemove = useEventListener(\n document,\n 'mousemove',\n dragHandler,\n )\n useEventListener(document, 'mouseup', () => {\n removeMousemove()\n })\n\n e.preventDefault()\n }\n\n function reset() {\n transform.value = {\n scale: 1,\n deg: 0,\n offsetX: 0,\n offsetY: 0,\n enableTransition: false,\n }\n }\n\n function toggleMode() {\n if (loading.value) return\n\n const modeNames = keysOf(modes)\n const modeValues = Object.values(modes)\n const currentMode = mode.value.name\n const index = modeValues.findIndex(i => i.name === currentMode)\n const nextIndex = (index + 1) % modeNames.length\n mode.value = modes[modeNames[nextIndex]]\n\n reset()\n }\n\n function setActiveItem(index: number) {\n const len = props.urlList.length\n activeIndex.value = (index + len) % len\n }\n\n function prev() {\n if (isFirst.value && !props.infinite) return\n setActiveItem(activeIndex.value - 1)\n }\n\n function next() {\n if (isLast.value && !props.infinite) return\n setActiveItem(activeIndex.value + 1)\n }\n\n function handleActions(action: ImageViewerAction, options = {}) {\n if (loading.value) return\n const { minScale, maxScale } = props\n const { zoomRate, rotateDeg, enableTransition } = {\n zoomRate: props.zoomRate,\n rotateDeg: 90,\n enableTransition: true,\n ...options,\n }\n switch (action) {\n case 'zoomOut':\n if (transform.value.scale > minScale) {\n transform.value.scale = Number.parseFloat(\n (transform.value.scale / zoomRate).toFixed(3),\n )\n }\n break\n case 'zoomIn':\n if (transform.value.scale < maxScale) {\n transform.value.scale = Number.parseFloat(\n (transform.value.scale * zoomRate).toFixed(3),\n )\n }\n break\n case 'clockwise':\n transform.value.deg += rotateDeg\n emit('rotate', transform.value.deg)\n break\n case 'anticlockwise':\n transform.value.deg -= rotateDeg\n emit('rotate', transform.value.deg)\n break\n }\n transform.value.enableTransition = enableTransition\n }\n\n watch(currentImg, () => {\n nextTick(() => {\n const $img = imgRefs.value[0]\n if (!$img?.complete) {\n loading.value = true\n }\n })\n })\n\n watch(activeIndex, val => {\n reset()\n emit('switch', val)\n })\n\n onMounted(() => {\n registerEventListener()\n // add tabindex then wrapper can be focusable via Javascript\n // focus wrapper so arrow key can't cause inner scroll behavior underneath\n wrapper.value?.focus?.()\n })\n\n return {\n namespace,\n activeIndex,\n wrapper,\n imgRefs,\n isSingle,\n isFirst,\n isLast,\n currentImg,\n imgStyle,\n mode,\n zIndex,\n handleActions,\n prev,\n next,\n hide,\n toggleMode,\n setActiveItem,\n handleImgLoad,\n handleImgError,\n handleMouseDown,\n }\n },\n})\n</script>\n"],"names":["_withCtx","_withDirectives"],"mappings":"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"}