@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,7 @@
1
1
  {
2
2
  "name": "@bikariya/image-viewer",
3
3
  "configKey": "image-viewer",
4
- "version": "0.0.4",
4
+ "version": "0.0.6",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -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
- isOpening?: boolean;
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 props = defineProps({
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
- isOpening: { type: Boolean, required: false }
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
- const rate = 0.9;
12
+ useEventListener("keydown", (event) => {
13
+ if (event.key === "Escape") {
14
+ emit("close");
15
+ }
16
+ });
11
17
  const options = computed(() => {
12
18
  return {
13
- duration: props.duration ?? 400,
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: startRect.width * rate2 + "px",
75
- height: startRect.height * rate2 + "px"
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: width * rate2 + "px",
104
- height: height * rate2 + "px"
117
+ width: finalWidth + "px",
118
+ height: finalHeight + "px"
105
119
  }, options.value);
106
120
  }
107
- useEventListener("keydown", (event) => {
108
- if (event.key === "Escape") {
109
- emit("close");
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 fixedWidth = window.innerWidth * rate;
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.floor(finalHeight / 2)}px)`,
119
- left: `calc(50% - ${Math.floor(finalWidth / 2)}px)`,
120
- width: Math.floor(finalWidth) + "px",
121
- height: Math.floor(finalHeight) + "px",
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: elLeft, top: elTop } = el.getBoundingClientRect();
127
- const { scrollX: x, scrollY: y } = window;
159
+ const { left, top } = el.getBoundingClientRect();
160
+ const { scrollX, scrollY } = window;
128
161
  const animation = el.animate([{
129
- top: 2 * y + elTop + "px",
130
- left: 2 * x + elLeft + "px",
162
+ top: 2 * scrollY + top + "px",
163
+ left: 2 * scrollX + left + "px",
131
164
  clipPath: "inset(0)"
132
- }, getOriginalKeyframe(x, y)], options.value);
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 } = props.target.getBoundingClientRect();
137
- const { naturalWidth, naturalHeight } = props.target;
138
- const { objectPosition } = getComputedStyle(props.target);
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 fullHeight = naturalHeight * width / naturalWidth;
148
- clipTop = (fullHeight - height) * vertical;
149
- clipBottom = fullHeight - height - clipTop;
199
+ const rest = width / naturalRatio - height;
200
+ clipTop = rest * vertical;
201
+ clipBottom = rest - clipTop;
150
202
  } else {
151
- const fullWidth = naturalWidth * height / naturalHeight;
152
- clipLeft = (fullWidth - width) * horizontal;
153
- clipRight = fullWidth - width - clipLeft;
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="isOpening"
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
- isOpening?: boolean;
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;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bikariya/image-viewer",
3
3
  "type": "module",
4
- "version": "0.0.4",
4
+ "version": "0.0.6",
5
5
  "description": "Bikariya image viewer for Nuxt",
6
6
  "author": "KazariEX",
7
7
  "license": "MIT",