@bikariya/image-viewer 0.0.4 → 0.0.6
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 (isSizeOutOfBounds(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,45 +106,88 @@ 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 (isSizeOutOfBounds(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 { containSize, coverSize, advantageSide } = getFitSize();
|
|
124
|
+
if (width < coverSize.width && height < coverSize.height) {
|
|
125
|
+
const centerLoc = {
|
|
126
|
+
left: `calc(50% - ${Math.round(coverSize.width / 2)}px)`,
|
|
127
|
+
top: `calc(50% - ${Math.round(coverSize.height / 2)}px)`
|
|
128
|
+
};
|
|
129
|
+
const pointerLoc = {
|
|
130
|
+
left: `${left - (event.clientX - left) * (coverSize.width / width - 1)}px`,
|
|
131
|
+
top: `${top - (event.clientY - top) * (coverSize.height / height - 1)}px`
|
|
132
|
+
};
|
|
133
|
+
rootEl.value.animate({
|
|
134
|
+
left: advantageSide === "height" ? centerLoc.left : pointerLoc.left,
|
|
135
|
+
top: advantageSide === "height" ? pointerLoc.top : centerLoc.top,
|
|
136
|
+
width: Math.ceil(coverSize.width) + "px",
|
|
137
|
+
height: Math.ceil(coverSize.height) + "px"
|
|
138
|
+
}, options.value);
|
|
139
|
+
} else {
|
|
140
|
+
rootEl.value.animate({
|
|
141
|
+
left: `calc(50% - ${Math.round(containSize.width / 2)}px)`,
|
|
142
|
+
top: `calc(50% - ${Math.round(containSize.height / 2)}px)`,
|
|
143
|
+
width: Math.floor(containSize.width) + "px",
|
|
144
|
+
height: Math.floor(containSize.height) + "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 { containSize } = getFitSize();
|
|
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(containSize.height / 2)}px)`,
|
|
152
|
+
left: `calc(50% - ${Math.round(containSize.width / 2)}px)`,
|
|
153
|
+
width: Math.floor(containSize.width) + "px",
|
|
154
|
+
height: Math.floor(containSize.height) + "px",
|
|
122
155
|
clipPath: "inset(0)"
|
|
123
156
|
}], options.value);
|
|
124
157
|
};
|
|
125
158
|
const onLeave = (el, done) => {
|
|
126
|
-
const { left
|
|
127
|
-
const { scrollX
|
|
159
|
+
const { left, top } = el.getBoundingClientRect();
|
|
160
|
+
const { scrollX, scrollY } = window;
|
|
128
161
|
const animation = el.animate([{
|
|
129
|
-
top: 2 *
|
|
130
|
-
left: 2 *
|
|
162
|
+
top: 2 * scrollY + top + "px",
|
|
163
|
+
left: 2 * scrollX + left + "px",
|
|
131
164
|
clipPath: "inset(0)"
|
|
132
|
-
}, getOriginalKeyframe(
|
|
165
|
+
}, getOriginalKeyframe(scrollX, scrollY)], options.value);
|
|
133
166
|
animation.addEventListener("finish", done);
|
|
134
167
|
};
|
|
168
|
+
function getFitSize() {
|
|
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
|
+
const widthSize = {
|
|
174
|
+
width: fixedWidth,
|
|
175
|
+
height: fixedWidth / naturalRatio
|
|
176
|
+
};
|
|
177
|
+
const heightSize = {
|
|
178
|
+
width: fixedHeight * naturalRatio,
|
|
179
|
+
height: fixedHeight
|
|
180
|
+
};
|
|
181
|
+
return {
|
|
182
|
+
containSize: advantageSide === "height" ? heightSize : widthSize,
|
|
183
|
+
coverSize: advantageSide === "height" ? widthSize : heightSize,
|
|
184
|
+
advantageSide
|
|
185
|
+
};
|
|
186
|
+
}
|
|
135
187
|
function getOriginalKeyframe(x = 0, y = 0) {
|
|
136
|
-
const { left, top, width, height } =
|
|
137
|
-
const { naturalWidth, naturalHeight } =
|
|
138
|
-
const { objectPosition } = getComputedStyle(
|
|
188
|
+
const { left, top, width, height } = target.getBoundingClientRect();
|
|
189
|
+
const { naturalWidth, naturalHeight } = target;
|
|
190
|
+
const { objectPosition } = getComputedStyle(target);
|
|
139
191
|
const [horizontal, vertical] = objectPosition.split(" ").map((pos) => Number(pos.slice(0, -1)) / 100);
|
|
140
192
|
const ratio = width / height;
|
|
141
193
|
const naturalRatio = naturalWidth / naturalHeight;
|
|
@@ -144,13 +196,13 @@ function getOriginalKeyframe(x = 0, y = 0) {
|
|
|
144
196
|
let clipLeft = 0;
|
|
145
197
|
let clipRight = 0;
|
|
146
198
|
if (ratio > naturalRatio) {
|
|
147
|
-
const
|
|
148
|
-
clipTop =
|
|
149
|
-
clipBottom =
|
|
199
|
+
const rest = width / naturalRatio - height;
|
|
200
|
+
clipTop = rest * vertical;
|
|
201
|
+
clipBottom = rest - clipTop;
|
|
150
202
|
} else {
|
|
151
|
-
const
|
|
152
|
-
clipLeft =
|
|
153
|
-
clipRight =
|
|
203
|
+
const rest = height * naturalRatio - width;
|
|
204
|
+
clipLeft = rest * horizontal;
|
|
205
|
+
clipRight = rest - clipLeft;
|
|
154
206
|
}
|
|
155
207
|
return {
|
|
156
208
|
top: y + top - clipTop + "px",
|
|
@@ -160,16 +212,27 @@ function getOriginalKeyframe(x = 0, y = 0) {
|
|
|
160
212
|
clipPath: `inset(${clipTop}px ${clipRight}px ${clipBottom}px ${clipLeft}px)`
|
|
161
213
|
};
|
|
162
214
|
}
|
|
215
|
+
function isSizeOutOfBounds(width, height, rate2) {
|
|
216
|
+
if (!clamp) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
if (rate2 > 1) {
|
|
220
|
+
return width > Math.max(window.innerWidth, target.width, target.naturalWidth) && height > Math.max(window.innerHeight, target.height, target.naturalHeight);
|
|
221
|
+
} else {
|
|
222
|
+
return width < Math.min(window.innerWidth, target.width, target.naturalWidth) && height < Math.min(window.innerHeight, target.height, target.naturalHeight);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
163
225
|
</script>
|
|
164
226
|
|
|
165
227
|
<template>
|
|
166
228
|
<transition @enter="onEnter" @leave="onLeave">
|
|
167
229
|
<img
|
|
168
|
-
v-if="
|
|
230
|
+
v-if="open"
|
|
169
231
|
ref="root"
|
|
170
232
|
class="bikariya-image-viewer"
|
|
171
233
|
:src="target.src"
|
|
172
234
|
:draggable="false"
|
|
235
|
+
@dblclick="onDoubleClick"
|
|
173
236
|
@wheel.prevent="onWheel"
|
|
174
237
|
/>
|
|
175
238
|
</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;
|