![]()
@@ -85,7 +89,7 @@ const padding = 30;
const ANIMATION_DURATION = 200;
// maxからはみ出す場合は縮小、maxに満たない場合は拡大する(contain)
-function calcNeutralSize(image: Image) {
+function calcImageRenderingSize(image: Image) {
const maxWidth = window.innerWidth - padding * 2;
const maxHeight = window.innerHeight - padding * 2;
@@ -99,74 +103,62 @@ function calcNeutralSize(image: Image) {
return { width, height };
}
-function calcNeutralTranslation(image: Image) {
- return { x: 0, y: 0 };
+const imageRenderingSize = calcImageRenderingSize(props.image);
+const transform = ref({ x: 0, y: 0, scale: 1 });
+
+// 元のimg要素の位置・サイズ(とobject-fitの設定値)を取得して、そこからneutralの位置にアニメーションするためのscaleとtranslationを計算する
+function getScaleAndTranslationForSourceElement(): { x: number; y: number; scale: number } {
+ const sourceElement = props.image.sourceElement;
+ if (sourceElement == null) return { x: 0, y: 0, scale: 1 };
+
+ return calculateSourceTransform({
+ fit: window.getComputedStyle(sourceElement).objectFit,
+ imageRenderingSize,
+ sourceRect: sourceElement.getBoundingClientRect(),
+ viewportSize: {
+ width: window.innerWidth,
+ height: window.innerHeight,
+ },
+ });
}
-const neutralSize = calcNeutralSize(props.image);
-const neutralTranslation = calcNeutralTranslation(props.image);
-
-const size = { width: neutralSize.width, height: neutralSize.height };
-const translation = ref({ x: neutralTranslation.x, y: neutralTranslation.y });
-const transformScale = ref(1);
-
if (props.image.sourceElement != null && props.activated) {
const sourceTransform = getScaleAndTranslationForSourceElement();
- translation.value.x = sourceTransform.x;
- translation.value.y = sourceTransform.y;
- transformScale.value = sourceTransform.scale;
+ transform.value.scale = sourceTransform.scale;
+ transform.value.x = sourceTransform.x;
+ transform.value.y = sourceTransform.y;
}
const isZooming = ref(false);
function zoomInTo(x: number, y: number, factor = 1.1, withAnimation = false) {
- const newScale = transformScale.value * factor;
+ const newScale = transform.value.scale * factor;
isZooming.value = true;
const rect = rootEl.value.getBoundingClientRect();
const offsetX = x - rect.left;
const offsetY = y - rect.top;
- const newTranslationX = offsetX - (offsetX - translation.value.x) * factor;
- const newTranslationY = offsetY - (offsetY - translation.value.y) * factor;
+ const newTranslationX = offsetX - (offsetX - transform.value.x) * factor;
+ const newTranslationY = offsetY - (offsetY - transform.value.y) * factor;
if (withAnimation) {
- beginAnimation({
- from: {
- x: translation.value.x,
- y: translation.value.y,
- scale: transformScale.value,
- },
- to: {
- x: newTranslationX,
- y: newTranslationY,
- scale: newScale,
- },
- duration: ANIMATION_DURATION,
- easing: easing_easeInOutQuad,
- apply: (state) => {
- translation.value.x = state.x;
- translation.value.y = state.y;
- transformScale.value = state.scale;
- },
- });
- } else {
- translation.value.x = newTranslationX;
- translation.value.y = newTranslationY;
- transformScale.value = newScale;
+ enableTransition.value = true;
}
+
+ transform.value.x = newTranslationX;
+ transform.value.y = newTranslationY;
+ transform.value.scale = newScale;
}
function resetToNeutral() {
isZooming.value = false;
enableTransition.value = true;
-
rootEl.value.offsetHeight; // reflow
-
- translation.value.x = neutralTranslation.x;
- translation.value.y = neutralTranslation.y;
- transformScale.value = 1;
+ transform.value.scale = 1;
+ transform.value.x = 0;
+ transform.value.y = 0;
}
function onWheel(event: WheelEvent) {
@@ -177,12 +169,12 @@ function onWheel(event: WheelEvent) {
const scaleFactor = 1.1;
const scale = delta > 0 ? 1 / scaleFactor : scaleFactor;
- const newScale = transformScale.value * scale;
+ const newScale = transform.value.scale * scale;
if (newScale < 1) {
- translation.value.x = neutralTranslation.x;
- translation.value.y = neutralTranslation.y;
- transformScale.value = 1;
+ transform.value.scale = 1;
+ transform.value.x = 0;
+ transform.value.y = 0;
isZooming.value = false;
return;
}
@@ -195,7 +187,7 @@ function onZoomGesture(ev: { delta: number; centerX: number; centerY: number })
}
function onZoomGestureEnd() {
- if (transformScale.value < 1) {
+ if (transform.value.scale < 1) {
isZooming.value = false;
resetToNeutral();
}
@@ -269,14 +261,13 @@ function onPointermove(ev: PointerEvent) {
const deltaY = ev.clientY - lastY;
if (isZooming.value) {
- translation.value.x += deltaX;
- translation.value.y += deltaY;
+ transform.value.x += deltaX;
+ transform.value.y += deltaY;
} else {
if (isVerticalSwiping) {
- translation.value.y += deltaY;
+ transform.value.y += deltaY;
verticalSwipeDelta += deltaY;
} else if (isHorizontalSwiping) {
- //translation.value.x += deltaX;
horizontalSwipeDelta = ev.clientX - currentPointerStartOffset.x;
emit('horizontalSwipe', horizontalSwipeDelta);
} else {
@@ -317,12 +308,10 @@ function onPointerup(ev: PointerEvent) {
const sourceTransform = getScaleAndTranslationForSourceElement();
enableTransition.value = true;
-
rootEl.value.offsetHeight; // reflow
-
- translation.value.x = sourceTransform.x;
- translation.value.y = sourceTransform.y;
- transformScale.value = sourceTransform.scale;
+ transform.value.x = sourceTransform.x;
+ transform.value.y = sourceTransform.y;
+ transform.value.scale = sourceTransform.scale;
return;
}
@@ -383,8 +372,8 @@ function updateInertia(timeStamp: number) {
if (isDragging) return;
if (!isZooming.value) return;
if (Math.abs(pointerVec.x) < 0.01 && Math.abs(pointerVec.y) < 0.01) return;
- translation.value.x += pointerVec.x * timeDelta;
- translation.value.y += pointerVec.y * timeDelta;
+ transform.value.x += pointerVec.x * timeDelta;
+ transform.value.y += pointerVec.y * timeDelta;
pointerVec.x *= inertiaFactor ** (timeDelta / 16.67);
pointerVec.y *= inertiaFactor ** (timeDelta / 16.67);
}
@@ -398,31 +387,13 @@ onBeforeUnmount(() => {
});
//#endregion
-// 元のimg要素の位置・サイズ(とobject-fitの設定値)を取得して、そこからneutralの位置にアニメーションするためのscaleとtranslationを計算する
-function getScaleAndTranslationForSourceElement(): { x: number; y: number; scale: number } {
- const sourceElement = props.image.sourceElement;
- if (sourceElement == null) return { x: 0, y: 0, scale: 1 };
-
- return calculateSourceTransform({
- fit: window.getComputedStyle(sourceElement).objectFit,
- neutralSize,
- sourceRect: sourceElement.getBoundingClientRect(),
- viewportSize: {
- width: window.innerWidth,
- height: window.innerHeight,
- },
- });
-}
-
onMounted(async () => {
if (props.image.sourceElement != null && props.activated) {
enableTransition.value = true;
-
rootEl.value.offsetHeight; // reflow
-
- translation.value.x = neutralTranslation.x;
- translation.value.y = neutralTranslation.y;
- transformScale.value = 1;
+ transform.value.x = 0;
+ transform.value.y = 0;
+ transform.value.scale = 1;
}
});
@@ -459,6 +430,12 @@ onMounted(async () => {
pointer-events: none;
}
+.transformer {
+ width: 100%;
+ height: 100%;
+ transform-origin: left top;
+}
+
.transition {
transition: translate 200ms ease, scale 200ms ease;
}
diff --git a/packages/frontend/src/components/MkImageGallery.utils.ts b/packages/frontend/src/components/MkImageGallery.utils.ts
index 001fbc6085..0847e79ae5 100644
--- a/packages/frontend/src/components/MkImageGallery.utils.ts
+++ b/packages/frontend/src/components/MkImageGallery.utils.ts
@@ -15,23 +15,23 @@ type Rect = Size & {
export function calculateSourceTransform({
fit,
- neutralSize,
+ imageRenderingSize,
sourceRect,
viewportSize,
}: {
fit: string;
- neutralSize: Size;
+ imageRenderingSize: Size;
sourceRect: Rect;
viewportSize: Size;
}): { x: number; y: number; scale: number } {
const scale = fit === 'cover'
- ? Math.max(sourceRect.width / neutralSize.width, sourceRect.height / neutralSize.height)
- : Math.min(sourceRect.width / neutralSize.width, sourceRect.height / neutralSize.height);
+ ? Math.max(sourceRect.width / imageRenderingSize.width, sourceRect.height / imageRenderingSize.height)
+ : Math.min(sourceRect.width / imageRenderingSize.width, sourceRect.height / imageRenderingSize.height);
- const neutralLeft = (viewportSize.width - neutralSize.width) / 2;
- const neutralTop = (viewportSize.height - neutralSize.height) / 2;
- const sourceImageWidth = neutralSize.width * scale;
- const sourceImageHeight = neutralSize.height * scale;
+ const neutralLeft = (viewportSize.width - imageRenderingSize.width) / 2;
+ const neutralTop = (viewportSize.height - imageRenderingSize.height) / 2;
+ const sourceImageWidth = imageRenderingSize.width * scale;
+ const sourceImageHeight = imageRenderingSize.height * scale;
const sourceImageLeft = sourceRect.left + (sourceRect.width - sourceImageWidth) / 2;
const sourceImageTop = sourceRect.top + (sourceRect.height - sourceImageHeight) / 2;