@bikariya/image-viewer 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/module.json
CHANGED
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
type __VLS_Props = {
|
|
2
|
+
/**
|
|
3
|
+
* 目标 `<img>` 元素
|
|
4
|
+
*/
|
|
2
5
|
target: HTMLImageElement;
|
|
6
|
+
/**
|
|
7
|
+
* 过渡动画时长 (ms)
|
|
8
|
+
* @default 400
|
|
9
|
+
*/
|
|
3
10
|
duration?: number;
|
|
4
|
-
|
|
11
|
+
/**
|
|
12
|
+
* 放大后占窗口比率
|
|
13
|
+
* @default 0.9
|
|
14
|
+
*/
|
|
15
|
+
rate?: number;
|
|
16
|
+
/**
|
|
17
|
+
* 是否限制缩放比例
|
|
18
|
+
* @default false
|
|
19
|
+
*/
|
|
20
|
+
clamp?: boolean;
|
|
21
|
+
open?: boolean;
|
|
5
22
|
};
|
|
6
23
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
7
24
|
close: () => any;
|
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed, ref, useEventListener, usePointer, useTemplateRef } from "#imports";
|
|
3
|
-
const
|
|
3
|
+
const { target, duration = 400, rate = 0.9, clamp } = defineProps({
|
|
4
4
|
target: { type: null, required: true },
|
|
5
5
|
duration: { type: Number, required: false },
|
|
6
|
-
|
|
6
|
+
rate: { type: Number, required: false },
|
|
7
|
+
clamp: { type: Boolean, required: false },
|
|
8
|
+
open: { type: Boolean, required: false }
|
|
7
9
|
});
|
|
8
10
|
const emit = defineEmits(["close"]);
|
|
9
11
|
const rootEl = useTemplateRef("root");
|
|
10
|
-
|
|
12
|
+
useEventListener("keydown", (event) => {
|
|
13
|
+
if (event.key === "Escape") {
|
|
14
|
+
emit("close");
|
|
15
|
+
}
|
|
16
|
+
});
|
|
11
17
|
const options = computed(() => {
|
|
12
18
|
return {
|
|
13
|
-
duration
|
|
19
|
+
duration,
|
|
14
20
|
easing: "ease",
|
|
15
21
|
fill: "forwards"
|
|
16
22
|
};
|
|
@@ -19,9 +25,7 @@ let startRect;
|
|
|
19
25
|
let startCenter;
|
|
20
26
|
let startDistance;
|
|
21
27
|
const pointers = ref({});
|
|
22
|
-
const fingers = computed(() =>
|
|
23
|
-
return Object.values(pointers.value).slice(0, 2);
|
|
24
|
-
});
|
|
28
|
+
const fingers = computed(() => Object.values(pointers.value).slice(0, 2));
|
|
25
29
|
const center = computed(() => getCenter("current"));
|
|
26
30
|
const distance = computed(() => getDistance("current"));
|
|
27
31
|
function getCenter(mode) {
|
|
@@ -68,11 +72,16 @@ const { isHolding } = usePointer(rootEl, {
|
|
|
68
72
|
const top = startRect.top + center.value.y - startCenter.y;
|
|
69
73
|
const finalLeft = left - (center.value.x - left) * (rate2 - 1);
|
|
70
74
|
const finalTop = top - (center.value.y - top) * (rate2 - 1);
|
|
75
|
+
const finalWidth = startRect.width * rate2;
|
|
76
|
+
const finalHeight = startRect.height * rate2;
|
|
77
|
+
if (isOverLimit(finalWidth, finalHeight, rate2)) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
71
80
|
rootEl.value.animate({
|
|
72
81
|
left: finalLeft + "px",
|
|
73
82
|
top: finalTop + "px",
|
|
74
|
-
width:
|
|
75
|
-
height:
|
|
83
|
+
width: finalWidth + "px",
|
|
84
|
+
height: finalHeight + "px"
|
|
76
85
|
}, {
|
|
77
86
|
duration: 0,
|
|
78
87
|
fill: "forwards"
|
|
@@ -97,28 +106,52 @@ function onWheel(event) {
|
|
|
97
106
|
const { left, top, width, height } = rootEl.value.getBoundingClientRect();
|
|
98
107
|
const finalLeft = left - (event.clientX - left) * (rate2 - 1);
|
|
99
108
|
const finalTop = top - (event.clientY - top) * (rate2 - 1);
|
|
109
|
+
const finalWidth = width * rate2;
|
|
110
|
+
const finalHeight = height * rate2;
|
|
111
|
+
if (isOverLimit(finalWidth, finalHeight, rate2)) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
100
114
|
rootEl.value.animate({
|
|
101
115
|
left: finalLeft + "px",
|
|
102
116
|
top: finalTop + "px",
|
|
103
|
-
width:
|
|
104
|
-
height:
|
|
117
|
+
width: finalWidth + "px",
|
|
118
|
+
height: finalHeight + "px"
|
|
105
119
|
}, options.value);
|
|
106
120
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
121
|
+
function onDoubleClick(event) {
|
|
122
|
+
const { left, top, width, height } = rootEl.value.getBoundingClientRect();
|
|
123
|
+
const { size: [coverWidth, coverHeight], advantageSide } = getFitSize("cover");
|
|
124
|
+
if (width < coverWidth && height < coverHeight) {
|
|
125
|
+
const [finalLeft, finalTop] = advantageSide === "height" ? [
|
|
126
|
+
`calc(50% - ${Math.round(coverWidth / 2)}px)`,
|
|
127
|
+
`${top - (event.clientY - top) * (coverHeight / height - 1)}px`
|
|
128
|
+
] : [
|
|
129
|
+
`${left - (event.clientX - left) * (coverWidth / width - 1)}px`,
|
|
130
|
+
`calc(50% - ${Math.round(coverHeight / 2)}px)`
|
|
131
|
+
];
|
|
132
|
+
rootEl.value.animate({
|
|
133
|
+
left: finalLeft,
|
|
134
|
+
top: finalTop,
|
|
135
|
+
width: Math.ceil(coverWidth) + "px",
|
|
136
|
+
height: Math.ceil(coverHeight) + "px"
|
|
137
|
+
}, options.value);
|
|
138
|
+
} else {
|
|
139
|
+
const { size: [containWidth, containHeight] } = getFitSize("contain");
|
|
140
|
+
rootEl.value.animate({
|
|
141
|
+
left: `calc(50% - ${Math.round(containWidth / 2)}px)`,
|
|
142
|
+
top: `calc(50% - ${Math.round(containHeight / 2)}px)`,
|
|
143
|
+
width: Math.floor(containWidth) + "px",
|
|
144
|
+
height: Math.floor(containHeight) + "px"
|
|
145
|
+
}, options.value);
|
|
110
146
|
}
|
|
111
|
-
}
|
|
147
|
+
}
|
|
112
148
|
const onEnter = (el) => {
|
|
113
|
-
const
|
|
114
|
-
const fixedHeight = window.innerHeight * rate;
|
|
115
|
-
const naturalRatio = props.target.naturalWidth / props.target.naturalHeight;
|
|
116
|
-
const [finalWidth, finalHeight] = fixedWidth / fixedHeight > naturalRatio ? [fixedHeight * naturalRatio, fixedHeight] : [fixedWidth, fixedWidth / naturalRatio];
|
|
149
|
+
const { size: [containWidth, containHeight] } = getFitSize("contain");
|
|
117
150
|
el.animate([getOriginalKeyframe(), {
|
|
118
|
-
top: `calc(50% - ${Math.
|
|
119
|
-
left: `calc(50% - ${Math.
|
|
120
|
-
width: Math.floor(
|
|
121
|
-
height: Math.floor(
|
|
151
|
+
top: `calc(50% - ${Math.round(containHeight / 2)}px)`,
|
|
152
|
+
left: `calc(50% - ${Math.round(containWidth / 2)}px)`,
|
|
153
|
+
width: Math.floor(containWidth) + "px",
|
|
154
|
+
height: Math.floor(containHeight) + "px",
|
|
122
155
|
clipPath: "inset(0)"
|
|
123
156
|
}], options.value);
|
|
124
157
|
};
|
|
@@ -132,10 +165,20 @@ const onLeave = (el, done) => {
|
|
|
132
165
|
}, getOriginalKeyframe(x, y)], options.value);
|
|
133
166
|
animation.addEventListener("finish", done);
|
|
134
167
|
};
|
|
168
|
+
function getFitSize(mode) {
|
|
169
|
+
const fixedWidth = window.innerWidth * rate;
|
|
170
|
+
const fixedHeight = window.innerHeight * rate;
|
|
171
|
+
const naturalRatio = target.naturalWidth / target.naturalHeight;
|
|
172
|
+
const advantageSide = fixedWidth / fixedHeight > naturalRatio ? "height" : "width";
|
|
173
|
+
return {
|
|
174
|
+
size: +(advantageSide === "height") ^ +(mode === "cover") ? [fixedHeight * naturalRatio, fixedHeight] : [fixedWidth, fixedWidth / naturalRatio],
|
|
175
|
+
advantageSide
|
|
176
|
+
};
|
|
177
|
+
}
|
|
135
178
|
function getOriginalKeyframe(x = 0, y = 0) {
|
|
136
|
-
const { left, top, width, height } =
|
|
137
|
-
const { naturalWidth, naturalHeight } =
|
|
138
|
-
const { objectPosition } = getComputedStyle(
|
|
179
|
+
const { left, top, width, height } = target.getBoundingClientRect();
|
|
180
|
+
const { naturalWidth, naturalHeight } = target;
|
|
181
|
+
const { objectPosition } = getComputedStyle(target);
|
|
139
182
|
const [horizontal, vertical] = objectPosition.split(" ").map((pos) => Number(pos.slice(0, -1)) / 100);
|
|
140
183
|
const ratio = width / height;
|
|
141
184
|
const naturalRatio = naturalWidth / naturalHeight;
|
|
@@ -160,16 +203,27 @@ function getOriginalKeyframe(x = 0, y = 0) {
|
|
|
160
203
|
clipPath: `inset(${clipTop}px ${clipRight}px ${clipBottom}px ${clipLeft}px)`
|
|
161
204
|
};
|
|
162
205
|
}
|
|
206
|
+
function isOverLimit(width, height, rate2) {
|
|
207
|
+
if (!clamp) {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
if (rate2 > 1) {
|
|
211
|
+
return width > Math.max(window.innerWidth, target.width, target.naturalWidth) && height > Math.max(window.innerHeight, target.height, target.naturalHeight);
|
|
212
|
+
} else {
|
|
213
|
+
return width < Math.min(window.innerWidth, target.width, target.naturalWidth) && height < Math.min(window.innerHeight, target.height, target.naturalHeight);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
163
216
|
</script>
|
|
164
217
|
|
|
165
218
|
<template>
|
|
166
219
|
<transition @enter="onEnter" @leave="onLeave">
|
|
167
220
|
<img
|
|
168
|
-
v-if="
|
|
221
|
+
v-if="open"
|
|
169
222
|
ref="root"
|
|
170
223
|
class="bikariya-image-viewer"
|
|
171
224
|
:src="target.src"
|
|
172
225
|
:draggable="false"
|
|
226
|
+
@dblclick="onDoubleClick"
|
|
173
227
|
@wheel.prevent="onWheel"
|
|
174
228
|
/>
|
|
175
229
|
</transition>
|
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
type __VLS_Props = {
|
|
2
|
+
/**
|
|
3
|
+
* 目标 `<img>` 元素
|
|
4
|
+
*/
|
|
2
5
|
target: HTMLImageElement;
|
|
6
|
+
/**
|
|
7
|
+
* 过渡动画时长 (ms)
|
|
8
|
+
* @default 400
|
|
9
|
+
*/
|
|
3
10
|
duration?: number;
|
|
4
|
-
|
|
11
|
+
/**
|
|
12
|
+
* 放大后占窗口比率
|
|
13
|
+
* @default 0.9
|
|
14
|
+
*/
|
|
15
|
+
rate?: number;
|
|
16
|
+
/**
|
|
17
|
+
* 是否限制缩放比例
|
|
18
|
+
* @default false
|
|
19
|
+
*/
|
|
20
|
+
clamp?: boolean;
|
|
21
|
+
open?: boolean;
|
|
5
22
|
};
|
|
6
23
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
7
24
|
close: () => any;
|