Skip to content

Commit

Permalink
fix: контекстное меню на IOS mobile
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanmem committed Nov 24, 2023
1 parent df077a6 commit 2a3778c
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 9 deletions.
76 changes: 74 additions & 2 deletions src/composables/useSwipes.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,64 @@
import { useApp } from "@/store/app/app";

const longTouchDuration = 400; // Минимальная продолжительность сенсорного нажатия для эмуляции contextmenu
const touchMoveThreshold = 10; // Минимальное смещение для считывания как смещение касания

export interface UsableSwipesOptions {
onLeft?: (e: TouchEvent) => any;
onRight?: (e: TouchEvent) => any;
onUp?: (e: TouchEvent) => any;
onDown?: (e: TouchEvent) => any;
/** @description contextmenu работающий на IOS mobile */
onContextMenu?: (e: MouseEvent | TouchEvent) => any;
minDiffTrigger?: number;
/** @description Если палец будет двигаться в любую из сторон на указанный процент, то свайп будет проигнорирован.
* Максимальное значение - 50%, тогда срабатывать будет в любом случае.
* При 0% срабатывать будет только под полным прямым углом.*/
precisionDirection?: number;
}

/** @description Функция позволяет установить обработчики на свайпы в определённые стороны */
/** @description Функция позволяет установить обработчики на свайпы в определённые стороны и contextmenu (модернизированный для работы с IOS mobile) */
export function useSwipes(opts: UsableSwipesOptions) {
let xDown: number | null = null;
let yDown: number | null = null;

// contextmenu variables
let touchStartTime = 0;
let touchStartX: number | undefined;
let touchStartY: number | undefined;
let touchMoved = false;
let checkLongTouchTimeout: NodeJS.Timeout | undefined;

const appStore = useApp();

function touchstart(evt: TouchEvent) {
const firstTouch = evt.touches[0];
xDown = firstTouch.clientX;
yDown = firstTouch.clientY;
if (opts.onContextMenu && appStore.isIos) {
touchMoved = false;
touchStartX = firstTouch.clientX;
touchStartY = firstTouch.clientY;
touchStartTime = new Date().getTime();
checkLongTouchTimeout = setTimeout(
() => checkLongTouch(evt),
longTouchDuration,
);
}
}

function touchmove(evt: TouchEvent) {
if (touchStartX !== undefined && touchStartY !== undefined) {
const touchCurrentX = evt.touches[0].clientX;
const touchCurrentY = evt.touches[0].clientY;
const distanceX = Math.abs(touchCurrentX - touchStartX);
const distanceY = Math.abs(touchCurrentY - touchStartY);

if (distanceX > touchMoveThreshold || distanceY > touchMoveThreshold) {
touchMoved = true;
}
}

if (!xDown || !yDown || evt.touches.length > 1) {
return;
}
Expand Down Expand Up @@ -69,8 +103,46 @@ export function useSwipes(opts: UsableSwipesOptions) {
}
}

return {
function checkLongTouch(evt: TouchEvent) {
if (
touchStartX !== undefined &&
touchStartY !== undefined &&
!touchMoved &&
new Date().getTime() - touchStartTime >= longTouchDuration
) {
contextmenu(evt);
}
}

function contextmenu(e: MouseEvent | TouchEvent) {
if (!opts.onContextMenu) {
return;
}

e.preventDefault();
e.stopPropagation();
opts.onContextMenu?.(e);
}

const events: Record<any, any> = {
touchstart,
touchmove,
};
if (opts.onContextMenu) {
if (appStore.isIos) {
function touchendOrTouchCancel() {
touchMoved = false;
touchStartX = undefined;
touchStartY = undefined;
clearTimeout(checkLongTouchTimeout);
}

events.touchend = touchendOrTouchCancel;
events.touchcancel = touchendOrTouchCancel;
} else {
events.contextmenu = contextmenu;
}
}

return events;
}
6 changes: 3 additions & 3 deletions src/helpers/showContextMenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { darkColorScheme } from "@/common/consts";
import { nextTick } from "vue";

export function showContextMenu(
e: MouseEvent,
e: MouseEvent | TouchEvent,
items: MenuItem[] | undefined,
onClose = () => {},
) {
const contextMenuInstance = Vue3ContextMenu.showContextMenu({
x: e.x,
y: e.y,
x: e instanceof MouseEvent ? e.x : e.touches[0].clientX,
y: e instanceof MouseEvent ? e.y : e.touches[0].clientY,
theme: darkColorScheme.value ? "mac dark" : "mac",
items,
closeWhenScroll: true,
Expand Down
4 changes: 2 additions & 2 deletions src/pages/AAlbum/APhoto.vue
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ watch(
{ immediate: true },
);
const onShowContextMenu = (e: MouseEvent) => {
const onShowContextMenu = (e: MouseEvent | TouchEvent) => {
const items: MenuItem[] = [];
items.push({
Expand Down Expand Up @@ -201,6 +201,7 @@ const swipes = useSwipes({
onRight: () => emit("photo:next"),
onDown: () => emit("photo:exit"),
onUp: () => (showMoreInfo.value = true),
onContextMenu: onShowContextMenu,
});
const { toClipboard } = useClipboard({ appendToBody: true });
Expand All @@ -217,7 +218,6 @@ const dateTime = computed(() => {
tabindex="1"
@click="onClick"
v-on="swipes"
@contextmenu.prevent.stop="onShowContextMenu"
@keydown.stop.prevent.esc="emit('photo:exit')"
@keydown.stop.prevent.space="emit('photo:exit')"
@keydown.stop.prevent.left="emit('photo:prev')"
Expand Down
2 changes: 1 addition & 1 deletion src/pages/AAlbum/APhotoShareDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const { toClipboard } = useClipboard({ appendToBody: true });
Скопировать прямую ссылку
</VBtn>
<VBtn :prepend-icon="icons.Icon24Share" @click="onShareWall">
Поделиться прямой ссылкой на стене
Прямую ссылку на стену
</VBtn>
</VCardItem>
<VCardActions>
Expand Down
7 changes: 6 additions & 1 deletion src/store/app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,14 @@ export const useApp = defineStore("app", {
isVkCom(): boolean {
return this.platform === "vkcom";
},
isIos(): boolean {
return (
this.platform === "ios"
);
},
isAppIos(): boolean {
return (
this.platform === "ios" &&
this.isIos &&
navigator.userAgent.startsWith("com.vk.vkclient")
);
},
Expand Down

0 comments on commit 2a3778c

Please sign in to comment.