diff --git a/package-lock.json b/package-lock.json
index 8596527..cc36e84 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -23,7 +23,9 @@
"multer": "^1.4.5-lts.2",
"socket.io-client": "^4.8.1",
"vue": "^3.5.13",
- "vue-router": "^4.5.1"
+ "vue-hammer": "^0.2.0",
+ "vue-router": "^4.5.1",
+ "vue-swipe": "^2.4.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.3",
@@ -4148,6 +4150,15 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/hammerjs": {
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz",
+ "integrity": "sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
"node_modules/has-bigints": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
@@ -6864,6 +6875,15 @@
}
}
},
+ "node_modules/vue-hammer": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/vue-hammer/-/vue-hammer-0.2.0.tgz",
+ "integrity": "sha512-u/J/TniXJAI/0cL2t3y+ZsEflw7sBl6cWhdlO0qToCDvKN31gaxxBMzZokhJ/SFzI5p2EGO8g0bmXWkbG10rVw==",
+ "license": "MIT",
+ "dependencies": {
+ "hammerjs": "^2.0.4"
+ }
+ },
"node_modules/vue-router": {
"version": "4.5.1",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.1.tgz",
@@ -6879,6 +6899,15 @@
"vue": "^3.2.0"
}
},
+ "node_modules/vue-swipe": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/vue-swipe/-/vue-swipe-2.4.0.tgz",
+ "integrity": "sha512-0N5gOED0XF/KsyeF7mT+52J2hGdnHx47xiBbVlZjCxzOyLHWX03W2/G4VhC7bvlMZO7kMt9I/rMPwKZhmk1XTQ==",
+ "license": "MIT",
+ "dependencies": {
+ "wind-dom": "0.0.3"
+ }
+ },
"node_modules/webidl-conversions": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
@@ -6987,6 +7016,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/wind-dom": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/wind-dom/-/wind-dom-0.0.3.tgz",
+ "integrity": "sha512-TlL8trn5lguxKs2O9smpnWC8Ti8rvWP1p7A+k+0DKP9mPRwFzYuijebE1I5BbbklAoBlBS1nwT8ng2DZI51tQg==",
+ "license": "MIT"
+ },
"node_modules/workbox-background-sync": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.3.0.tgz",
diff --git a/package.json b/package.json
index 24b0afb..9bef6c5 100644
--- a/package.json
+++ b/package.json
@@ -24,7 +24,9 @@
"multer": "^1.4.5-lts.2",
"socket.io-client": "^4.8.1",
"vue": "^3.5.13",
- "vue-router": "^4.5.1"
+ "vue-hammer": "^0.2.0",
+ "vue-router": "^4.5.1",
+ "vue-swipe": "^2.4.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.3",
diff --git a/src/main.js b/src/main.js
index cf51445..215af48 100644
--- a/src/main.js
+++ b/src/main.js
@@ -2,6 +2,8 @@ import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import { useAuth } from './auth'; // 1. Импортируем useAuth
+import { VueHammer } from 'vue3-hammer';
+import VueSwipe from 'vue-swipe';
// Импорт стилей Bootstrap
import 'bootstrap/dist/css/bootstrap.min.css';
@@ -9,6 +11,11 @@ import 'bootstrap/dist/css/bootstrap.min.css';
// Создаем приложение
const app = createApp(App);
+// Регистрируем библиотеки для улучшенного свайпа
+app.use(VueHammer);
+app.component('swipe', VueSwipe.Swipe);
+app.component('swipe-item', VueSwipe.SwipeItem);
+
// Используем роутер
app.use(router);
diff --git a/src/views/SwipeView.vue b/src/views/SwipeView.vue
index f261837..ca8ac82 100644
--- a/src/views/SwipeView.vue
+++ b/src/views/SwipeView.vue
@@ -42,8 +42,12 @@
-
-
+
@@ -160,8 +160,8 @@
{{ user.bio || 'Нет описания.' }}
-
-
+
+
@@ -240,17 +240,10 @@ const currentPhotoIndex = reactive({});
// Для свайпа
const swipeDirection = ref(null);
-const touchStartX = ref(0);
-const touchStartY = ref(0);
-const touchEndX = ref(0);
-const touchEndY = ref(0);
-const swipeThreshold = 100; // Минимальное расстояние для срабатывания свайпа
-const isDragging = ref(false);
-const dragStartX = ref(0);
-const dragStartY = ref(0);
-const dragOffset = ref({ x: 0, y: 0 });
+const swipeThreshold = 80; // Минимальное расстояние для срабатывания свайпа
const cardStyle = ref({});
const swipeAngle = 12; // Угол наклона карты при свайпе (в градусах)
+const isPanning = ref(false);
// Вычисляемые свойства
const currentUserToSwipe = computed(() => {
@@ -291,12 +284,12 @@ const initPhotoIndices = () => {
};
const changePhoto = (userId, index) => {
- if (isDragging.value) return; // Не меняем фото во время свайпа
+ if (isPanning.value) return; // Не меняем фото во время свайпа
currentPhotoIndex[userId] = index;
};
const nextPhoto = (userId) => {
- if (isDragging.value) return;
+ if (isPanning.value) return;
const user = suggestions.value.find(u => u._id === userId);
if (user && user.photos && user.photos.length > 0) {
currentPhotoIndex[userId] = (currentPhotoIndex[userId] + 1) % user.photos.length;
@@ -304,65 +297,55 @@ const nextPhoto = (userId) => {
};
const prevPhoto = (userId) => {
- if (isDragging.value) return;
+ if (isPanning.value) return;
const user = suggestions.value.find(u => u._id === userId);
if (user && user.photos && user.photos.length > 0) {
currentPhotoIndex[userId] = (currentPhotoIndex[userId] - 1 + user.photos.length) % user.photos.length;
}
};
-// Методы для свайпа на мобильных
-const startTouch = (event) => {
- touchStartX.value = event.touches[0].clientX;
- touchStartY.value = event.touches[0].clientY;
- swipeDirection.value = null;
-};
-
-const moveTouch = (event) => {
- if (!touchStartX.value) return;
+// Обработчики жестов для Hammer.js
+const onPan = (event) => {
+ isPanning.value = true;
- const currentX = event.touches[0].clientX;
- const currentY = event.touches[0].clientY;
+ // Получаем смещение по x и y
+ const deltaX = event.deltaX;
+ const deltaY = event.deltaY;
- // Сохраняем текущую позицию для использования в endTouch
- touchEndX.value = currentX;
- touchEndY.value = currentY;
-
- const diffX = currentX - touchStartX.value;
- const diffY = currentY - touchStartY.value;
-
- // Применяем точное смещение к карточке, чтобы она следовала за пальцем
- // Нет дополнительных коэффициентов, карточка перемещается точно на то же расстояние, что и палец
- if (Math.abs(diffX) > Math.abs(diffY)) {
- // Определяем направление свайпа для индикаторов
- if (diffX > 20) {
+ // Определяем направление свайпа для индикаторов
+ if (Math.abs(deltaX) > Math.abs(deltaY)) {
+ if (deltaX > 20) {
swipeDirection.value = 'right';
- } else if (diffX < -20) {
+ } else if (deltaX < -20) {
swipeDirection.value = 'left';
} else {
swipeDirection.value = null;
}
- // Применяем небольшой поворот для естественности, но меньший чем был
- // 0.05 вместо 0.1, чтобы эффект был легче
- const rotate = diffX * 0.05;
+ // Применяем небольшой поворот для естественности
+ const rotate = deltaX * 0.05;
+ // Применяем точное смещение к карточке, она следует за пальцем 1 к 1
cardStyle.value = {
- transform: `translateX(${diffX}px) rotate(${rotate}deg)`,
+ transform: `translateX(${deltaX}px) rotate(${rotate}deg)`,
transition: 'none'
};
-
- // Блокируем скролл страницы
- event.preventDefault();
+ }
+
+ // Блокируем скролл страницы при горизонтальном свайпе
+ if (Math.abs(deltaX) > Math.abs(deltaY)) {
+ event.srcEvent.preventDefault();
}
};
-const endTouch = () => {
- const diffX = touchEndX.value - touchStartX.value;
+const onPanEnd = (event) => {
+ isPanning.value = false;
- if (Math.abs(diffX) > swipeThreshold) {
- // Если свайп был достаточно длинный, анимируем к краю экрана
- if (diffX > 0) {
+ // Проверяем, достаточно ли было смещение для действия
+ if (Math.abs(event.deltaX) > swipeThreshold) {
+ // Если свайп был достаточно сильным, выполняем соответствующее действие
+ if (event.deltaX > 0) {
+ // Свайп вправо (лайк)
cardStyle.value = {
transform: `translateX(120%) rotate(${swipeAngle}deg)`,
transition: 'transform 0.3s ease'
@@ -370,6 +353,7 @@ const endTouch = () => {
// Плавная анимация уходящей карточки
setTimeout(() => handleLike(), 300);
} else {
+ // Свайп влево (пропуск)
cardStyle.value = {
transform: `translateX(-120%) rotate(-${swipeAngle}deg)`,
transition: 'transform 0.3s ease'
@@ -385,98 +369,11 @@ const endTouch = () => {
};
swipeDirection.value = null;
}
-
- // Сбрасываем значения
- touchStartX.value = 0;
- touchStartY.value = 0;
- touchEndX.value = 0;
- touchEndY.value = 0;
};
-// Методы для свайпа на десктопе (drag)
-const startDrag = (event) => {
- isDragging.value = true;
- dragStartX.value = event.clientX;
- dragStartY.value = event.clientY;
- dragOffset.value = { x: 0, y: 0 };
- swipeDirection.value = null;
-
- // Предотвращаем выделение текста
- event.preventDefault();
-};
-
-const moveDrag = (event) => {
- if (!isDragging.value) return;
-
- dragOffset.value = {
- x: event.clientX - dragStartX.value,
- y: event.clientY - dragStartY.value
- };
-
- // Определяем направление свайпа для индикаторов
- if (Math.abs(dragOffset.value.x) > Math.abs(dragOffset.value.y)) {
- if (dragOffset.value.x > 20) {
- swipeDirection.value = 'right';
- } else if (dragOffset.value.x < -20) {
- swipeDirection.value = 'left';
- } else {
- swipeDirection.value = null;
- }
-
- // Применяем небольшой поворот для естественности (уменьшен)
- const rotate = dragOffset.value.x * 0.05;
-
- // Карточка следует точно за курсором, без дополнительных коэффициентов
- cardStyle.value = {
- transform: `translateX(${dragOffset.value.x}px) rotate(${rotate}deg)`,
- transition: 'none'
- };
- }
-
- event.preventDefault();
-};
-
-const endDrag = () => {
- if (!isDragging.value) return;
-
- if (Math.abs(dragOffset.value.x) > swipeThreshold) {
- // Если перетаскивание было достаточно большим, выполняем действие лайка/пропуска с анимацией
- if (dragOffset.value.x > 0) {
- cardStyle.value = {
- transform: `translateX(120%) rotate(${swipeAngle}deg)`,
- transition: 'transform 0.3s ease'
- };
- // Плавная анимация уходящей карточки
- setTimeout(() => handleLike(), 300);
- } else {
- cardStyle.value = {
- transform: `translateX(-120%) rotate(-${swipeAngle}deg)`,
- transition: 'transform 0.3s ease'
- };
- // Плавная анимация уходящей карточки
- setTimeout(() => handlePass(), 300);
- }
- } else {
- // Если перетаскивание небольшое, плавно возвращаем карточку в исходное положение
- cardStyle.value = {
- transform: 'none',
- transition: 'transform 0.3s ease'
- };
- swipeDirection.value = null;
- }
-
- isDragging.value = false;
-};
-
-const cancelDrag = () => {
- if (isDragging.value) {
- cardStyle.value = {
- transform: 'none',
- transition: 'transform 0.3s ease'
- };
- swipeDirection.value = null;
- isDragging.value = false;
- }
+const handleCardTap = (event) => {
+ // Обрабатываем обычный тап по карточке, если нужно
+ // Например, можно открыть полную информацию о пользователе
};
// Основные функции
@@ -641,22 +538,10 @@ onMounted(() => {
error.value = "Пожалуйста, войдите в систему, чтобы просматривать анкеты.";
loading.value = false;
}
-
- // Предотвращаем скролл страницы при свайпе
- if (swipeArea.value) {
- swipeArea.value.addEventListener('touchmove', (e) => {
- if (isDragging.value) {
- e.preventDefault();
- }
- }, { passive: false });
- }
});
onUnmounted(() => {
- // Удаляем слушатели событий
- if (swipeArea.value) {
- swipeArea.value.removeEventListener('touchmove', null);
- }
+ // Здесь можно выполнить очистку, если необходимо
});
// Следим за изменениями в списке предложений
@@ -1250,6 +1135,18 @@ watch(suggestions, () => {
opacity: 0;
}
+/* Добавляем стили для Swipe компонентов */
+.swipe {
+ height: 100%;
+ width: 100%;
+ overflow: visible;
+}
+
+.swipe-item {
+ height: 100%;
+ width: 100%;
+}
+
/* Responsive Adjustments */
@media (max-width: 576px) {
.swipe-area {