在之前的博客中有通过vue-diff实现两侧文本对比的功能,由于博主的使用场景是需要在一个页面中用v-for来渲染多个vue-diff插件实现文本对比的,这个时候发现在多段的基础上想跨段落复制单侧的文字内容会把另外一侧的内容也给选中,基于这个问题做出了一下解决方案:
vue-diff插件使用请参考:vue3使用vue-diff插件实现文本对比_vue-diff文本比较插件-CSDN博客
解决思路:
通过user-select
控制左/右两侧文本的选择行为,在用户交互时动态禁用或启用文本选择功能
-
事件时序控制
-
selectstart
:选择操作开始前锁定区域 -
mousedown
:物理点击时立即重置状态
-
-
CSS控制策略
user-select: none; /* 禁用选择 */ user-select: text; /* 恢复选择 */
相比
disabled
属性方案的优势:-
保持元素可交互状态
-
避免视觉样式冲突
-
支持平滑过渡动画
-
-
DOM遍历优化
-
使用
classList.contains
替代className匹配 -
通过父容器(.vue-diff-row)缩小查询范围
-
批量操作替代逐元素处理
-
代码示例:
<div v-for="(item, index) in compareData" :key="index">
<Diff
mode="split"
theme="light"
language="text"
:prev="item.originalText"
:current="item.translationText"
/>
</div>
nextTick(() => {
// 监听 selectstart 事件(用户开始选择文本时触发)
document.addEventListener("selectstart", (e) => {
if (e.target) {
// 判断目标元素是否在右侧类名区域内
let currentIndex = handleClosest(
e.target,
"vue-diff-cell-added",
"vue-diff-row"
)
? 0 // 如果用户选中的是右侧元素,currentIndex 设为 0,将左侧禁用
: 1; // 否则设为 1,将右侧禁用
// 遍历所有 .vue-diff-row 元素
document.querySelectorAll(".vue-diff-row").forEach((row) => {
let codeList = row.querySelectorAll(".code");
if (codeList) {
// 禁用对应索引的 .code 元素的文本选择
codeList[currentIndex].style.userSelect = "none";
}
});
}
});
// 监听 mousedown 事件(鼠标按下时触发)
document.addEventListener("mousedown", () => {
// 启用所有 .code 元素的文本选择
document.querySelectorAll(".code")?.forEach((val) => {
val.style.userSelect = "text";
});
});
});
// 判断目标元素target是否在右侧目标元素内,一直溯源的sourceName根元素
const handleClosest = (target, parentName, sourceName) => {
if (target.classList?.contains(sourceName)) {
return false; // 目标元素是 sourceName 类,返回 false
} else if (target.classList?.contains(parentName)) {
return true; // 目标元素或父元素是 parentName 类,返回 true
} else {
// 递归向上查找父元素
return handleClosest(target.parentNode, parentName, sourceName);
}
};