@bikariya/image-viewer
Advanced tools
+1
-1
| { | ||
| "name": "@bikariya/image-viewer", | ||
| "configKey": "image-viewer", | ||
| "version": "0.0.4", | ||
| "version": "0.0.5", | ||
| "builder": { | ||
@@ -6,0 +6,0 @@ "@nuxt/module-builder": "1.0.2", |
| type __VLS_Props = { | ||
| /** | ||
| * 目标 `<img>` 元素 | ||
| */ | ||
| target: HTMLImageElement; | ||
| /** | ||
| * 过渡动画时长 (ms) | ||
| * @default 400 | ||
| */ | ||
| duration?: number; | ||
| isOpening?: boolean; | ||
| /** | ||
| * 放大后占窗口比率 | ||
| * @default 0.9 | ||
| */ | ||
| rate?: number; | ||
| /** | ||
| * 是否限制缩放比例 | ||
| * @default false | ||
| */ | ||
| clamp?: boolean; | ||
| open?: boolean; | ||
| }; | ||
@@ -6,0 +23,0 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, { |
| <script setup> | ||
| import { computed, ref, useEventListener, usePointer, useTemplateRef } from "#imports"; | ||
| const props = defineProps({ | ||
| const { target, duration = 400, rate = 0.9, clamp } = defineProps({ | ||
| target: { type: null, required: true }, | ||
| duration: { type: Number, required: false }, | ||
| isOpening: { type: Boolean, required: false } | ||
| rate: { type: Number, required: false }, | ||
| clamp: { type: Boolean, required: false }, | ||
| open: { type: Boolean, required: false } | ||
| }); | ||
| const emit = defineEmits(["close"]); | ||
| const rootEl = useTemplateRef("root"); | ||
| const rate = 0.9; | ||
| useEventListener("keydown", (event) => { | ||
| if (event.key === "Escape") { | ||
| emit("close"); | ||
| } | ||
| }); | ||
| const options = computed(() => { | ||
| return { | ||
| duration: props.duration ?? 400, | ||
| duration, | ||
| easing: "ease", | ||
@@ -22,5 +28,3 @@ fill: "forwards" | ||
| const pointers = ref({}); | ||
| const fingers = computed(() => { | ||
| return Object.values(pointers.value).slice(0, 2); | ||
| }); | ||
| const fingers = computed(() => Object.values(pointers.value).slice(0, 2)); | ||
| const center = computed(() => getCenter("current")); | ||
@@ -72,7 +76,12 @@ const distance = computed(() => getDistance("current")); | ||
| const finalTop = top - (center.value.y - top) * (rate2 - 1); | ||
| const finalWidth = startRect.width * rate2; | ||
| const finalHeight = startRect.height * rate2; | ||
| if (isOverLimit(finalWidth, finalHeight, rate2)) { | ||
| return; | ||
| } | ||
| rootEl.value.animate({ | ||
| left: finalLeft + "px", | ||
| top: finalTop + "px", | ||
| width: startRect.width * rate2 + "px", | ||
| height: startRect.height * rate2 + "px" | ||
| width: finalWidth + "px", | ||
| height: finalHeight + "px" | ||
| }, { | ||
@@ -102,24 +111,48 @@ duration: 0, | ||
| const finalTop = top - (event.clientY - top) * (rate2 - 1); | ||
| const finalWidth = width * rate2; | ||
| const finalHeight = height * rate2; | ||
| if (isOverLimit(finalWidth, finalHeight, rate2)) { | ||
| return; | ||
| } | ||
| rootEl.value.animate({ | ||
| left: finalLeft + "px", | ||
| top: finalTop + "px", | ||
| width: width * rate2 + "px", | ||
| height: height * rate2 + "px" | ||
| width: finalWidth + "px", | ||
| height: finalHeight + "px" | ||
| }, options.value); | ||
| } | ||
| useEventListener("keydown", (event) => { | ||
| if (event.key === "Escape") { | ||
| emit("close"); | ||
| function onDoubleClick(event) { | ||
| const { left, top, width, height } = rootEl.value.getBoundingClientRect(); | ||
| const { size: [coverWidth, coverHeight], advantageSide } = getFitSize("cover"); | ||
| if (width < coverWidth && height < coverHeight) { | ||
| const [finalLeft, finalTop] = advantageSide === "height" ? [ | ||
| `calc(50% - ${Math.round(coverWidth / 2)}px)`, | ||
| `${top - (event.clientY - top) * (coverHeight / height - 1)}px` | ||
| ] : [ | ||
| `${left - (event.clientX - left) * (coverWidth / width - 1)}px`, | ||
| `calc(50% - ${Math.round(coverHeight / 2)}px)` | ||
| ]; | ||
| rootEl.value.animate({ | ||
| left: finalLeft, | ||
| top: finalTop, | ||
| width: Math.ceil(coverWidth) + "px", | ||
| height: Math.ceil(coverHeight) + "px" | ||
| }, options.value); | ||
| } else { | ||
| const { size: [containWidth, containHeight] } = getFitSize("contain"); | ||
| rootEl.value.animate({ | ||
| left: `calc(50% - ${Math.round(containWidth / 2)}px)`, | ||
| top: `calc(50% - ${Math.round(containHeight / 2)}px)`, | ||
| width: Math.floor(containWidth) + "px", | ||
| height: Math.floor(containHeight) + "px" | ||
| }, options.value); | ||
| } | ||
| }); | ||
| } | ||
| const onEnter = (el) => { | ||
| const fixedWidth = window.innerWidth * rate; | ||
| const fixedHeight = window.innerHeight * rate; | ||
| const naturalRatio = props.target.naturalWidth / props.target.naturalHeight; | ||
| const [finalWidth, finalHeight] = fixedWidth / fixedHeight > naturalRatio ? [fixedHeight * naturalRatio, fixedHeight] : [fixedWidth, fixedWidth / naturalRatio]; | ||
| const { size: [containWidth, containHeight] } = getFitSize("contain"); | ||
| el.animate([getOriginalKeyframe(), { | ||
| top: `calc(50% - ${Math.floor(finalHeight / 2)}px)`, | ||
| left: `calc(50% - ${Math.floor(finalWidth / 2)}px)`, | ||
| width: Math.floor(finalWidth) + "px", | ||
| height: Math.floor(finalHeight) + "px", | ||
| top: `calc(50% - ${Math.round(containHeight / 2)}px)`, | ||
| left: `calc(50% - ${Math.round(containWidth / 2)}px)`, | ||
| width: Math.floor(containWidth) + "px", | ||
| height: Math.floor(containHeight) + "px", | ||
| clipPath: "inset(0)" | ||
@@ -138,6 +171,16 @@ }], options.value); | ||
| }; | ||
| function getFitSize(mode) { | ||
| const fixedWidth = window.innerWidth * rate; | ||
| const fixedHeight = window.innerHeight * rate; | ||
| const naturalRatio = target.naturalWidth / target.naturalHeight; | ||
| const advantageSide = fixedWidth / fixedHeight > naturalRatio ? "height" : "width"; | ||
| return { | ||
| size: +(advantageSide === "height") ^ +(mode === "cover") ? [fixedHeight * naturalRatio, fixedHeight] : [fixedWidth, fixedWidth / naturalRatio], | ||
| advantageSide | ||
| }; | ||
| } | ||
| function getOriginalKeyframe(x = 0, y = 0) { | ||
| const { left, top, width, height } = props.target.getBoundingClientRect(); | ||
| const { naturalWidth, naturalHeight } = props.target; | ||
| const { objectPosition } = getComputedStyle(props.target); | ||
| const { left, top, width, height } = target.getBoundingClientRect(); | ||
| const { naturalWidth, naturalHeight } = target; | ||
| const { objectPosition } = getComputedStyle(target); | ||
| const [horizontal, vertical] = objectPosition.split(" ").map((pos) => Number(pos.slice(0, -1)) / 100); | ||
@@ -167,2 +210,12 @@ const ratio = width / height; | ||
| } | ||
| function isOverLimit(width, height, rate2) { | ||
| if (!clamp) { | ||
| return false; | ||
| } | ||
| if (rate2 > 1) { | ||
| return width > Math.max(window.innerWidth, target.width, target.naturalWidth) && height > Math.max(window.innerHeight, target.height, target.naturalHeight); | ||
| } else { | ||
| return width < Math.min(window.innerWidth, target.width, target.naturalWidth) && height < Math.min(window.innerHeight, target.height, target.naturalHeight); | ||
| } | ||
| } | ||
| </script> | ||
@@ -173,3 +226,3 @@ | ||
| <img | ||
| v-if="isOpening" | ||
| v-if="open" | ||
| ref="root" | ||
@@ -179,2 +232,3 @@ class="bikariya-image-viewer" | ||
| :draggable="false" | ||
| @dblclick="onDoubleClick" | ||
| @wheel.prevent="onWheel" | ||
@@ -181,0 +235,0 @@ /> |
| type __VLS_Props = { | ||
| /** | ||
| * 目标 `<img>` 元素 | ||
| */ | ||
| target: HTMLImageElement; | ||
| /** | ||
| * 过渡动画时长 (ms) | ||
| * @default 400 | ||
| */ | ||
| duration?: number; | ||
| isOpening?: boolean; | ||
| /** | ||
| * 放大后占窗口比率 | ||
| * @default 0.9 | ||
| */ | ||
| rate?: number; | ||
| /** | ||
| * 是否限制缩放比例 | ||
| * @default false | ||
| */ | ||
| clamp?: boolean; | ||
| open?: boolean; | ||
| }; | ||
@@ -6,0 +23,0 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, { |
+1
-1
| { | ||
| "name": "@bikariya/image-viewer", | ||
| "type": "module", | ||
| "version": "0.0.4", | ||
| "version": "0.0.5", | ||
| "description": "Bikariya image viewer for Nuxt", | ||
@@ -6,0 +6,0 @@ "author": "KazariEX", |
13001
26.13%87
64.15%