starwait/components/ResizeContent.vue
2025-04-25 18:02:03 +08:00

143 lines
3.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
// =====================
// 响应式数据
// =====================
const resizableElement = ref(null) // 元素引用
const width = ref(240) // 显示的宽度值
const isDragging = ref(false) // 拖拽状态标识
const startX = ref(0) // 拖拽起始X坐标
const startWidth = ref(0) // 拖拽起始宽度
let rafId = null // requestAnimationFrame ID
// =====================
// 核心方法
// =====================
const startDrag = (e) => {
isDragging.value = true
// 获取初始坐标(同时支持鼠标和触摸事件)
const clientX = e.clientX ?? e.touches[0].clientX
startX.value = clientX
startWidth.value = width.value
// 添加事件监听(使用 passive 提升滚动性能)
document.addEventListener('mousemove', handleDrag, { passive: true })
document.addEventListener('touchmove', handleDrag, { passive: true })
document.addEventListener('mouseup', stopDrag)
document.addEventListener('touchend', stopDrag)
}
const handleDrag = (e) => {
if (!isDragging.value) return
// 使用 requestAnimationFrame 进行节流
cancelAnimationFrame(rafId)
rafId = requestAnimationFrame(() => {
// 获取当前坐标(兼容触摸事件)
const clientX = e.clientX ?? e.touches?.[0]?.clientX
const deltaX = clientX - startX.value
const newWidth = startWidth.value + deltaX
// 应用约束示例最小100px可根据需要扩展
const clampedWidth = Math.max(100, newWidth)
// 优化:直接操作 CSS 变量减少响应式更新
resizableElement.value.style.setProperty('--width', `${clampedWidth}px`)
// 低频更新响应式数据(用于显示数值)
width.value = clampedWidth
})
}
const stopDrag = () => {
isDragging.value = false
// 清理事件监听
document.removeEventListener('mousemove', handleDrag)
document.removeEventListener('touchmove', handleDrag)
document.removeEventListener('mouseup', stopDrag)
document.removeEventListener('touchend', stopDrag)
}
// =====================
// 生命周期
// =====================
onMounted(() => {
// 初始化 CSS 变量
resizableElement.value.style.setProperty('--width', `${width.value}px`)
})
onBeforeUnmount(() => {
// 组件卸载时取消未执行的动画帧
cancelAnimationFrame(rafId)
})
</script>
<template>
<div class="resizable-element" :style="{ '--width': width + 'px' }" ref="resizableElement">
<slot/>
<!-- 拖拽手柄支持触摸事件 -->
<div class="dargContainer">
<div class="dargBar" @mousedown="startDrag" @touchstart.passive="startDrag"/>
</div>
</div>
</template>
<style scoped>
.resizable-element {
--width: 300px; /* 默认值,会被 JS 覆盖 */
position: relative;
height: 100%;
background: #f8f9fa;
width: var(--width);
transition: width 0.15s ease; /* 平滑过渡效果 */
will-change: width; /* 提示浏览器优化重绘 */
}
.dargContainer{
position: absolute;
width: 6px;
//background: red;
height: calc(100% - 10px);
right: 0;
top: 0;
bottom: 0;
margin: auto 0;
}
.dargBar {
position: relative;
margin: 0 auto;
width: 2px;
height: 100%;
cursor: col-resize;
border-radius: 2px;
opacity: 0;
background: #00000000;
transition: opacity 0.1s ease, background 0.1s ease;
transform: translateZ(0); /* 触发 GPU 加速 */
z-index: 1;
}
/* 悬停状态 */
.dargBar:hover, .dargContainer:hover .dargBar{
opacity: 1;
background: #495057cc;
}
/* 拖拽激活状态 */
.dargBar:active {
background: #212529;
}
/* 移动端优化 */
@media (hover: none) {
.dargBar {
opacity: 0.5; /* 移动端保持可见 */
width: 12px; /* 增大触摸区域 */
}
}
</style>