Smoother touch panel sliding
Deploy Livetrail / deploy (push) Successful in 17s

This commit is contained in:
2026-09-09 23:48:24 +02:00
parent a70f6fe64c
commit 56d8f46434
6 changed files with 81 additions and 39 deletions
+9 -9
View File
@@ -229,14 +229,6 @@ export default {
v-model:base-map="baseMap"
:terrain-enabled="terrainEnabled"
/>
<Lightbox
ref="lightbox"
:always-show-nav-on-touch-devices="true"
:position-from-top="0"
:has-video="true"
@media-change="onLightboxMediaChange"
@closing="onLightboxClosing"
/>
<ProjectSettings
:ref="setSettings"
:projects="projectOptions"
@@ -256,5 +248,13 @@ export default {
@new-markers="addNewMarkers"
@toggle="(bIsOpen, iAnimDuration) => onPanelToggle('right', bIsOpen, iAnimDuration)"
/>
<Lightbox
ref="lightbox"
:always-show-nav-on-touch-devices="true"
:position-from-top="0"
:has-video="true"
@media-change="onLightboxMediaChange"
@closing="onLightboxClosing"
/>
</div>
</template>
</template>
+4 -20
View File
@@ -3,6 +3,7 @@ import Simplebar from 'simplebar-vue';
import AppIcon from '@components/AppIcon';
import ProjectPost from '@components/ProjectPost';
import { createSwipeToClose, initialSwipeState } from '@scripts/swipeToClose';
export default {
components: {
@@ -26,7 +27,7 @@ export default {
isOpen: false,
posts: [],
refreshRate: 60,
swipe: {x: null, y: null}
swipe: initialSwipeState()
};
},
emits: ['request-last-update', 'new-markers', 'toggle'],
@@ -76,7 +77,6 @@ export default {
this.refIdLast = 0;
this.firstChunk = true;
this.posts = [];
this.swipe = {x: null, y: null};
this.toggle(!this.isMobile(), 0);
this.getScrollElement().scrollTop = 0;
@@ -162,23 +162,7 @@ export default {
if((box.scrollTop + box.clientHeight) / (content?.offsetHeight || 1) >= 0.8) this.getNextFeed();
},
onTouchStart(oEvent) {
if(!this.isMobile() || !this.isOpen || oEvent.touches.length != 1) return;
const oTouch = oEvent.touches[0];
this.swipe = {x: oTouch.clientX, y: oTouch.clientY};
},
onTouchEnd(oEvent) {
const oTouch = oEvent.changedTouches[0];
if(!oTouch || this.swipe.x === null) return;
const iDeltaX = oTouch.clientX - this.swipe.x;
const iDeltaY = oTouch.clientY - this.swipe.y;
if(iDeltaX > 80 && Math.abs(iDeltaX) > Math.abs(iDeltaY) * 1.5) this.toggle();
this.swipe = {x: null, y: null};
},
...createSwipeToClose(1),
setUpdateTimer(iSeconds) {
if(typeof this.feedTimer != 'undefined') clearTimeout(this.feedTimer);
if(iSeconds >= 0) this.feedTimer = setTimeout(this.onUpdateTimer, iSeconds * 1000);
@@ -224,7 +208,7 @@ export default {
</script>
<template>
<div id="feed" class="panel panel-right" @touchstart.passive="onTouchStart" @touchend.passive="onTouchEnd">
<div id="feed" class="panel panel-right" @touchstart.passive="onTouchStart" @touchmove.passive="onTouchMove" @touchend.passive="onTouchEnd">
<Simplebar id="feed-panel" class="panel-content" ref="feedSimpleBar" :aria-busy="loading">
<div id="feed-header">
<ProjectPost v-if="modeHisto" :options="{type: 'archived', headerless: true}" />
+6 -3
View File
@@ -5,6 +5,7 @@ import AppIcon from '@components/AppIcon';
import ProjectAccount from '@components/ProjectAccount';
import logoIconUrl from '@images/icons/favicon.svg';
import logoTitleUrl from '@images/logo_title.svg';
import { createSwipeToClose, initialSwipeState } from '@scripts/swipeToClose';
export default {
components: {
@@ -26,7 +27,8 @@ export default {
isOpen: false,
lastUpdate: {unix_time: 0, relative_time: '', formatted_time: ''},
logoIconUrl,
logoTitleUrl
logoTitleUrl,
swipe: initialSwipeState()
};
},
emits: ['update:baseMap', 'update:projectCodeName', 'update:terrainEnabled', 'toggle'],
@@ -81,13 +83,14 @@ export default {
},
getWidth() {
return this.$el.getBoundingClientRect().width;
}
},
...createSwipeToClose(-1)
}
};
</script>
<template>
<div id="settings" class="panel panel-left">
<div id="settings" class="panel panel-left" @touchstart.passive="onTouchStart" @touchmove.passive="onTouchMove" @touchend.passive="onTouchEnd">
<div id="settings-panel" class="panel-content">
<div class="settings-header settings-box">
<img :src="logoIconUrl" class="logo-icon clickable" :title="lang.get('project.overview')" @click="hash.items = []" />
+54
View File
@@ -0,0 +1,54 @@
/* Swipe-to-close gesture for a slide-in side panel */
// Initial value for the swipe data field createSwipeToClose()'s handlers expect on the component
export function initialSwipeState() {
return {x: null, y: null, dragging: false};
}
// Builds touchstart/touchmove/touchend handlers for a panel that closes by
// dragging it towards its own off-screen side. iDirection is:
// +1 for a panel that opens from the right
// -1 for one that opens from the left
export function createSwipeToClose(iDirection) {
return {
onTouchStart(oEvent) {
if(!this.isMobile() || !this.isOpen || oEvent.touches.length != 1) return;
const oTouch = oEvent.touches[0];
this.swipe = {...initialSwipeState(), x: oTouch.clientX, y: oTouch.clientY};
},
onTouchMove(oEvent) {
if(this.swipe.x === null || oEvent.touches.length != 1) return;
const oTouch = oEvent.touches[0];
const iDeltaX = oTouch.clientX - this.swipe.x;
const iDeltaY = oTouch.clientY - this.swipe.y;
if(!this.swipe.dragging) {
//Only commit to a horizontal drag once the gesture is clearly not a vertical scroll
if(iDeltaX * iDirection <= 10 || Math.abs(iDeltaX) <= Math.abs(iDeltaY) * 1.5) return;
this.swipe.dragging = true;
this.$el.classList.add('moving');
}
const iDragX = iDirection * Math.min(Math.max(iDeltaX * iDirection, 0), this.getWidth());
this.$el.style.setProperty('--drag-x', `${iDragX}px`);
},
onTouchEnd(oEvent) {
const oTouch = oEvent.changedTouches[0];
if(!oTouch || this.swipe.x === null) return;
const iDeltaX = oTouch.clientX - this.swipe.x;
const iDeltaY = oTouch.clientY - this.swipe.y;
if(this.swipe.dragging) {
this.$el.classList.remove('moving');
this.$el.style.removeProperty('--drag-x');
}
if(iDeltaX * iDirection > 80 && Math.abs(iDeltaX) > Math.abs(iDeltaY) * 1.5) this.toggle(false);
this.swipe = initialSwipeState();
}
};
}
+2 -2
View File
@@ -52,13 +52,13 @@
}
.panel.panel-right {
transform: translateX(calc(var(--button-width) + var.$block-spacing * 2));
transform: translateX(calc(var(--button-width) + var.$block-spacing * 2 + var(--drag-x, 0px)));
}
}
&.with-left-panel {
.panel.panel-left {
transform: translateX(0);
transform: translateX(var(--drag-x, 0px));
}
.panel.panel-right {
+6 -5
View File
@@ -19,7 +19,7 @@ $panel-actual-width: min($panel-width, #{$panel-width-max});
}
.panel.panel-left {
transform: translateX(0);
transform: translateX(var(--drag-x, 0px));
}
}
@@ -53,17 +53,18 @@ $panel-actual-width: min($panel-width, #{$panel-width-max});
bottom: 0;
z-index: 1;
user-select: none;
touch-action: pan-y;
width: #{$panel-width};
max-width: calc(#{$panel-width-max});
transition: transform var.$trans-slow;
&.moving {
cursor: grabbing;
transition: none;
}
&.panel-left {
transform: translateX(-100%);
transform: translateX(calc(-100% + var(--drag-x, 0px)));
.panel-content {
width: calc(100% - var.$block-spacing);
@@ -81,8 +82,8 @@ $panel-actual-width: min($panel-width, #{$panel-width-max});
}
&.panel-right {
transform: translateX(100vw);
transform: translateX(calc(100vw + var(--drag-x, 0px)));
.panel-content {
width: 100%;
padding-top: var.$block-spacing;