mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-07-25 08:35:02 +02:00
wip
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user