初始需求是返回说话人数组,每一项有头像,昵称,时间和说话文本,但是鼠标选中只需要说话文本的集合,而且可以跨多行选中。
- 因为我只要说话文本,所以将说话文本用p标签包起来,其它的元素用div。
后端返回的文本是一长串字符串,前端需要处理将每一个字分割,然后用span标签包起来。
- 利用mouseup方法,当鼠标指针移动到元素上方,并松开鼠标左键时,会发生 mouseup 事件。
- 在mouseup事件里面使用window.getSelection()属性
// Range就是通过鼠标或者键盘或者代码选中的一段内容,这段内容浏览器会将其封装成一个Range对象
const range = window.getSelection().getRangeAt(0);
docFragment,获取鼠标选中的所有DOM元素
const docFragment = range.cloneContents();
// 用户选择多行文字的时候
const parentP = docFragment.querySelectorAll("p");
// 用户选择一行文字,但是文字是多个的时候
const childSpan = docFragment.querySelectorAll("span");
// 用户选择一行文字,并且文字只有一个的时候
const oneText = [range.startContainer.parentElement];
- 因为在开发过程中,发现当用户选择一行文字,并且文字只有一个的时候,parentP和childSpan数组是空的。当用户选择一行文字,但是文字是多个的时候parentP数组是空的。所以做了这三个判断。
- 以用户选中多行文字为例:循环parentP,因为parentP是类数组,所以需要用Array.from(parentP)转义一下。
目的是为了拿到parentP数组对象中dataset里面的值,然后处理成我想要的格式:
[
{
"parentId": "111",
"childId": [
{
"id": "0",
"val": "字"
},
{
"id": "1",
"val": "段"
}
]
},
{
"parentId": "222",
"childId": [
{
"id": "0",
"val": "我"
},
{
"id": "1",
"val": "刚"
}
]
}
]
- 点击确认选中的时候,将选中的文字高亮。this.selectText能够拿到选中的纯文本
- 下面是完整代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>vue开发:利用window.getSelection()实现多行文字选中高亮,只针对需要的标签</title>
<!--引入 element-ui 的样式,-->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<script src="https://cdn.bootcss.com/vue/2.5.16/vue.min.js"></script>
<!-- 引入element 的组件库-->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<style>
* {
margin: 0;
padding: 0;
}
#app {
padding: 50px;
width: 100vw;
height: 100vh;
box-sizing: border-box;
}
.el-textarea__inner {
width: 500px;
}
.labelbutton {
box-sizing: border-box;
position: absolute;
z-index: 1;
height: 44px;
left: 30%;
top: 30%;
background: #ffffff;
border: 1px solid #e5e7ef;
box-shadow: 0px 8px 24px rgba(18, 19, 20, 0.08);
border-radius: 3px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 10px;
cursor: pointer;
}
span.highlightActive {
background: red;
}
</style>
</head>
<body>
<div @mouseup.stop="pitchOnFun($event)" id="app">
<ul>
<li v-for="(item, index) in detail" :key="index">
<div>
昵称:{{ item.nickname }}
</div>
<p :data-id="item.id">
<span :class="{highlightActive: textItem.highlightActive }" v-for="(textItem, textIndex) in item.text2"
:key="textIndex" :data-id="textIndex" :data-val="textItem.val" :data-parentid="item.id">{{ textItem.val
}}</span>
</p>
</li>
</ul>
<div @click.stop="selectFun" class="labelbutton" v-show="highlightDialog"
:style="{ left: layerX + 'px', top: layerY + 'px' }">
确定选中
</div>
<br>
<br>
<br>
<br>
<br>
获取选中的纯文本:
<br>
{{selectText}}
</div>
<script>
new Vue({
el: '#app',
data() {
return {
// 弹层坐标
layerX: 0,
layerY: 0,
highlightDialog: false,
selectedText: [],
selectText: '',
// 模拟数据是从后端返回
detail: [
{
"text": "字段,是说明我再创建一个联系人。",
"nickname": "宝贝",
id: '111',
},
{
"text": "我刚刚创建了一个供应商,然后它是一个自定义类型,我要把它做成一个细。",
"nickname": "宝贝",
id: '222',
},
{
"text": "表,怎么弄?我的供应商,拍摄系表,在这选到我的供应商。",
"nickname": "宝贝",
id: '333',
}
],
}
},
created() {
this.detail = this.detail.map((item) => {
// 前端将每一句话分割成每一个字
item.text2 = item.text.split("").map((item, index) => {
const obj = {};
obj.val = item;
return obj;
});
return item
});
},
mounted() {
document.addEventListener("click", this.unhighlightPopupHideFun);
},
beforeUnmount() {
window.removeEventListener("click", this.unhighlightPopupHideFun);
},
methods: {
// 关闭取消高亮弹出层
unhighlightPopupHideFun() {
this.unhighlightPopup = false;
},
pitchOnFun(event) {
setTimeout(() => {
// console.log(event, "event");
// console.log(window.getSelection().toString(), "选中的文本");
if (!window.getSelection().toString()) {
//选中文本后出现标注列表弹框
this.highlightDialog = false;
return;
};
// 当前选中的集合
this.selectedText = [];
this.layerX = event.clientX;
this.layerY = event.clientY + 10;
const range = window.getSelection().getRangeAt(0);
const docFragment = range.cloneContents();
const parentP = docFragment.querySelectorAll("p");
const childSpan = docFragment.querySelectorAll("span");
const oneText = [range.startContainer.parentElement];
this.selectText = "";
// 选择多行文字
if (parentP.length) {
Array.from(parentP).forEach((item) => {
const childArr = Array.from(item.children).map((item2) => {
return { id: item2.dataset.id, val: item2.dataset.val };
});
if (item.dataset.id && childArr.length) {
this.selectedText.push({
parentId: item.dataset.id,
childId: childArr,
});
}
});
} else {
let childArr = [];
let parentId = "";
// 选择一行文字'
if (Array.from(childSpan).length) {
childArr = Array.from(childSpan).map((item2) => {
return { id: item2.dataset.id, val: item2.dataset.val };
});
parentId = childSpan[0].dataset.parentid;
} else {
// 一行文字只选择了一个字
childArr = [{ id: oneText[0].dataset.id, val: oneText[0].dataset.val }];
parentId = oneText[0].dataset.parentid;
}
if (parentId && childArr.length) {
this.selectedText = [
{
parentId: parentId,
childId: childArr,
},
];
}
}
if (!this.selectedText.length) {
return;
} else {
// 当选中文本是用户说话内容的时候,弹出标签列表
this.highlightDialog = true;
}
// 获取选中的纯文本
if (docFragment.querySelectorAll("span").length) {
this.selectText = Array.from(docFragment.querySelectorAll("span"))
.map((item) => {
return item.innerHTML;
})
.join("");
} else {
this.selectText = range.startContainer.parentElement.innerHTML;
}
}, 50);
},
selectFun() {
console.log(this.selectedText, '选中的文字集合')
// 先取消所有高亮
this.detail.forEach((item, index) => {
item.text2.forEach((item3, index3) => {
if (item3.highlightActive) {
this.$set(item3, "highlightActive", false);
}
});
});
this.detail.forEach((item, index) => {
this.selectedText.forEach((item2, index2) => {
// 父集id相同
if (item.id == item2.parentId) {
item.text2.forEach((item3, index3) => {
item2.childId.forEach((item4, index4) => {
// 子集下标相同
if (index3 == item4.id) {
this.$set(item3, "highlightActive", true);
}
});
});
}
});
});
},
}
})
</script>
</body>
</html>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。