1
0
mirror of https://github.com/misskey-dev/misskey.git synced 2026-07-25 08:35:02 +02:00
This commit is contained in:
syuilo
2026-07-09 20:32:20 +09:00
parent f0b3a7241d
commit cc356cf542
3 changed files with 104 additions and 34 deletions

View File

@@ -4,21 +4,26 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div ref="rootEl" :class="$style.root" :style="{ transform: `translate3d(${translation.x}px, ${translation.y}px, 0)` }">
<img
ref="imageEl"
:class="$style.image"
:src="image.src"
:width="size.width"
:height="size.height"
draggable="false"
@wheel="onWheel"
@pointerdown="onPointerdown"
@pointermove="onPointermove"
@pointerup="onPointerup"
@touchstart="onTouchstart"
@touchmove="onTouchmove"
>
<div
ref="rootEl"
:class="$style.root"
@pointerdown="onPointerdown"
@pointermove="onPointermove"
@pointerup="onPointerup"
@touchstart="onTouchstart"
@touchmove="onTouchmove"
>
<div :style="{ transform: `translate3d(${translation.x}px, ${translation.y}px, 0)` }">
<img
ref="imageEl"
:class="$style.image"
:src="image.src"
:width="size.width"
:height="size.height"
draggable="false"
@wheel="onWheel"
>
</div>
</div>
</template>
@@ -43,6 +48,10 @@ const props = withDefaults(defineProps<{
const emit = defineEmits<{
(ev: 'close'): void;
(ev: 'horizontalSwipe', offset: number): void;
(ev: 'next'): void;
(ev: 'prev'): void;
(ev: 'cancelHorizontalSwipe'): void;
}>();
const rootEl = useTemplateRef('rootEl');
@@ -193,6 +202,7 @@ let currentPointerStartOffset = { x: 0, y: 0 };
let isVerticalSwiping = false;
let isHorizontalSwiping = false;
let verticalSwipeDelta = 0;
let horizontalSwipeDelta = 0;
const pointerEventCache = new Map<number, PointerEvent>();
let pointerVec = { x: 0, y: 0 };
@@ -256,7 +266,9 @@ function onPointermove(ev: PointerEvent) {
translation.value.y += deltaY;
verticalSwipeDelta += deltaY;
} else if (isHorizontalSwiping) {
translation.value.x += deltaX;
//translation.value.x += deltaX;
horizontalSwipeDelta += deltaX;
emit('horizontalSwipe', currentPointerStartOffset.x - ev.clientX);
} else {
const isVerticalVector = Math.abs(deltaY) > Math.abs(deltaX);
if (isVerticalVector) {
@@ -285,8 +297,8 @@ function onPointerup(ev: PointerEvent) {
currentPointerId = null;
if (isVerticalSwiping) {
const shouldCloseByUpwardSwipe = verticalSwipeDelta < -200 || pointerVec.y < -3; // 上の方で離された、または上に向かって強めに弾かれた
const shouldCloseByDownwardSwipe = verticalSwipeDelta > 200 || pointerVec.y > 3; // 下の方で離された、または下に向かって強めに弾かれた
const shouldCloseByUpwardSwipe = verticalSwipeDelta < -200 || (verticalSwipeDelta < 0 && pointerVec.y < -3); // 上の方で離された、または上に向かって強めに弾かれた
const shouldCloseByDownwardSwipe = verticalSwipeDelta > 200 || (verticalSwipeDelta > 0 && pointerVec.y > 3); // 下の方で離された、または下に向かって強めに弾かれた
if (shouldCloseByUpwardSwipe || shouldCloseByDownwardSwipe) {
emit('close');
return;
@@ -298,6 +310,16 @@ function onPointerup(ev: PointerEvent) {
x: defaultTranslation.x,
y: defaultTranslation.y,
}, ANIMATION_DURATION);
} else if (isHorizontalSwiping) {
const shouldNext = horizontalSwipeDelta < -200 || (horizontalSwipeDelta < 0 && pointerVec.x < -3); // 左の方で離された、または左に向かって強めに弾かれた
const shouldPrev = horizontalSwipeDelta > 200 || (horizontalSwipeDelta > 0 && pointerVec.x > 3); // 右の方で離された、または右に向かって強めに弾かれた
if (shouldNext) {
emit('next');
} else if (shouldPrev) {
emit('prev');
} else {
emit('cancelHorizontalSwipe');
}
}
}
isVerticalSwiping = false;
@@ -366,14 +388,15 @@ onBeforeUnmount(() => {
<style lang="scss" module>
.root {
//transition: transform 0.2s ease;
position: absolute;
touch-action: none;
width: 100%;
height: 100%;
}
.image {
display: block;
-webkit-touch-callout: none;
user-select: none;
//transition: width 0.2s ease, height 0.2s ease;
}
</style>

View File

@@ -7,9 +7,16 @@ SPDX-License-Identifier: AGPL-3.0-only
<div ref="rootEl" :class="$style.root" :style="{ zIndex }">
<div :class="[$style.bg]"></div>
<div ref="mainEl" :class="$style.main">
<div :class="$style.items">
<div v-for="image in images" :key="image.src" :class="$style.item">
<XItem :image="image" @close="onItemClose"/>
<div ref="itemsEl" :class="$style.items">
<div v-for="image in images" :key="image.src" ref="itemEl" :class="$style.item">
<XItem
:image="image"
@close="onItemClose"
@horizontalSwipe="onHorizontalSwipe"
@prev="onPrev"
@next="onNext"
@cancelHorizontalSwipe="onCancelHorizontalSwipe"
/>
</div>
</div>
</div>
@@ -31,7 +38,7 @@ type Image = {
};
const props = withDefaults(defineProps<{
defaultId?: string;
defaultIndex?: number;
images: Image[];
}>(), {
});
@@ -42,8 +49,48 @@ const emit = defineEmits<{
const rootEl = useTemplateRef('rootEl');
const mainEl = useTemplateRef('mainEl');
const itemsEl = useTemplateRef('itemsEl');
const itemEl = useTemplateRef('itemEl');
const zIndex = os.claimZIndex('high');
let currentScrollLeft = 0;
let currentIndex = props.defaultIndex ?? 0;
function onHorizontalSwipe(offset: number) {
itemsEl.value.scrollLeft = currentScrollLeft + offset;
}
function onCancelHorizontalSwipe() {
itemsEl.value.scrollTo({
left: currentScrollLeft,
behavior: 'smooth',
});
}
function onNext() {
if (currentIndex < props.images.length - 1) {
currentIndex++;
const el = itemEl.value[currentIndex];
currentScrollLeft = el.offsetLeft;
itemsEl.value.scrollTo({
left: currentScrollLeft,
behavior: 'smooth',
});
}
}
function onPrev() {
if (currentIndex > 0) {
currentIndex--;
const el = itemEl.value[currentIndex];
currentScrollLeft = el.offsetLeft;
itemsEl.value.scrollTo({
left: currentScrollLeft,
behavior: 'smooth',
});
}
}
function onItemClose() {
emit('closed');
}
@@ -76,8 +123,7 @@ function onItemClose() {
display: flex;
width: 100dvw;
height: 100dvh;
overflow-x: auto;
overflow-y: clip;
overflow: hidden;
scrollbar-width: none;
}

View File

@@ -94,15 +94,16 @@ const previewable = (file: Misskey.entities.DriveFile): boolean => {
};
async function openGallery(id: string) {
const images = props.mediaList.filter(media => previewable(media)).map(media => ({
id: media.id,
src: media.url,
width: media.properties.width ?? 0,
height: media.properties.height ?? 0,
sourceElement: gallery.value?.querySelector(`.image[data-id="${media.id}"]`) as HTMLElement | undefined,
}));
const { dispose } = await os.popupAsyncWithDialog(import('@/components/MkImageGallery.vue').then(x => x.default), {
defaultId: id,
images: props.mediaList.filter(media => previewable(media)).map(media => ({
id: media.id,
src: media.url,
width: media.properties.width ?? 0,
height: media.properties.height ?? 0,
sourceElement: gallery.value?.querySelector(`.image[data-id="${media.id}"]`) as HTMLElement | undefined,
})),
defaultIndex: images.findIndex(image => image.id === id),
images: images,
}, {
closed: () => dispose(),
});