这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助
页面效果
具体实现
新增
- 1、监听鼠标抬起事件,通过
window.getSelection()
方法获取鼠标用户选择的文本范围或光标的当前位置。 - 2、通过
选中的文字长度是否大于0
或window.getSelection().isCollapsed
(返回一个布尔值用于描述选区的起始点和终止点是否位于一个位置,即是否框选了)来判断是否展示标签选择的弹窗。 - 3、标签选择的弹窗采用
子绝父相
的定位方式,通过鼠标抬起的位置确认弹窗的top
与left
值。
const TAG_WIDTH = 280 //自定义最大范围,以保证不超过内容的最大宽度
const tagInfo = ref({
visible: false,
top: 0,
left: 0,
})
const el = document.getElementById('text-container')
//鼠标抬起
el?.addEventListener('mouseup', (e) => {
const text = window?.getSelection()?.toString() || ''
if (text.length > 0) {
const left = e.offsetX < TAG_WIDTH ? 0 : e.offsetX - 300
tagInfo.value = {
visible: true,
top: e.offsetY + 40,
left: left,
}
getSelectedTextData()
} else {
tagInfo.value.visible = false
}
//清空重选/取消数据
resetEditTag()
const selectedText = reactive({
start: 0,
end: 0,
content: '',
})
//获取选取的文字数据
const getSelectedTextData = () => {
const select = window?.getSelection() as any
console.log('selectselectselectselect', select)
const nodeValue = select.focusNode?.nodeValue
const anchorOffset = select.anchorOffset
const focusOffset = select.focusOffset
const nodeValueSatrtIndex = markContent.value?.indexOf(nodeValue)
selectedText.content = select.toString()
if (anchorOffset < focusOffset) {
//从左到右标注
selectedText.start = nodeValueSatrtIndex + anchorOffset
selectedText.end = nodeValueSatrtIndex + focusOffset
} else {
//从右到左
selectedText.start = nodeValueSatrtIndex + focusOffset
selectedText.end = nodeValueSatrtIndex + anchorOffset
}
}
javascript操作光标和选区详情可参考文档:blog.51cto.com/u_14524391/…
- 4、选中标签后,采用markjs的
markRanges()
方式去创建一个选中的元素并为其添加样式和绑定事件。 - 5、定义一个响应式的文字列表,专门记录标记的内容,添加完元素后可追加一条已标记的数据。
import Mark from 'mark.js'
import {ref} from 'vue
import { nanoid } from 'nanoid'
const selectedTextList = ref([])
const handleSelectLabel = (t) => {
const marker = new Mark(document.getElementById('text-container'))
const { tag_color, tag_name, tag_id } = t
const markId = nanoid(10)
marker.markRanges(
[
{
start: selectedText.start, //必填
length: selectedText.content.length, //必填
},
],
{
className: 'text-selected',
element: 'span',
each: (element: any) => {
//为元素添加样式和属性
element.setAttribute('id', markId)
element.style.borderBottom = `2px solid ${t.tag_color}` //添加下划线
element.style.color = t.tag_color
//绑定事件
element.onclick = function (e: any) {
//
}
},
}
)
selectedTextList.value.push({
tag_color,
tag_name,
tag_id,
start: selectedText.start,
end: selectedText.end,
mark_content:selectedText.content,
mark_id: markId,
})
}
删除
点击已进行标记的文字————>重选/取消弹窗显示————>点击取消
如何判断点击的文字是否已标记,通过在创建的标记元素中绑定点击事件,触发则表示已标记。
- 在点击事件中记录该标记的相关内容,如颜色,文字,起始位置,以及唯一标识id(新建时给元素添加一个id属性,点击时即可通过
e.target.id
获取)
import { nanoid } from 'nanoid'
//选择标签后
const markId = nanoid(10)
marker.markRanges(
[
{
start: isReset ? editTag.value.start : selectedText.start,
length: isReset ? editTag.value.content.length : selectedText.content.length,
},
],
{
className: 'text-selected',
element: 'span',
each: (element: any) => {
element.setAttribute('id', markId)
//绑定事件
element.onclick = function (e: any) {
e.preventDefault()
if (!e.target.id) return
const left = e.offsetX < TAG_WIDTH ? 0 : e.offsetX - 300
const item = selectedTextList.value?.find?.((t) => t.mark_id == e.target.id) as any
const { mark_content, tag_id, start, end } = item || {}
editTag.value = {
visible: true,
top: e.offsetY + 40,
left: e.offsetX,
mark_id: e.target.id,
content: mark_content || '',
tag_id: tag_id || '',
start: start,
end: end,
}
tagInfo.value = {
visible: false,
top: e.offsetY + 40,
left: left,
}
}
},
}
)
- 点击取消后,获取在此前记录的id,根据id查询相关的标记元素
- 使用
markjs.unmark()
方法即可删除此元素。 - 绑定的响应式数据,可使用
findIndex
和splice()
删除
- 编辑弹窗隐藏
const handleCancel = () => {
if (!editTag.value.mark_id) return
const markEl = new Mark(document.getElementById(editTag.value.mark_id))
markEl.unmark()
selectedTextList.value.splice(
selectedTextList.value?.findIndex((t) => t.mark_id == editTag.value.mark_id),
1
)
tagInfo.value = {
visible: false,
top: 0,
left: 0,
}
resetEditTag()
}
const resetEditTag = () => {
editTag.value = {
visible: false,
top: 0,
left: 0,
mark_id: '',
content: '',
tag_id: '',
start: 0,
end: 0,
}
}
重选
和取消的步骤一样,只不过在点击重选后,先弹出标签弹窗,选择标签后,需要先删除选中的元素,然后再新增一个标记元素。由于在标签选择,在标签选择中判断一下是否是重选,是重选的话就需删除后再创建元素,不是的话就代表是新增,直接新增标记元素(综上所述)。
const handleSelectLabel = (t: TTag) => {
tagInfo.value.visible = false
const { tag_color, tag_name, tag_id } = t
const marker = new Mark(document.getElementById('text-container'))
const markId = nanoid(10)
const isReset = selectedTextList.value?.map((j) => j.mark_id).includes(editTag.value.mark_id)
? 1
: 0 // 1:重选 0:新增
if (isReset) {
//如若重选,则删除后再新增标签
const markEl = new Mark(document.getElementById(editTag.value.mark_id))
markEl.unmark()
selectedTextList.value.splice(
selectedTextList.value?.findIndex((t) => t.mark_id == editTag.value.mark_id),
1
)
}
marker.markRanges(
[
{
start: isReset ? editTag.value.start : selectedText.start,
length: isReset ? editTag.value.content.length : selectedText.content.length,
},
],
{
className: 'text-selected',
element: 'span',
each: (element: any) => {
element.setAttribute('id', markId)
element.style.borderBottom = `2px solid ${t.tag_color}`
element.style.color = t.tag_color
element.style.userSelect = 'none'
element.style.paddingBottom = '6px'
element.onclick = function (e: any) {
e.preventDefault()
if (!e.target.id) return
const left = e.offsetX < TAG_WIDTH ? 0 : e.offsetX - 300
const item = selectedTextList.value?.find?.((t) => t.mark_id == e.target.id) as any
const { mark_content, tag_id, start, end } = item || {}
editTag.value = {
visible: true,
top: e.offsetY + 40,
left: e.offsetX,
mark_id: e.target.id,
content: mark_content || '',
tag_id: tag_id || '',
start: start,
end: end,
}
tagInfo.value = {
visible: false,
top: e.offsetY + 40,
left: left,
}
}
},
}
)
selectedTextList.value.push({
tag_color,
tag_name,
tag_id,
start: isReset ? editTag.value.start : selectedText.start,
end: isReset ? editTag.value.end : selectedText.end,
mark_content: isReset ? editTag.value.content : selectedText.content,
mark_id: markId,
})
}
清空标记
const handleAllDelete = () => {
selectedTextList.value = []
const marker = new Mark(document.getElementById('text-container'))
marker.unmark()
}
完整代码
清空标记
保存
{{ markContent }}
{{ i.tag_name }}
√
取 消
重 选
本文转载于:
https://juejin.cn/post/7282950051319283770
如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。