- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
window.getSelection()
方法获取鼠标用户选择的文本范围或光标的当前位置。 选中的文字长度是否大于0
或 window.getSelection().isCollapsed
(返回一个布尔值用于描述选区的起始点和终止点是否位于一个位置,即是否框选了)来判断是否展示标签选择的弹窗。 子绝父相
的定位方式,通过鼠标抬起的位置确认弹窗的 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操作光标和选区详情可参考文档: https://blog.51cto.com/u_14524391/3712814 。
markRanges()
方式去创建一个选中的元素并为其添加样式和绑定事件。
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,
})
}
点击已进行标记的文字————>重选/取消弹窗显示————>点击取消 。
如何判断点击的文字是否已标记,通过在创建的标记元素中绑定点击事件,触发则表示已标记.
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,
}
}
},
}
)
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()
}
<script setup lang="ts">
import { ref, onMounted, reactive } from 'vue'
import Mark from 'mark.js'
import { nanoid } from 'nanoid'
type TTag = {
tag_name: string
tag_id: string
tag_color: string
}
type TSelectText = {
tag_id: string
tag_name: string
tag_color: string
start: number
end: number
mark_content: string
mark_id: string
}
const TAG_WIDTH = 280
const selectedTextList = ref<TSelectText[]>([])
const selectedText = reactive({
start: 0,
end: 0,
content: '',
})
const markContent = ref(
'这是标注的内容有业绩还是我我很快就很快就开完如突然好几个地方各级很大功夫数据库二极管捍卫国家和我回家很晚十九世纪俄国激活工具和丈母娘环境和颠覆国家的高房价奥苏爱哦因为i以太网图的还是觉得好看啊空间函数调用加快速度还是饥渴的发货可是磕碰日俄和那那么会就开始开会的数据库和也会觉得讲故事的而黄金九二额呵呵三角函数的吧合乎实际的和尽快核实当升科技看交互的接口和送二ui为人开朗少女都被你们进货金额麦当娜表面上的'
)
const tagInfo = ref({
visible: false,
top: 0,
left: 0,
})
const editTag = ref({
visible: false,
top: 0,
left: 0,
mark_id: '',
content: '',
tag_id: '',
start: 0,
end: 0,
})
const tagList: TTag[] = [
{
tag_name: '标签一',
tag_color: `#DE050CFF`,
tag_id: 'tag_id1',
},
{
tag_name: '标签二',
tag_color: `#6ADE05FF`,
tag_id: 'tag_id2',
},
{
tag_name: '标签三',
tag_color: `#DE058BFF`,
tag_id: 'tag_id3',
},
{
tag_name: '标签四',
tag_color: `#9205DEFF`,
tag_id: 'tag_id4',
},
{
tag_name: '标签五',
tag_color: `#DE5F05FF`,
tag_id: 'tag_id5',
},
]
const handleAllDelete = () => {
selectedTextList.value = []
const marker = new Mark(document.getElementById('text-container'))
marker.unmark()
}
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 handleReset = () => {
editTag.value.visible = false
tagInfo.value.visible = true
}
const handleSave = () => {
console.log('标注的数据', selectedTextList.value)
}
const handleSelectLabel = (t: TTag) => {
const { tag_color, tag_name, tag_id } = t
tagInfo.value.visible = false
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 getSelectedTextData = () => {
const select = window?.getSelection() as any
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
}
}
const resetEditTag = () => {
editTag.value = {
visible: false,
top: 0,
left: 0,
mark_id: '',
content: '',
tag_id: '',
start: 0,
end: 0,
}
}
const drawMark = () => {
//模拟后端返回的数据
const res = [
{
start: 2, //必备
end: 6,
tag_color: '#DE050CFF',
tag_id: 'tag_id1',
tag_name: '标签一',
mark_content: '标注的内容',
mark_id: 'mark_id1',
},
{
start: 39,
end: 41,
tag_color: '#6ADE05FF',
tag_id: 'tag_id2',
tag_name: '标签二',
mark_content: '二极管',
mark_id: 'mark_id2',
},
{
start: 58,
end: 61,
tag_color: '#DE058BFF',
tag_id: 'tag_id3',
tag_name: '标签三',
mark_content: '激活工具',
mark_id: 'mark_id3',
},
]
selectedTextList.value = res?.map((t) => ({
tag_id: t.tag_id,
tag_name: t.tag_name,
tag_color: t.tag_color,
start: t.start,
end: t.end,
mark_content: t.mark_content,
mark_id: t.mark_id,
}))
const markList =
selectedTextList.value?.map((j) => ({
...j,
start: j.start, //必备
length: j.end - j.start + 1, //必备
})) || []
const marker = new Mark(document.getElementById('text-container'))
markList?.forEach?.(function (m: any) {
marker.markRanges([m], {
element: 'span',
className: 'text-selected',
each: (element: any) => {
element.setAttribute('id', m.mark_id)
element.style.borderBottom = `2px solid ${m.tag_color}`
element.style.color = m.tag_color
element.style.userSelect = 'none'
element.style.paddingBottom = '6px'
element.onclick = function (e: any) {
console.log('cccccc', m)
const left = e.offsetX < TAG_WIDTH ? 0 : e.offsetX - 300
editTag.value = {
visible: true,
top: e.offsetY + 40,
left: e.offsetX,
mark_id: m.mark_id,
content: m.mark_content,
tag_id: m.tag_id,
start: m.start,
end: m.end,
}
tagInfo.value = {
visible: false,
top: e.offsetY + 40,
left: left,
}
}
},
})
})
}
//页面初始化
onMounted(() => {
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()
})
//从后端获取标注数据,进行初始化标注
drawMark()
})
</script>
<template>
<header>
<n-button
type="primary"
:disabled="selectedTextList.length == 0 ? true : false"
ghost
@click="handleAllDelete"
>
清空标记
</n-button>
<n-button
type="primary"
:disabled="selectedTextList.length == 0 ? true : false"
@click="handleSave"
>
保存
</n-button>
</header>
<main>
<div id="text-container" class="text">
{{ markContent }}
</div>
<!-- 标签选择 -->
<div
v-if="tagInfo.visible && tagList.length > 0"
:class="['tag-box p-4 ']"
:style="{ top: tagInfo.top + 'px', left: tagInfo.left + 'px' }"
>
<div v-for="i in tagList" :key="i.tag_id" class="tag-name" @click="handleSelectLabel(i)">
<n-space>
<p>{{ i.tag_name }}</p>
<n-button v-if="i.tag_id == editTag.tag_id" text type="primary">√</n-button>
</n-space>
<div
:class="['w-4 h-4']"
:style="{
background: i.tag_color,
}"
></div>
</div>
</div>
<!-- 重选/取消 -->
<div
v-if="editTag.visible"
class="edit-tag"
:style="{ top: editTag.top + 'px', left: editTag.left + 'px' }"
>
<div class="py-1 bg-gray-100 text-center" @click="handleCancel">取 消</div>
<div class="py-1 bg-gray-100 mt-2 text-center" @click="handleReset">重 选</div>
</div>
</main>
</template>
<style lang="less" scoped>
header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 24px;
height: 80px;
border-bottom: 1px solid #e5e7eb;
user-select: none;
background: #fff;
}
main {
background: #fff;
margin: 24px;
height: 80vh;
padding: 24px;
overflow-y: auto;
position: relative;
box-shadow: 0 3px 8px 0 rgb(0 0 0 / 13%);
.text {
color: #333;
font-weight: 500;
font-size: 16px;
line-height: 50px;
}
.tag-box {
position: absolute;
z-index: 10;
width: 280px;
max-height: 40vh;
overflow-y: auto;
background: #fff;
border-radius: 4px;
box-shadow: 0 9px 28px 8px rgb(0 0 0 / 3%), 0 6px 16px 4px rgb(0 0 0 / 9%),
0 3px 6px -2px rgb(0 0 0 / 20%);
user-select: none;
.tag-name {
width: 100%;
background: rgba(243, 244, 246, var(--tw-bg-opacity));
font-size: 14px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
padding: 4px 8px;
margin-top: 8px;
}
.tag-name:nth-of-type(1) {
margin-top: 0;
}
}
.edit-tag {
position: absolute;
z-index: 20;
padding: 16px;
cursor: pointer;
width: 100px;
background: #fff;
border-radius: 4px;
box-shadow: 0 9px 28px 8px rgb(0 0 0 / 3%), 0 6px 16px 4px rgb(0 0 0 / 9%),
0 3px 6px -2px rgb(0 0 0 / 20%);
user-select: none;
}
::selection {
background: rgb(51 51 51 / 20%);
}
}
</style>
目前功能实现比较简单,还有很多发挥的空间,先小小的记录一下,最后~,预祝大家,双节快乐!! 。
markjs 。
最后此篇关于vue3+mark.js|实现文字标注功能的文章就讲到这里了,如果你想了解更多关于vue3+mark.js|实现文字标注功能的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我正在构建一个 RCP 应用程序,其中每个季度都会更新功能/插件。因此,如果用户选择自动更新功能/插件,则会下载更新插件的新 jar,但旧插件仍在使用我不再使用的磁盘空间。 我厌倦了删除包含旧 jar
我如何从外部 Controller 功能中调用 Controller 内部的功能,例如电话间隙回调功能 这是 Controller 外部定义的功能 function onDeviceReady()
如果某个功能(例如 MediaSource)可用,我如何使用 Google Dart 检查。 new MediaSource() 抛出一个错误。如何以编程方式检查此类或功能是否存在?有任何想法吗?是否
我正在尝试运行 Azure Orchestrations,突然我开始从 statusQueryGetUri 收到错误: 协调器函数“UploadDocumentOrchestrator”失败:函数“U
我见过 iPhone 上的应用程序,如果在 3.0 上运行,将使用 3.0 功能/API,例如应用内电子邮件编辑器,如果在 2.x 上运行,则不使用这些功能,并退出应用程序以启动邮件相反。 这是怎么做
这是 DB 规范化理论中的一个概念: Third normal form is violated when a non-key field is a fact about another non-ke
如果我定义 #if SOMETHING #endif 而且我还没有在任何地方定义 SOMETHING。 #if 中的代码会编译吗? 最佳答案 当#if的参数表达式中使用的名称未定义为宏时(在所有其他宏
我刚刚澄清了 A* 路径查找应该如何在两条路径具有相等值的 [情况] 下运行,无论是在计算期间还是在结束时,如果有两条相等的短路径。 例如,我在我的起始节点,我可以扩展到两个可能的节点,但它们都具有相
Java有没有类似下面的东西 宏 一种遍历所有私有(private)字段的方法 类似于 smalltalk symbols 的东西——即用于快速比较静态字符串的东西? 请注意,我正在尝试为 black
这个程序应该将华氏度转换为摄氏度: #include int main() { float fahrenheit, celsius; int max, min, step;
当打开PC缓存功能后, 软件将采用先进先出的原则排队对示波器采集的每一帧数据, 进行帧缓存。 当发现屏幕中有感兴趣的波形掠过时, 鼠标点击软件的(暂停)按钮, 可以选择回看某一帧的波形
我有一个特殊的(虚拟)函数,我想在沙盒环境中使用它: disable.system.call eval(parse(text = 'model.frame("1 ~ 1")'), envir = e
使用新的 Service 实现,我是否必须为我的所有服务提供一个 Options 方法? 使用我的所有服务当前使用的旧 ServiceBase 方法,OPTIONS 返回 OK,但没有 Access-
我正在阅读 Fogus 的关于 Clojure 的喜悦的书,在并行编程章节中,我看到了一个函数定义,它肯定想说明一些重要的事情,但我不知道是什么。此外,我看不到这个函数有什么用 - 当我执行时,它什么
我有大量的 C 代码,大部分代码被注释掉和/或 #if 0。当我使用 % 键匹配 if-else 的左括号和右括号时,它也匹配注释掉的代码。 有没有办法或vim插件在匹配括号时不考虑注释掉或#if 0
我有这个功能: map(map(fn x =>[x])) [[],[1],[2,3,4]]; 产生: val it = [[],[[1]],[[2],[3],[4]]] 我不明白这个功能是如何工作的。
我使用 Visual Studio 代码创建了一个函数应用程序,然后发布了它。功能应用程序运行良好。我现在在功能门户中使用代码部署功能(KUDU)并跳过构建。下面是日志 9:55:46 AM
我有一个数据框df: userID Score Task_Alpha Task_Beta Task_Charlie Task_Delta 3108 -8.00 Easy Easy
我真的无法解决这个问题: 我有一个返回数据框的函数。但是,数据框仅打印在我的控制台中,尽管我希望将其存储在工作空间中。我怎样才能做到这一点? 样本数据: n <- 32640 t <- seq(3*p
有没有办法找出所有可能的激活器命令行选项? activator -help仅提供最低限度的可用选项/功能列表,但所有好的东西都隐藏起来,即使在 typesafe 网站在线文档中也不可用。 到目前为止,
我是一名优秀的程序员,十分优秀!