image-viewer2.mjs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. import { defineComponent, markRaw, ref, effectScope, shallowRef, computed, watch, nextTick, onMounted, openBlock, createBlock, Teleport, createVNode, Transition, withCtx, createElementVNode, normalizeClass, unref, normalizeStyle, withModifiers, createCommentVNode, createElementBlock, Fragment, resolveDynamicComponent, renderList, withDirectives, vShow, renderSlot } from 'vue';
  2. import { isNumber, useEventListener } from '@vueuse/core';
  3. import { throttle } from 'lodash-unified';
  4. import '../../../hooks/index.mjs';
  5. import '../../../constants/index.mjs';
  6. import '../../../utils/index.mjs';
  7. import { ElIcon } from '../../icon/index.mjs';
  8. import { FullScreen, ScaleToOriginal, Close, ArrowLeft, ArrowRight, ZoomOut, ZoomIn, RefreshLeft, RefreshRight } from '@element-plus/icons-vue';
  9. import { imageViewerProps, imageViewerEmits } from './image-viewer.mjs';
  10. import _export_sfc from '../../../_virtual/plugin-vue_export-helper.mjs';
  11. import { isFirefox } from '../../../utils/browser.mjs';
  12. import { useLocale } from '../../../hooks/use-locale/index.mjs';
  13. import { useNamespace } from '../../../hooks/use-namespace/index.mjs';
  14. import { useZIndex } from '../../../hooks/use-z-index/index.mjs';
  15. import { EVENT_CODE } from '../../../constants/aria.mjs';
  16. import { keysOf } from '../../../utils/objects.mjs';
  17. const _hoisted_1 = ["src"];
  18. const __default__ = defineComponent({
  19. name: "ElImageViewer"
  20. });
  21. const _sfc_main = /* @__PURE__ */ defineComponent({
  22. ...__default__,
  23. props: imageViewerProps,
  24. emits: imageViewerEmits,
  25. setup(__props, { expose, emit }) {
  26. const props = __props;
  27. const modes = {
  28. CONTAIN: {
  29. name: "contain",
  30. icon: markRaw(FullScreen)
  31. },
  32. ORIGINAL: {
  33. name: "original",
  34. icon: markRaw(ScaleToOriginal)
  35. }
  36. };
  37. const mousewheelEventName = isFirefox() ? "DOMMouseScroll" : "mousewheel";
  38. const { t } = useLocale();
  39. const ns = useNamespace("image-viewer");
  40. const { nextZIndex } = useZIndex();
  41. const wrapper = ref();
  42. const imgRefs = ref([]);
  43. const scopeEventListener = effectScope();
  44. const loading = ref(true);
  45. const activeIndex = ref(props.initialIndex);
  46. const mode = shallowRef(modes.CONTAIN);
  47. const transform = ref({
  48. scale: 1,
  49. deg: 0,
  50. offsetX: 0,
  51. offsetY: 0,
  52. enableTransition: false
  53. });
  54. const isSingle = computed(() => {
  55. const { urlList } = props;
  56. return urlList.length <= 1;
  57. });
  58. const isFirst = computed(() => {
  59. return activeIndex.value === 0;
  60. });
  61. const isLast = computed(() => {
  62. return activeIndex.value === props.urlList.length - 1;
  63. });
  64. const currentImg = computed(() => {
  65. return props.urlList[activeIndex.value];
  66. });
  67. const imgStyle = computed(() => {
  68. const { scale, deg, offsetX, offsetY, enableTransition } = transform.value;
  69. let translateX = offsetX / scale;
  70. let translateY = offsetY / scale;
  71. switch (deg % 360) {
  72. case 90:
  73. case -270:
  74. ;
  75. [translateX, translateY] = [translateY, -translateX];
  76. break;
  77. case 180:
  78. case -180:
  79. ;
  80. [translateX, translateY] = [-translateX, -translateY];
  81. break;
  82. case 270:
  83. case -90:
  84. ;
  85. [translateX, translateY] = [-translateY, translateX];
  86. break;
  87. }
  88. const style = {
  89. transform: `scale(${scale}) rotate(${deg}deg) translate(${translateX}px, ${translateY}px)`,
  90. transition: enableTransition ? "transform .3s" : ""
  91. };
  92. if (mode.value.name === modes.CONTAIN.name) {
  93. style.maxWidth = style.maxHeight = "100%";
  94. }
  95. return style;
  96. });
  97. const computedZIndex = computed(() => {
  98. return isNumber(props.zIndex) ? props.zIndex : nextZIndex();
  99. });
  100. function hide() {
  101. unregisterEventListener();
  102. emit("close");
  103. }
  104. function registerEventListener() {
  105. const keydownHandler = throttle((e) => {
  106. switch (e.code) {
  107. case EVENT_CODE.esc:
  108. props.closeOnPressEscape && hide();
  109. break;
  110. case EVENT_CODE.space:
  111. toggleMode();
  112. break;
  113. case EVENT_CODE.left:
  114. prev();
  115. break;
  116. case EVENT_CODE.up:
  117. handleActions("zoomIn");
  118. break;
  119. case EVENT_CODE.right:
  120. next();
  121. break;
  122. case EVENT_CODE.down:
  123. handleActions("zoomOut");
  124. break;
  125. }
  126. });
  127. const mousewheelHandler = throttle((e) => {
  128. const delta = e.wheelDelta ? e.wheelDelta : -e.detail;
  129. if (delta > 0) {
  130. handleActions("zoomIn", {
  131. zoomRate: props.zoomRate,
  132. enableTransition: false
  133. });
  134. } else {
  135. handleActions("zoomOut", {
  136. zoomRate: props.zoomRate,
  137. enableTransition: false
  138. });
  139. }
  140. });
  141. scopeEventListener.run(() => {
  142. useEventListener(document, "keydown", keydownHandler);
  143. useEventListener(document, mousewheelEventName, mousewheelHandler);
  144. });
  145. }
  146. function unregisterEventListener() {
  147. scopeEventListener.stop();
  148. }
  149. function handleImgLoad() {
  150. loading.value = false;
  151. }
  152. function handleImgError(e) {
  153. loading.value = false;
  154. e.target.alt = t("el.image.error");
  155. }
  156. function handleMouseDown(e) {
  157. if (loading.value || e.button !== 0 || !wrapper.value)
  158. return;
  159. transform.value.enableTransition = false;
  160. const { offsetX, offsetY } = transform.value;
  161. const startX = e.pageX;
  162. const startY = e.pageY;
  163. const dragHandler = throttle((ev) => {
  164. transform.value = {
  165. ...transform.value,
  166. offsetX: offsetX + ev.pageX - startX,
  167. offsetY: offsetY + ev.pageY - startY
  168. };
  169. });
  170. const removeMousemove = useEventListener(document, "mousemove", dragHandler);
  171. useEventListener(document, "mouseup", () => {
  172. removeMousemove();
  173. });
  174. e.preventDefault();
  175. }
  176. function reset() {
  177. transform.value = {
  178. scale: 1,
  179. deg: 0,
  180. offsetX: 0,
  181. offsetY: 0,
  182. enableTransition: false
  183. };
  184. }
  185. function toggleMode() {
  186. if (loading.value)
  187. return;
  188. const modeNames = keysOf(modes);
  189. const modeValues = Object.values(modes);
  190. const currentMode = mode.value.name;
  191. const index = modeValues.findIndex((i) => i.name === currentMode);
  192. const nextIndex = (index + 1) % modeNames.length;
  193. mode.value = modes[modeNames[nextIndex]];
  194. reset();
  195. }
  196. function setActiveItem(index) {
  197. const len = props.urlList.length;
  198. activeIndex.value = (index + len) % len;
  199. }
  200. function prev() {
  201. if (isFirst.value && !props.infinite)
  202. return;
  203. setActiveItem(activeIndex.value - 1);
  204. }
  205. function next() {
  206. if (isLast.value && !props.infinite)
  207. return;
  208. setActiveItem(activeIndex.value + 1);
  209. }
  210. function handleActions(action, options = {}) {
  211. if (loading.value)
  212. return;
  213. const { zoomRate, rotateDeg, enableTransition } = {
  214. zoomRate: props.zoomRate,
  215. rotateDeg: 90,
  216. enableTransition: true,
  217. ...options
  218. };
  219. switch (action) {
  220. case "zoomOut":
  221. if (transform.value.scale > 0.2) {
  222. transform.value.scale = Number.parseFloat((transform.value.scale / zoomRate).toFixed(3));
  223. }
  224. break;
  225. case "zoomIn":
  226. if (transform.value.scale < 7) {
  227. transform.value.scale = Number.parseFloat((transform.value.scale * zoomRate).toFixed(3));
  228. }
  229. break;
  230. case "clockwise":
  231. transform.value.deg += rotateDeg;
  232. break;
  233. case "anticlockwise":
  234. transform.value.deg -= rotateDeg;
  235. break;
  236. }
  237. transform.value.enableTransition = enableTransition;
  238. }
  239. watch(currentImg, () => {
  240. nextTick(() => {
  241. const $img = imgRefs.value[0];
  242. if (!($img == null ? void 0 : $img.complete)) {
  243. loading.value = true;
  244. }
  245. });
  246. });
  247. watch(activeIndex, (val) => {
  248. reset();
  249. emit("switch", val);
  250. });
  251. onMounted(() => {
  252. var _a, _b;
  253. registerEventListener();
  254. (_b = (_a = wrapper.value) == null ? void 0 : _a.focus) == null ? void 0 : _b.call(_a);
  255. });
  256. expose({
  257. setActiveItem
  258. });
  259. return (_ctx, _cache) => {
  260. return openBlock(), createBlock(Teleport, {
  261. to: "body",
  262. disabled: !_ctx.teleported
  263. }, [
  264. createVNode(Transition, {
  265. name: "viewer-fade",
  266. appear: ""
  267. }, {
  268. default: withCtx(() => [
  269. createElementVNode("div", {
  270. ref_key: "wrapper",
  271. ref: wrapper,
  272. tabindex: -1,
  273. class: normalizeClass(unref(ns).e("wrapper")),
  274. style: normalizeStyle({ zIndex: unref(computedZIndex) })
  275. }, [
  276. createElementVNode("div", {
  277. class: normalizeClass(unref(ns).e("mask")),
  278. onClick: _cache[0] || (_cache[0] = withModifiers(($event) => _ctx.hideOnClickModal && hide(), ["self"]))
  279. }, null, 2),
  280. createCommentVNode(" CLOSE "),
  281. createElementVNode("span", {
  282. class: normalizeClass([unref(ns).e("btn"), unref(ns).e("close")]),
  283. onClick: hide
  284. }, [
  285. createVNode(unref(ElIcon), null, {
  286. default: withCtx(() => [
  287. createVNode(unref(Close))
  288. ]),
  289. _: 1
  290. })
  291. ], 2),
  292. createCommentVNode(" ARROW "),
  293. !unref(isSingle) ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
  294. createElementVNode("span", {
  295. class: normalizeClass([
  296. unref(ns).e("btn"),
  297. unref(ns).e("prev"),
  298. unref(ns).is("disabled", !_ctx.infinite && unref(isFirst))
  299. ]),
  300. onClick: prev
  301. }, [
  302. createVNode(unref(ElIcon), null, {
  303. default: withCtx(() => [
  304. createVNode(unref(ArrowLeft))
  305. ]),
  306. _: 1
  307. })
  308. ], 2),
  309. createElementVNode("span", {
  310. class: normalizeClass([
  311. unref(ns).e("btn"),
  312. unref(ns).e("next"),
  313. unref(ns).is("disabled", !_ctx.infinite && unref(isLast))
  314. ]),
  315. onClick: next
  316. }, [
  317. createVNode(unref(ElIcon), null, {
  318. default: withCtx(() => [
  319. createVNode(unref(ArrowRight))
  320. ]),
  321. _: 1
  322. })
  323. ], 2)
  324. ], 64)) : createCommentVNode("v-if", true),
  325. createCommentVNode(" ACTIONS "),
  326. createElementVNode("div", {
  327. class: normalizeClass([unref(ns).e("btn"), unref(ns).e("actions")])
  328. }, [
  329. createElementVNode("div", {
  330. class: normalizeClass(unref(ns).e("actions__inner"))
  331. }, [
  332. createVNode(unref(ElIcon), {
  333. onClick: _cache[1] || (_cache[1] = ($event) => handleActions("zoomOut"))
  334. }, {
  335. default: withCtx(() => [
  336. createVNode(unref(ZoomOut))
  337. ]),
  338. _: 1
  339. }),
  340. createVNode(unref(ElIcon), {
  341. onClick: _cache[2] || (_cache[2] = ($event) => handleActions("zoomIn"))
  342. }, {
  343. default: withCtx(() => [
  344. createVNode(unref(ZoomIn))
  345. ]),
  346. _: 1
  347. }),
  348. createElementVNode("i", {
  349. class: normalizeClass(unref(ns).e("actions__divider"))
  350. }, null, 2),
  351. createVNode(unref(ElIcon), { onClick: toggleMode }, {
  352. default: withCtx(() => [
  353. (openBlock(), createBlock(resolveDynamicComponent(unref(mode).icon)))
  354. ]),
  355. _: 1
  356. }),
  357. createElementVNode("i", {
  358. class: normalizeClass(unref(ns).e("actions__divider"))
  359. }, null, 2),
  360. createVNode(unref(ElIcon), {
  361. onClick: _cache[3] || (_cache[3] = ($event) => handleActions("anticlockwise"))
  362. }, {
  363. default: withCtx(() => [
  364. createVNode(unref(RefreshLeft))
  365. ]),
  366. _: 1
  367. }),
  368. createVNode(unref(ElIcon), {
  369. onClick: _cache[4] || (_cache[4] = ($event) => handleActions("clockwise"))
  370. }, {
  371. default: withCtx(() => [
  372. createVNode(unref(RefreshRight))
  373. ]),
  374. _: 1
  375. })
  376. ], 2)
  377. ], 2),
  378. createCommentVNode(" CANVAS "),
  379. createElementVNode("div", {
  380. class: normalizeClass(unref(ns).e("canvas"))
  381. }, [
  382. (openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.urlList, (url, i) => {
  383. return withDirectives((openBlock(), createElementBlock("img", {
  384. ref_for: true,
  385. ref: (el) => imgRefs.value[i] = el,
  386. key: url,
  387. src: url,
  388. style: normalizeStyle(unref(imgStyle)),
  389. class: normalizeClass(unref(ns).e("img")),
  390. onLoad: handleImgLoad,
  391. onError: handleImgError,
  392. onMousedown: handleMouseDown
  393. }, null, 46, _hoisted_1)), [
  394. [vShow, i === activeIndex.value]
  395. ]);
  396. }), 128))
  397. ], 2),
  398. renderSlot(_ctx.$slots, "default")
  399. ], 6)
  400. ]),
  401. _: 3
  402. })
  403. ], 8, ["disabled"]);
  404. };
  405. }
  406. });
  407. var ImageViewer = /* @__PURE__ */ _export_sfc(_sfc_main, [["__file", "/home/runner/work/element-plus/element-plus/packages/components/image-viewer/src/image-viewer.vue"]]);
  408. export { ImageViewer as default };
  409. //# sourceMappingURL=image-viewer2.mjs.map