You've already forked SmartisanNote.Remake
\"feat: 优化移动端图片拖拽体验和视觉反馈\"
This commit is contained in:
@@ -20,6 +20,17 @@ body {
|
|||||||
|
|
||||||
img {
|
img {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
outline-color: transparent;
|
||||||
|
lighting-color: transparent;
|
||||||
|
}
|
||||||
|
::selection {
|
||||||
|
background-color: #d3b9a7; /* 选中时的背景颜色 */
|
||||||
|
color: #ffffff; /* 选中时的文字颜色 */
|
||||||
|
}
|
||||||
|
img::selection {
|
||||||
|
background-color: transparent; /* 选中时的背景颜色 */
|
||||||
|
color: #ffffff; /* 选中时的文字颜色 */
|
||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
border: none;
|
border: none;
|
||||||
|
|||||||
@@ -601,10 +601,12 @@ const insertTodoList = () => {
|
|||||||
const dragState = ref({
|
const dragState = ref({
|
||||||
isDragging: false,
|
isDragging: false,
|
||||||
draggedImage: null,
|
draggedImage: null,
|
||||||
|
startX: 0,
|
||||||
startY: 0,
|
startY: 0,
|
||||||
currentY: 0,
|
currentY: 0,
|
||||||
longPressTimer: null,
|
longPressTimer: null,
|
||||||
isLongPress: false
|
isLongPress: false,
|
||||||
|
indicator: null
|
||||||
})
|
})
|
||||||
|
|
||||||
// 插入图片
|
// 插入图片
|
||||||
@@ -797,12 +799,22 @@ const handleTouchStart = (e) => {
|
|||||||
const img = e.target
|
const img = e.target
|
||||||
if (!img.classList.contains('editor-image')) return
|
if (!img.classList.contains('editor-image')) return
|
||||||
|
|
||||||
|
// 防止图片被选中
|
||||||
|
img.style.userSelect = 'none'
|
||||||
|
img.style.webkitUserSelect = 'none'
|
||||||
|
img.style.mozUserSelect = 'none'
|
||||||
|
img.style.msUserSelect = 'none'
|
||||||
|
|
||||||
// 清除之前的定时器
|
// 清除之前的定时器
|
||||||
if (dragState.value.longPressTimer) {
|
if (dragState.value.longPressTimer) {
|
||||||
clearTimeout(dragState.value.longPressTimer)
|
clearTimeout(dragState.value.longPressTimer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置长按检测定时器(1秒)
|
// 记录触摸开始位置
|
||||||
|
dragState.value.startX = e.touches[0].clientX
|
||||||
|
dragState.value.startY = e.touches[0].clientY
|
||||||
|
|
||||||
|
// 设置长按检测定时器(400毫秒)
|
||||||
dragState.value.longPressTimer = setTimeout(() => {
|
dragState.value.longPressTimer = setTimeout(() => {
|
||||||
dragState.value.isLongPress = true
|
dragState.value.isLongPress = true
|
||||||
dragState.value.draggedImage = img
|
dragState.value.draggedImage = img
|
||||||
@@ -811,35 +823,86 @@ const handleTouchStart = (e) => {
|
|||||||
|
|
||||||
// 添加拖拽样式
|
// 添加拖拽样式
|
||||||
img.classList.add('dragging')
|
img.classList.add('dragging')
|
||||||
img.style.opacity = '0.7'
|
img.style.opacity = '0.85'
|
||||||
img.style.transform = 'scale(0.95)'
|
img.style.transform = 'scale(0.99)'
|
||||||
img.style.zIndex = '999'
|
img.style.zIndex = '999'
|
||||||
img.style.transition = 'all 0.2s ease'
|
img.style.transition = 'all 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94)'
|
||||||
|
|
||||||
|
// 添加拖拽指示器
|
||||||
|
const indicator = document.createElement('div')
|
||||||
|
indicator.className = 'drag-indicator'
|
||||||
|
indicator.style.position = 'fixed'
|
||||||
|
indicator.style.top = '50%'
|
||||||
|
indicator.style.left = '50%'
|
||||||
|
indicator.style.transform = 'translate(-50%, -50%)'
|
||||||
|
indicator.style.padding = '8px 16px'
|
||||||
|
indicator.style.background = 'rgba(0, 0, 0, 0.75)'
|
||||||
|
indicator.style.color = 'white'
|
||||||
|
indicator.style.borderRadius = '16px'
|
||||||
|
indicator.style.fontSize = '13px'
|
||||||
|
indicator.style.zIndex = '1000'
|
||||||
|
indicator.style.opacity = '0'
|
||||||
|
indicator.style.transition = 'opacity 0.2s ease'
|
||||||
|
indicator.textContent = '拖拽排序'
|
||||||
|
document.body.appendChild(indicator)
|
||||||
|
|
||||||
|
// 渐显指示器
|
||||||
|
setTimeout(() => {
|
||||||
|
indicator.style.opacity = '1'
|
||||||
|
}, 10)
|
||||||
|
|
||||||
|
// 保存指示器引用以便后续移除
|
||||||
|
dragState.value.indicator = indicator
|
||||||
|
|
||||||
// 添加震动反馈(如果设备支持)
|
// 添加震动反馈(如果设备支持)
|
||||||
if (navigator.vibrate) {
|
if (navigator.vibrate) {
|
||||||
navigator.vibrate(50)
|
navigator.vibrate(15)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 阻止页面滚动
|
// 阻止页面滚动
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}, 1000) // 1秒长按触发拖拽
|
}, 400) // 400毫秒长按触发拖拽
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理触摸移动事件
|
// 处理触摸移动事件
|
||||||
const handleTouchMove = (e) => {
|
const handleTouchMove = (e) => {
|
||||||
if (!dragState.value.isLongPress || !dragState.value.draggedImage) return
|
if (!dragState.value.longPressTimer && !dragState.value.isLongPress) return
|
||||||
|
|
||||||
|
const img = dragState.value.draggedImage
|
||||||
|
const currentX = e.touches[0].clientX
|
||||||
|
const currentY = e.touches[0].clientY
|
||||||
|
|
||||||
|
// 防止图片被选中
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
// 如果还没有触发长按,检查是否移动过多(超过8px则取消长按)
|
||||||
|
if (!dragState.value.isLongPress) {
|
||||||
|
const deltaX = Math.abs(currentX - dragState.value.startX)
|
||||||
|
const deltaY = Math.abs(currentY - dragState.value.startY)
|
||||||
|
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY)
|
||||||
|
|
||||||
|
if (distance > 8) {
|
||||||
|
// 移动过多,取消长按
|
||||||
|
if (dragState.value.longPressTimer) {
|
||||||
|
clearTimeout(dragState.value.longPressTimer)
|
||||||
|
dragState.value.longPressTimer = null
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dragState.value.isLongPress || !img) return
|
||||||
|
|
||||||
e.preventDefault() // 阻止页面滚动
|
e.preventDefault() // 阻止页面滚动
|
||||||
|
|
||||||
const img = dragState.value.draggedImage
|
dragState.value.currentY = currentY
|
||||||
dragState.value.currentY = e.touches[0].clientY
|
|
||||||
|
|
||||||
// 计算位移
|
// 计算位移
|
||||||
const deltaY = dragState.value.currentY - dragState.value.startY
|
const deltaY = dragState.value.currentY - dragState.value.startY
|
||||||
|
|
||||||
// 更新图片位置
|
// 更新图片位置,添加缓动效果
|
||||||
img.style.transform = `translateY(${deltaY}px) scale(0.95)`
|
const easeFactor = 0.7
|
||||||
|
img.style.transform = `translateY(${deltaY * easeFactor}px) scale(0.99)`
|
||||||
|
|
||||||
// 使用requestAnimationFrame优化性能
|
// 使用requestAnimationFrame优化性能
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
@@ -863,22 +926,43 @@ const handleTouchEnd = (e) => {
|
|||||||
|
|
||||||
// 重置拖拽状态
|
// 重置拖拽状态
|
||||||
const img = dragState.value.draggedImage
|
const img = dragState.value.draggedImage
|
||||||
img.classList.remove('dragging')
|
|
||||||
img.style.opacity = ''
|
// 添加释放动画
|
||||||
img.style.transform = ''
|
img.style.transition = 'all 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94)'
|
||||||
img.style.zIndex = ''
|
img.style.transform = 'translateY(0) scale(1)'
|
||||||
img.style.transition = ''
|
img.style.opacity = '1'
|
||||||
|
|
||||||
|
// 移除拖拽指示器
|
||||||
|
if (dragState.value.indicator) {
|
||||||
|
const indicator = dragState.value.indicator
|
||||||
|
indicator.style.opacity = '0'
|
||||||
|
setTimeout(() => {
|
||||||
|
if (indicator.parentNode) {
|
||||||
|
indicator.parentNode.removeChild(indicator)
|
||||||
|
}
|
||||||
|
}, 250)
|
||||||
|
}
|
||||||
|
|
||||||
// 添加震动反馈(如果设备支持)
|
// 添加震动反馈(如果设备支持)
|
||||||
if (navigator.vibrate) {
|
if (navigator.vibrate) {
|
||||||
navigator.vibrate(30)
|
navigator.vibrate(8)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 延迟重置样式以显示动画
|
||||||
|
setTimeout(() => {
|
||||||
|
if (img) {
|
||||||
|
img.classList.remove('dragging')
|
||||||
|
img.style.zIndex = ''
|
||||||
|
img.style.transition = ''
|
||||||
|
}
|
||||||
|
}, 250)
|
||||||
|
|
||||||
// 重置状态
|
// 重置状态
|
||||||
dragState.value.isLongPress = false
|
dragState.value.isLongPress = false
|
||||||
dragState.value.draggedImage = null
|
dragState.value.draggedImage = null
|
||||||
dragState.value.startY = 0
|
dragState.value.startY = 0
|
||||||
dragState.value.currentY = 0
|
dragState.value.currentY = 0
|
||||||
|
dragState.value.indicator = null
|
||||||
|
|
||||||
// 触发内容更新
|
// 触发内容更新
|
||||||
handleInput()
|
handleInput()
|
||||||
@@ -899,17 +983,38 @@ const handleTouchCancel = (e) => {
|
|||||||
|
|
||||||
// 重置拖拽状态
|
// 重置拖拽状态
|
||||||
const img = dragState.value.draggedImage
|
const img = dragState.value.draggedImage
|
||||||
img.classList.remove('dragging')
|
|
||||||
img.style.opacity = ''
|
// 添加取消动画
|
||||||
img.style.transform = ''
|
img.style.transition = 'all 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94)'
|
||||||
img.style.zIndex = ''
|
img.style.transform = 'translateY(0) scale(1)'
|
||||||
img.style.transition = ''
|
img.style.opacity = '1'
|
||||||
|
|
||||||
|
// 移除拖拽指示器
|
||||||
|
if (dragState.value.indicator) {
|
||||||
|
const indicator = dragState.value.indicator
|
||||||
|
indicator.style.opacity = '0'
|
||||||
|
setTimeout(() => {
|
||||||
|
if (indicator.parentNode) {
|
||||||
|
indicator.parentNode.removeChild(indicator)
|
||||||
|
}
|
||||||
|
}, 250)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 延迟重置样式以显示动画
|
||||||
|
setTimeout(() => {
|
||||||
|
if (img) {
|
||||||
|
img.classList.remove('dragging')
|
||||||
|
img.style.zIndex = ''
|
||||||
|
img.style.transition = ''
|
||||||
|
}
|
||||||
|
}, 250)
|
||||||
|
|
||||||
// 重置状态
|
// 重置状态
|
||||||
dragState.value.isLongPress = false
|
dragState.value.isLongPress = false
|
||||||
dragState.value.draggedImage = null
|
dragState.value.draggedImage = null
|
||||||
dragState.value.startY = 0
|
dragState.value.startY = 0
|
||||||
dragState.value.currentY = 0
|
dragState.value.currentY = 0
|
||||||
|
dragState.value.indicator = null
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查并交换图片位置
|
// 检查并交换图片位置
|
||||||
@@ -921,7 +1026,7 @@ const checkAndSwapImages = (draggedImg, deltaY) => {
|
|||||||
|
|
||||||
// 计算拖拽图片的中心位置
|
// 计算拖拽图片的中心位置
|
||||||
const draggedRect = draggedImg.getBoundingClientRect()
|
const draggedRect = draggedImg.getBoundingClientRect()
|
||||||
const draggedCenterY = draggedRect.top + draggedRect.height / 2 + deltaY
|
const draggedCenterY = draggedRect.top + draggedRect.height / 2 + deltaY * 0.8 // 添加缓动因子
|
||||||
|
|
||||||
// 查找最近的图片进行交换
|
// 查找最近的图片进行交换
|
||||||
for (let i = 0; i < allImages.length; i++) {
|
for (let i = 0; i < allImages.length; i++) {
|
||||||
@@ -931,8 +1036,23 @@ const checkAndSwapImages = (draggedImg, deltaY) => {
|
|||||||
const targetRect = targetImg.getBoundingClientRect()
|
const targetRect = targetImg.getBoundingClientRect()
|
||||||
const targetCenterY = targetRect.top + targetRect.height / 2
|
const targetCenterY = targetRect.top + targetRect.height / 2
|
||||||
|
|
||||||
// 检查是否与目标图片重叠
|
// 检查是否与目标图片重叠,使用更精确的碰撞检测
|
||||||
if (Math.abs(draggedCenterY - targetCenterY) < (draggedRect.height + targetRect.height) / 2) {
|
const overlapThreshold = (draggedRect.height + targetRect.height) * 0.4
|
||||||
|
const distance = Math.abs(draggedCenterY - targetCenterY)
|
||||||
|
|
||||||
|
if (distance < overlapThreshold) {
|
||||||
|
// 添加接近效果
|
||||||
|
targetImg.style.transition = 'all 0.2s ease'
|
||||||
|
targetImg.style.transform = 'scale(1.02)'
|
||||||
|
targetImg.style.boxShadow = '0 0 0 2px var(--primary)'
|
||||||
|
|
||||||
|
// 短暂延迟后恢复
|
||||||
|
setTimeout(() => {
|
||||||
|
targetImg.style.transform = ''
|
||||||
|
targetImg.style.boxShadow = ''
|
||||||
|
targetImg.style.transition = ''
|
||||||
|
}, 200)
|
||||||
|
|
||||||
// 交换位置
|
// 交换位置
|
||||||
swapImages(draggedImg, targetImg)
|
swapImages(draggedImg, targetImg)
|
||||||
break
|
break
|
||||||
@@ -947,26 +1067,42 @@ const swapImages = (img1, img2) => {
|
|||||||
|
|
||||||
// 如果两张图片在同一父元素中
|
// 如果两张图片在同一父元素中
|
||||||
if (parent1 === parent2) {
|
if (parent1 === parent2) {
|
||||||
// 创建临时标记来帮助交换位置
|
// 添加交换动画
|
||||||
|
img1.style.transition = 'transform 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94)'
|
||||||
|
img2.style.transition = 'transform 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94)'
|
||||||
|
|
||||||
|
// 获取当前位置
|
||||||
|
const rect1 = img1.getBoundingClientRect()
|
||||||
|
const rect2 = img2.getBoundingClientRect()
|
||||||
|
|
||||||
|
// 计算位移
|
||||||
|
const translateY1 = rect2.top - rect1.top
|
||||||
|
const translateY2 = rect1.top - rect2.top
|
||||||
|
|
||||||
|
// 应用位移
|
||||||
|
img1.style.transform = `translateY(${translateY1}px) scale(0.99)`
|
||||||
|
img2.style.transform = `translateY(${translateY2}px) scale(0.99)`
|
||||||
|
|
||||||
|
// 交换DOM位置
|
||||||
const tempMarker = document.createElement('div')
|
const tempMarker = document.createElement('div')
|
||||||
parent1.insertBefore(tempMarker, img1)
|
parent1.insertBefore(tempMarker, img1)
|
||||||
|
|
||||||
// 交换位置
|
|
||||||
parent1.insertBefore(img1, img2)
|
parent1.insertBefore(img1, img2)
|
||||||
parent1.insertBefore(img2, tempMarker)
|
parent1.insertBefore(img2, tempMarker)
|
||||||
|
|
||||||
// 移除临时标记
|
|
||||||
tempMarker.remove()
|
tempMarker.remove()
|
||||||
|
|
||||||
// 添加过渡效果
|
// 重置变换
|
||||||
img1.style.transition = 'transform 0.2s ease'
|
|
||||||
img2.style.transition = 'transform 0.2s ease'
|
|
||||||
|
|
||||||
// 短暂延时后移除过渡效果
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
img1.style.transition = ''
|
img1.style.transform = 'scale(0.99)'
|
||||||
img2.style.transition = ''
|
img2.style.transform = 'scale(0.99)'
|
||||||
}, 200)
|
|
||||||
|
// 短暂延时后移除过渡效果
|
||||||
|
setTimeout(() => {
|
||||||
|
img1.style.transition = ''
|
||||||
|
img2.style.transition = ''
|
||||||
|
img1.style.transform = ''
|
||||||
|
img2.style.transform = ''
|
||||||
|
}, 250)
|
||||||
|
}, 250)
|
||||||
} else {
|
} else {
|
||||||
// 不同父元素的情况(更复杂,需要特殊处理)
|
// 不同父元素的情况(更复杂,需要特殊处理)
|
||||||
// 这里简化处理,实际项目中可能需要更复杂的逻辑
|
// 这里简化处理,实际项目中可能需要更复杂的逻辑
|
||||||
@@ -1531,6 +1667,12 @@ defineExpose({
|
|||||||
background: var(--background-secondary);
|
background: var(--background-secondary);
|
||||||
position: relative;
|
position: relative;
|
||||||
outline: none; /* 移除默认焦点轮廓 */
|
outline: none; /* 移除默认焦点轮廓 */
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.editor-content .editor-image.draggable) {
|
:deep(.editor-content .editor-image.draggable) {
|
||||||
@@ -1538,11 +1680,17 @@ defineExpose({
|
|||||||
}
|
}
|
||||||
|
|
||||||
:deep(.editor-content .editor-image.dragging) {
|
:deep(.editor-content .editor-image.dragging) {
|
||||||
opacity: 0.7;
|
opacity: 0.85;
|
||||||
transform: scale(0.95);
|
transform: scale(0.99);
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 12px 25px rgba(0, 0, 0, 0.22);
|
||||||
|
will-change: transform;
|
||||||
|
filter: brightness(1.03);
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 待办事项样式 */
|
/* 待办事项样式 */
|
||||||
|
|||||||
Reference in New Issue
Block a user