diff --git a/src/common/base.css b/src/common/base.css index 497de04..2354854 100644 --- a/src/common/base.css +++ b/src/common/base.css @@ -20,6 +20,17 @@ body { img { 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 { border: none; diff --git a/src/components/RichTextEditor.vue b/src/components/RichTextEditor.vue index b0094e6..d4d87e0 100644 --- a/src/components/RichTextEditor.vue +++ b/src/components/RichTextEditor.vue @@ -601,10 +601,12 @@ const insertTodoList = () => { const dragState = ref({ isDragging: false, draggedImage: null, + startX: 0, startY: 0, currentY: 0, longPressTimer: null, - isLongPress: false + isLongPress: false, + indicator: null }) // 插入图片 @@ -797,12 +799,22 @@ const handleTouchStart = (e) => { const img = e.target 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) { 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.isLongPress = true dragState.value.draggedImage = img @@ -811,35 +823,86 @@ const handleTouchStart = (e) => { // 添加拖拽样式 img.classList.add('dragging') - img.style.opacity = '0.7' - img.style.transform = 'scale(0.95)' + img.style.opacity = '0.85' + img.style.transform = 'scale(0.99)' 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) { - navigator.vibrate(50) + navigator.vibrate(15) } // 阻止页面滚动 e.preventDefault() - }, 1000) // 1秒长按触发拖拽 + }, 400) // 400毫秒长按触发拖拽 } // 处理触摸移动事件 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() // 阻止页面滚动 - const img = dragState.value.draggedImage - dragState.value.currentY = e.touches[0].clientY + dragState.value.currentY = currentY // 计算位移 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(() => { @@ -863,22 +926,43 @@ const handleTouchEnd = (e) => { // 重置拖拽状态 const img = dragState.value.draggedImage - img.classList.remove('dragging') - img.style.opacity = '' - img.style.transform = '' - img.style.zIndex = '' - img.style.transition = '' + + // 添加释放动画 + img.style.transition = 'all 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94)' + img.style.transform = 'translateY(0) scale(1)' + 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) { - 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.draggedImage = null dragState.value.startY = 0 dragState.value.currentY = 0 + dragState.value.indicator = null // 触发内容更新 handleInput() @@ -899,17 +983,38 @@ const handleTouchCancel = (e) => { // 重置拖拽状态 const img = dragState.value.draggedImage - img.classList.remove('dragging') - img.style.opacity = '' - img.style.transform = '' - img.style.zIndex = '' - img.style.transition = '' + + // 添加取消动画 + img.style.transition = 'all 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94)' + img.style.transform = 'translateY(0) scale(1)' + 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.draggedImage = null dragState.value.startY = 0 dragState.value.currentY = 0 + dragState.value.indicator = null } // 检查并交换图片位置 @@ -921,7 +1026,7 @@ const checkAndSwapImages = (draggedImg, deltaY) => { // 计算拖拽图片的中心位置 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++) { @@ -931,8 +1036,23 @@ const checkAndSwapImages = (draggedImg, deltaY) => { const targetRect = targetImg.getBoundingClientRect() 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) break @@ -947,26 +1067,42 @@ const swapImages = (img1, img2) => { // 如果两张图片在同一父元素中 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') parent1.insertBefore(tempMarker, img1) - - // 交换位置 parent1.insertBefore(img1, img2) parent1.insertBefore(img2, tempMarker) - - // 移除临时标记 tempMarker.remove() - // 添加过渡效果 - img1.style.transition = 'transform 0.2s ease' - img2.style.transition = 'transform 0.2s ease' - - // 短暂延时后移除过渡效果 + // 重置变换 setTimeout(() => { - img1.style.transition = '' - img2.style.transition = '' - }, 200) + img1.style.transform = 'scale(0.99)' + img2.style.transform = 'scale(0.99)' + + // 短暂延时后移除过渡效果 + setTimeout(() => { + img1.style.transition = '' + img2.style.transition = '' + img1.style.transform = '' + img2.style.transform = '' + }, 250) + }, 250) } else { // 不同父元素的情况(更复杂,需要特殊处理) // 这里简化处理,实际项目中可能需要更复杂的逻辑 @@ -1531,6 +1667,12 @@ defineExpose({ background: var(--background-secondary); position: relative; 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) { @@ -1538,11 +1680,17 @@ defineExpose({ } :deep(.editor-content .editor-image.dragging) { - opacity: 0.7; - transform: scale(0.95); + opacity: 0.85; + transform: scale(0.99); z-index: 999; - transition: all 0.2s ease; - box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); + transition: all 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94); + 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; } /* 待办事项样式 */