feat(PdfViewer): 添加控制栏拖动功能
为PDF查看器添加控制栏拖动功能,支持鼠标和触摸屏操作 添加阻尼效果和拖动阈值,优化用户体验 更新样式以反映拖动状态
Showing
2 changed files
with
135 additions
and
3 deletions
| ... | @@ -52,6 +52,7 @@ declare module 'vue' { | ... | @@ -52,6 +52,7 @@ declare module 'vue' { |
| 52 | VanDropdownMenu: typeof import('vant/es')['DropdownMenu'] | 52 | VanDropdownMenu: typeof import('vant/es')['DropdownMenu'] |
| 53 | VanEmpty: typeof import('vant/es')['Empty'] | 53 | VanEmpty: typeof import('vant/es')['Empty'] |
| 54 | VanField: typeof import('vant/es')['Field'] | 54 | VanField: typeof import('vant/es')['Field'] |
| 55 | + VanFloatingBubble: typeof import('vant/es')['FloatingBubble'] | ||
| 55 | VanForm: typeof import('vant/es')['Form'] | 56 | VanForm: typeof import('vant/es')['Form'] |
| 56 | VanIcon: typeof import('vant/es')['Icon'] | 57 | VanIcon: typeof import('vant/es')['Icon'] |
| 57 | VanImage: typeof import('vant/es')['Image'] | 58 | VanImage: typeof import('vant/es')['Image'] | ... | ... |
| 1 | <!-- | 1 | <!-- |
| 2 | * @Date: 2025-01-21 | 2 | * @Date: 2025-01-21 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2025-10-22 13:49:33 | 4 | + * @LastEditTime: 2025-10-22 14:11:50 |
| 5 | * @FilePath: /mlaj/src/components/ui/PdfViewer.vue | 5 | * @FilePath: /mlaj/src/components/ui/PdfViewer.vue |
| 6 | * @Description: PDF预览组件 - 使用pdf-vue3库 | 6 | * @Description: PDF预览组件 - 使用pdf-vue3库 |
| 7 | --> | 7 | --> |
| ... | @@ -10,7 +10,7 @@ | ... | @@ -10,7 +10,7 @@ |
| 10 | :style="{ height: '100%', width: '100%' }"> | 10 | :style="{ height: '100%', width: '100%' }"> |
| 11 | <div class="pdf-viewer-container"> | 11 | <div class="pdf-viewer-container"> |
| 12 | <!-- PDF内容区域 --> | 12 | <!-- PDF内容区域 --> |
| 13 | - <div class="pdf-content" :class="{ 'pdf-no-select': preventSave }"> | 13 | + <div id="pdf-content" class="pdf-content" :class="{ 'pdf-no-select': preventSave }"> |
| 14 | <PDF v-if="url && show && !loadingError" | 14 | <PDF v-if="url && show && !loadingError" |
| 15 | ref="pdfRef" | 15 | ref="pdfRef" |
| 16 | :src="url" | 16 | :src="url" |
| ... | @@ -53,7 +53,16 @@ | ... | @@ -53,7 +53,16 @@ |
| 53 | <!-- 已移除顶部关闭按钮,关闭功能迁移至控制栏 --> | 53 | <!-- 已移除顶部关闭按钮,关闭功能迁移至控制栏 --> |
| 54 | 54 | ||
| 55 | <!-- 缩放控制按钮 --> | 55 | <!-- 缩放控制按钮 --> |
| 56 | - <div v-if="!loading && !loadingError" class="zoom-controls"> | 56 | + <div |
| 57 | + v-if="!loading && !loadingError" | ||
| 58 | + class="zoom-controls" | ||
| 59 | + :class="{ 'dragging': isDragging }" | ||
| 60 | + :style="{ bottom: controlsY + 'px' }" | ||
| 61 | + @mousedown="handleDragStart" | ||
| 62 | + @touchstart="handleTouchStart" | ||
| 63 | + @touchmove="handleTouchMove" | ||
| 64 | + @touchend="handleTouchEnd" | ||
| 65 | + > | ||
| 57 | <van-button class="zoom-btn zoom-close-btn" type="default" icon="cross" round @click="handleClose" /> | 66 | <van-button class="zoom-btn zoom-close-btn" type="default" icon="cross" round @click="handleClose" /> |
| 58 | <!-- <van-button class="zoom-btn zoom-in-btn" type="default" icon="plus" round @click="zoomIn" /> --> | 67 | <!-- <van-button class="zoom-btn zoom-in-btn" type="default" icon="plus" round @click="zoomIn" /> --> |
| 59 | <font-awesome-icon icon="magnifying-glass-plus" class="text-2xl text-gray-600 ml-2 mr-2" @click="zoomIn" /> | 68 | <font-awesome-icon icon="magnifying-glass-plus" class="text-2xl text-gray-600 ml-2 mr-2" @click="zoomIn" /> |
| ... | @@ -152,6 +161,14 @@ const pageInput = ref(''); | ... | @@ -152,6 +161,14 @@ const pageInput = ref(''); |
| 152 | const isEditingPage = ref(false); | 161 | const isEditingPage = ref(false); |
| 153 | const pageInputRef = ref(null); | 162 | const pageInputRef = ref(null); |
| 154 | 163 | ||
| 164 | +// 拖动控制相关变量 | ||
| 165 | +const isDragging = ref(false); // 是否正在拖动 | ||
| 166 | +const dragStartY = ref(0); // 拖动开始时的Y坐标 | ||
| 167 | +const controlsY = ref(20); // 控制栏距离底部的距离(px) | ||
| 168 | +const dragOffset = ref(0); // 拖动偏移量 | ||
| 169 | +const initialControlsY = ref(20); // 拖动开始时控制栏的初始位置 | ||
| 170 | +const dragThreshold = 5; // 拖动阈值,超过这个距离才认为是拖动 | ||
| 171 | + | ||
| 155 | /** | 172 | /** |
| 156 | * 处理页码输入框失焦事件 | 173 | * 处理页码输入框失焦事件 |
| 157 | */ | 174 | */ |
| ... | @@ -181,6 +198,110 @@ const focusPageInput = () => { | ... | @@ -181,6 +198,110 @@ const focusPageInput = () => { |
| 181 | } | 198 | } |
| 182 | }; | 199 | }; |
| 183 | 200 | ||
| 201 | +/** | ||
| 202 | + * 处理拖动开始事件(鼠标) | ||
| 203 | + */ | ||
| 204 | +const handleDragStart = (e) => { | ||
| 205 | + // 如果点击的是按钮,不处理拖动 | ||
| 206 | + if (e.target.closest('.zoom-btn') || e.target.closest('.page-jump')) { | ||
| 207 | + return; | ||
| 208 | + } | ||
| 209 | + | ||
| 210 | + dragStartY.value = e.clientY; | ||
| 211 | + initialControlsY.value = controlsY.value; // 记录拖动开始时的位置 | ||
| 212 | + dragOffset.value = 0; | ||
| 213 | + document.addEventListener('mousemove', handleDragMove); | ||
| 214 | + document.addEventListener('mouseup', handleDragEnd); | ||
| 215 | + // 不立即调用preventDefault,等确认是拖动后再阻止 | ||
| 216 | +}; | ||
| 217 | + | ||
| 218 | +/** | ||
| 219 | + * 处理拖动移动事件(鼠标) | ||
| 220 | + */ | ||
| 221 | +const handleDragMove = (e) => { | ||
| 222 | + const deltaY = Math.abs(dragStartY.value - e.clientY); | ||
| 223 | + | ||
| 224 | + // 只有移动距离超过阈值才开始拖动 | ||
| 225 | + if (!isDragging.value && deltaY > dragThreshold) { | ||
| 226 | + isDragging.value = true; | ||
| 227 | + e.preventDefault(); | ||
| 228 | + } | ||
| 229 | + | ||
| 230 | + if (!isDragging.value) return; | ||
| 231 | + | ||
| 232 | + const actualDeltaY = dragStartY.value - e.clientY; // 向上为正,向下为负 | ||
| 233 | + const dampingFactor = 0.3; // 阻尼系数,降低移动速度 | ||
| 234 | + const dampedDelta = actualDeltaY * dampingFactor; | ||
| 235 | + | ||
| 236 | + const newY = Math.max(20, Math.min(window.innerHeight - 100, initialControlsY.value + dampedDelta)); | ||
| 237 | + controlsY.value = newY; | ||
| 238 | + dragOffset.value = dampedDelta; | ||
| 239 | + e.preventDefault(); | ||
| 240 | +}; | ||
| 241 | + | ||
| 242 | +/** | ||
| 243 | + * 处理拖动结束事件(鼠标) | ||
| 244 | + */ | ||
| 245 | +const handleDragEnd = () => { | ||
| 246 | + isDragging.value = false; | ||
| 247 | + dragStartY.value = 0; | ||
| 248 | + dragOffset.value = 0; | ||
| 249 | + document.removeEventListener('mousemove', handleDragMove); | ||
| 250 | + document.removeEventListener('mouseup', handleDragEnd); | ||
| 251 | +}; | ||
| 252 | + | ||
| 253 | +/** | ||
| 254 | + * 处理触摸开始事件(移动端) | ||
| 255 | + */ | ||
| 256 | +const handleTouchStart = (e) => { | ||
| 257 | + if (e.touches.length === 1) { | ||
| 258 | + // 如果触摸的是按钮,不处理拖动 | ||
| 259 | + if (e.target.closest('.zoom-btn') || e.target.closest('.page-jump')) { | ||
| 260 | + return; | ||
| 261 | + } | ||
| 262 | + | ||
| 263 | + dragStartY.value = e.touches[0].clientY; | ||
| 264 | + initialControlsY.value = controlsY.value; // 记录拖动开始时的位置 | ||
| 265 | + dragOffset.value = 0; | ||
| 266 | + // 不立即调用preventDefault,等确认是拖动后再阻止 | ||
| 267 | + } | ||
| 268 | +}; | ||
| 269 | + | ||
| 270 | +/** | ||
| 271 | + * 处理触摸移动事件(移动端) | ||
| 272 | + */ | ||
| 273 | +const handleTouchMove = (e) => { | ||
| 274 | + if (e.touches.length !== 1) return; | ||
| 275 | + | ||
| 276 | + const deltaY = Math.abs(dragStartY.value - e.touches[0].clientY); | ||
| 277 | + | ||
| 278 | + // 只有移动距离超过阈值才开始拖动 | ||
| 279 | + if (!isDragging.value && deltaY > dragThreshold) { | ||
| 280 | + isDragging.value = true; | ||
| 281 | + e.preventDefault(); | ||
| 282 | + } | ||
| 283 | + | ||
| 284 | + if (!isDragging.value) return; | ||
| 285 | + | ||
| 286 | + const actualDeltaY = dragStartY.value - e.touches[0].clientY; // 向上为正,向下为负 | ||
| 287 | + const dampingFactor = 0.4; // 移动端稍微高一点的阻尼系数 | ||
| 288 | + const dampedDelta = actualDeltaY * dampingFactor; | ||
| 289 | + | ||
| 290 | + const newY = Math.max(20, Math.min(window.innerHeight - 100, initialControlsY.value + dampedDelta)); | ||
| 291 | + controlsY.value = newY; | ||
| 292 | + dragOffset.value = dampedDelta; | ||
| 293 | + e.preventDefault(); | ||
| 294 | +}; | ||
| 295 | + | ||
| 296 | +/** | ||
| 297 | + * 处理触摸结束事件(移动端) | ||
| 298 | + */ | ||
| 299 | +const handleTouchEnd = () => { | ||
| 300 | + isDragging.value = false; | ||
| 301 | + dragStartY.value = 0; | ||
| 302 | + dragOffset.value = 0; | ||
| 303 | +}; | ||
| 304 | + | ||
| 184 | // 超时处理相关 | 305 | // 超时处理相关 |
| 185 | const loadingTimeout = ref(30000); // 超时时间,默认30秒 | 306 | const loadingTimeout = ref(30000); // 超时时间,默认30秒 |
| 186 | const timeoutTimer = ref(null); // 超时计时器 | 307 | const timeoutTimer = ref(null); // 超时计时器 |
| ... | @@ -749,6 +870,8 @@ const applyPinchZoom = (newZoom, centerX, centerY, container) => { | ... | @@ -749,6 +870,8 @@ const applyPinchZoom = (newZoom, centerX, centerY, container) => { |
| 749 | box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); | 870 | box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); |
| 750 | backdrop-filter: blur(8px); | 871 | backdrop-filter: blur(8px); |
| 751 | transition: all 0.3s ease; | 872 | transition: all 0.3s ease; |
| 873 | + cursor: grab; | ||
| 874 | + user-select: none; | ||
| 752 | } | 875 | } |
| 753 | 876 | ||
| 754 | .zoom-controls:hover { | 877 | .zoom-controls:hover { |
| ... | @@ -757,6 +880,14 @@ const applyPinchZoom = (newZoom, centerX, centerY, container) => { | ... | @@ -757,6 +880,14 @@ const applyPinchZoom = (newZoom, centerX, centerY, container) => { |
| 757 | transform: translateX(-50%) translateY(-2px); | 880 | transform: translateX(-50%) translateY(-2px); |
| 758 | } | 881 | } |
| 759 | 882 | ||
| 883 | +.zoom-controls.dragging { | ||
| 884 | + cursor: grabbing; | ||
| 885 | + transform: translateX(-50%) scale(1.05); | ||
| 886 | + box-shadow: 0 8px 40px rgba(0, 0, 0, 0.25); | ||
| 887 | + background: rgba(255, 255, 255, 1); | ||
| 888 | + transition: none; | ||
| 889 | +} | ||
| 890 | + | ||
| 760 | .zoom-btn { | 891 | .zoom-btn { |
| 761 | width: 40px; | 892 | width: 40px; |
| 762 | height: 40px; | 893 | height: 40px; | ... | ... |
-
Please register or login to post a comment