需求:
1峭竣、 鼠標(biāo)選中文本后,為選中的文本添加背景色晃虫。并在選中的文本上增加標(biāo)簽皆撩。
2、 生成的文本標(biāo)簽有兩種哲银,不同類型的標(biāo)簽可以相互拖拽行成關(guān)系扛吞,并形成連線,連線后增加關(guān)系標(biāo)簽荆责。
3喻粹、點(diǎn)擊標(biāo)簽可以查看到當(dāng)前點(diǎn)擊的標(biāo)簽指向哪個(gè)文本內(nèi)容,關(guān)系標(biāo)簽也是如此
實(shí)現(xiàn)工具:
1草巡、JQ
2守呜、svg
實(shí)現(xiàn)思路
前期:乍一看這個(gè)功能的實(shí)現(xiàn)其實(shí)還挺簡單,但是在實(shí)現(xiàn)過程中山憨,由于之前過分依賴vue框架查乒,導(dǎo)致對(duì)原生js以及Jq的一些api有些不熟悉。
在實(shí)現(xiàn)過程中斷斷續(xù)續(xù)出現(xiàn)了很多問題郁竟。
1玛迄、如何為選中的文本添加背景色
這里有兩種實(shí)現(xiàn)思路:
-
根據(jù)鼠標(biāo)選中的坐標(biāo),再使用svg進(jìn)行畫圖棚亩。
弊端:我前期是使用這種方法蓖议,但是這種方法根據(jù)鼠標(biāo)點(diǎn)擊的位置來獲取x,y的坐標(biāo)后再去畫圖的虏杰,這就意味一個(gè)問題。鼠標(biāo)的點(diǎn)擊位置是不統(tǒng)一的導(dǎo)致標(biāo)簽不對(duì)齊問題勒虾。(找不到解決思路)
image.png -
根據(jù)選中的文本位置纺阔,為文本增加span標(biāo)簽,再給span標(biāo)簽加上樣式修然。這種通過添加節(jié)點(diǎn)的方式笛钝,解決了我們的標(biāo)簽不對(duì)齊問題。
image.png
但這里面還有很多問題愕宋。后面呈現(xiàn)代碼時(shí)分析玻靡。
2、如何畫線
svg畫線就是根據(jù)坐標(biāo)進(jìn)行畫線的中贝,所以我們要拿到兩個(gè)標(biāo)簽的位置進(jìn)行連線囤捻。
他有兩種情況,第一種是同一行標(biāo)簽上連線邻寿。第二行是跨行連線最蕾。
這里沒什么需要特別注意的。
代碼實(shí)現(xiàn)
<div class="txtBox">
<div id="1" data-num="1">【性狀】本品為腸溶衣片老厌,除去腸溶衣后顯類白色或淡黃色瘟则。</div>
</div>
// 選中文本
let text = ''; // 獲取選中的文本
let range = ''; // 獲取選中的文本對(duì)象
let beginTxt;
// 文本結(jié)束位置
let endTxt;
// 當(dāng)前行數(shù)
let rowNum;
// 鼠標(biāo)事件
$(".txtBox").mouseup(function(e){
text = ''
range = ''
// 獲取屏幕的寬度
let clientWidth = document.body.clientWidth;
// 這個(gè)方法可以獲取當(dāng)前鼠標(biāo)選中的文本
txt = window.getSelection();
// 獲取到當(dāng)行選中的文本開始索引和結(jié)束索引
beginTxt = txt.anchorOffset;
endTxt = txt.focusOffset;
var parentOffset = $(this).offset();
if(txt.type!=='None'){
// range 對(duì)象是我們當(dāng)前選中的文本對(duì)象
// 有了這個(gè)對(duì)象,我們可以為選中的文本添加標(biāo)簽
range = txt.getRangeAt(0);
if (txt.toString().length >= 1) {
text = txt.toString();
}
}
})
這里只貼關(guān)鍵代碼
選中了文本對(duì)象后枝秤,我是通過一個(gè)點(diǎn)擊事件后才進(jìn)行標(biāo)簽生成的醋拧。所以在前面
記錄了range對(duì)象。選中文本后我們要生成的標(biāo)簽有兩個(gè)淀弹。
// 隨機(jī)生成一個(gè)id丹壕,因?yàn)橐傻臉?biāo)簽有兩個(gè),后面我們有刪除功能
// 根據(jù)同一個(gè)id來刪除薇溃,當(dāng)然我們生成標(biāo)簽是不能重復(fù)id,所以有一個(gè)標(biāo)簽
// 用class來記錄
let id = Number(Math.random().toString().substr(3,length) + Date.now()).toString(36)
// 這里面有些自定義屬性始根據(jù)我需求來的菌赖,關(guān)鍵的就class和style
var newNode = document.createElement("span");
newNode.setAttribute("class",`${id}`);
newNode.setAttribute("style",`background:${bgColor}`);
newNode.setAttribute("data-type",`${type}`);
newNode.setAttribute("data-bg",`${bgColor}`);
newNode.setAttribute("data-coverText",`${text}`);
newNode.setAttribute("data-rowNum",`${rowNum}`);
// 這個(gè)方法就可以為文本添加標(biāo)簽
range.surroundContents(newNode);
目前已經(jīng)實(shí)現(xiàn)到這里了
但是這里有一個(gè)比較嚴(yán)重的問題。我們目前是選中文本之后使用js的getSelection內(nèi)置方法才得到range對(duì)象的沐序。如果我們之后要回顯琉用,是沒有選中文本這一說的。我們應(yīng)該怎么記錄選中后的文本呢策幼,而且準(zhǔn)確的在那個(gè)文本里增加標(biāo)簽邑时。
其實(shí)我一開始的思路是直接使用選中的文本和當(dāng)前的行數(shù)的文本做配對(duì)。但是如果一行內(nèi)有多個(gè)相同的文本特姐,這種方法行不通
所以還是要精準(zhǔn)的方式晶丘,獲取到這一行里面選中文本的當(dāng)前索引。然后用自定義屬性保存當(dāng)前文本的開始和結(jié)束位置
// 前面已經(jīng)實(shí)現(xiàn)了。
beginTxt = txt.anchorOffset;
endTxt = txt.focusOffset;
但是考慮到之前的是純文本浅浮,所以選中文本時(shí)肯定可以拿到索引沫浆。如果這個(gè)文本已經(jīng)加過標(biāo)簽了。
我已經(jīng)<span>加過標(biāo)簽了</span>了滚秩,已經(jīng)不是純文本专执。
那么getSelection就不會(huì)幫你算的這么準(zhǔn)了。它變成這樣計(jì)算了叔遂,也就是跳過了前面的標(biāo)簽他炊。因?yàn)橐恍锌梢杂泻芏鄠€(gè)標(biāo)簽争剿,所以做了一個(gè)循環(huán)計(jì)算當(dāng)前的文本索引
// 隨機(jī)生成一個(gè)id已艰,因?yàn)橐傻臉?biāo)簽有兩個(gè),后面我們有刪除功能
// 根據(jù)同一個(gè)id來刪除蚕苇,當(dāng)然我們生成標(biāo)簽是不能重復(fù)id,所以有一個(gè)標(biāo)簽
// 用class來記錄
let id = Number(Math.random().toString().substr(3,length) + Date.now()).toString(36)
// 這里面有些自定義屬性始根據(jù)我需求來的哩掺,關(guān)鍵的就class和style
var newNode = document.createElement("span");
// 原本應(yīng)該在這里就添加文本的開始和結(jié)束索引
newNode.setAttribute("class",`${id}`);
newNode.setAttribute("style",`background:${bgColor}`);
newNode.setAttribute("data-type",`${type}`);
newNode.setAttribute("data-bg",`${bgColor}`);
newNode.setAttribute("data-coverText",`${text}`);
newNode.setAttribute("data-rowNum",`${rowNum}`);
// 標(biāo)簽還是要加,不過在判斷后加
//range.surroundContents(newNode);
// 首先我的文本結(jié)構(gòu)是做成這樣的
//看下圖涩笤,并且定義一個(gè)mousedown事件記錄當(dāng)前點(diǎn)擊的是第幾行
// 使用 rowNum 來記錄
if(rowNum){
// 標(biāo)簽還是要加的嚼吞,我們要改變的只是要獲取開始和結(jié)束
// 目前的div結(jié)構(gòu)可能是這樣的
// <div id="1">我是<span>標(biāo)簽1</span>我<span>標(biāo)簽</span>我不是</div>
range.surroundContents(newNode);
// div的id要和行數(shù)一致
var divName=document.getElementById(rowNum);
// 記錄當(dāng)前的文本在哪個(gè)位置
let strLength = 0;
//判斷在此之前有無添加過標(biāo)簽
if(divName.childNodes){
for(let i = 0; i<divName.childNodes.length; i++){
if(divName.childNodes[i].nodeType===3){
strLength += divName.childNodes[i].length;
}
if(divName.childNodes[i].nodeType===1&&divName.childNodes[i].class!==id){
strLength += divName.childNodes[i].dataset.covertext.length;
}
if(divName.childNodes[i].class===id){
beginTxt = strLength;
endTxt = strLength+text.length
break;
}
}
}
$(`.${id}`).attr('data-beginTxt', beginTxt);
$(`.${id}`).attr('data-endTxt', endTxt);
// 獲取到標(biāo)簽的位置,新增第二個(gè)標(biāo)簽
let heigth = $(`.${id}`).offset().top+ $(".contentBox").scrollTop()+10;
let spanX = downX+$(".contentBox").scrollLeft();
let clientWidth = document.body.clientWidth;
let candrag = e.target.dataset.type==="ingredient"?true:false;
let width = 16*(e.target.innerHTML.length);
// 添加標(biāo)簽
let spanElement = `
<span
id='${id}'
data-covertext='${text}'
data-type='${type}'
class='labelCategory'
style='background:${bgColor};
width:${width}px'
draggable="${candrag}"
ondragstart="drag(event)"
ondragover="allowDrop(event)"
ondrop="drop(event)">
${ e.target.innerHTML }
</span>`;
$(`.${id}`).append(spanElement)
$(".txt_dialog").hide();
}
}
$(".txtBox").mousedown(function(e){
rowNum = e.target.dataset.num;
downX = e.pageX;
})
這樣就能拿到文本的索引值了蹬碧,當(dāng)然我們這里不考慮換行舱禽。
畫線
我覺得這里沒什么好講的,就是獲取兩個(gè)標(biāo)簽的索引和位置恩沽,判斷高度誊稚,如果同一高度證明在同一行,否則跨行而已罗心。代碼僅供參考
<!--畫圖面板-->
<svg class="svgBox" width='100%' height='1000px' xmlns='http://www.w3.org/2000/svg'>
<g id='whiskers'></g>
</svg>
function draw(currentSpanId,dragDownSpan,height,height2,left,left2,text1,text2){
var mysvg = document.getElementById("whiskers");
var rectObj = document.createElementNS("http://www.w3.org/2000/svg","polyline");
if(rectObj){
// 用來記錄線條里伯,到時(shí)可以刪除
let randomIndex = Math.round(Math.random()*100);
let lineClassName = Number(Math.random().toString().substr(3,length) + Date.now()).toString(36) +"b" + randomIndex
let lineClassName2 = lineClassName+'two'
// 如果跨行
if(height!==height2){
// 第一條
rectObj.setAttribute("points",`
${left} ${height-5},
${left+10} ${height-15},
${left+3000} ${height-15}`);
rectObj.setAttribute("style","stroke:black; fill:none");
mysvg.appendChild(rectObj);
// 第二條
var rectObj2 = document.createElementNS("http://www.w3.org/2000/svg","polyline");
rectObj2.setAttribute("points",`
0 ${height2-15},
${left2-10} ${height2-15},
${left2} ${height2-5}`);
rectObj2.setAttribute("style","stroke:black; fill:none");
rectObj.setAttribute("class",lineClassName);
rectObj2.setAttribute("class",lineClassName2);
mysvg.appendChild(rectObj2);
}else{
rectObj.setAttribute("points",`${left} ${height-5},${left+10} ${height-15}, ${left2-5} ${height2-15}, ${left2} ${height2-5}`);
rectObj.setAttribute("style","stroke:black; fill:none");
rectObj.setAttribute("class",lineClassName);
mysvg.appendChild(rectObj);
}
$('.txt_dialog').hide();
relationTag(currentSpanId,dragDownSpan,left,height,left2,height2,text1,text2,lineClassName,lineClassName2);
}
}
保存的數(shù)據(jù)結(jié)構(gòu)
回顯問題
1、標(biāo)簽的回顯我覺得很麻煩渤闷。也是和之前的問題一樣疾瓮。一行可能有多個(gè)標(biāo)注。
標(biāo)注之后會(huì)有標(biāo)簽飒箭,加了標(biāo)簽之后就不是純文本了狼电,所以又要計(jì)算。
這里直接貼出代碼弦蹂。
function created () {
let str = '[{"class":"kf7oxv4ic58","name":"反復(fù)發(fā)","row":"0","type":"ingredient","relationId":"kf7oxy5vc98","beginTxt":"9","endTxt":"12","bg":"#ef5353"},{"class":"kf7oxy5vc98","name":"咳嗽","row":"1","type":"effect","beginTxt":"38","endTxt":"40","bg":"#f1ae45"},{"class":"kf7oy470c48","name":"病重","row":"1","type":"ingredient","relationId":"kf7oy6tmc40","beginTxt":"68","endTxt":"70","bg":"#ef5353"},{"class":"kf7oy6tmc40","name":"睡眠漫萄、胃納稍差","row":"1","type":"effect","beginTxt":"99","endTxt":"106","bg":"#f1ae45"},{"class":"kf7oyf67c38","name":"耳: 外形","row":"13","type":"ingredient","relationId":"kf7oyh1kc81","beginTxt":"0","endTxt":"5","bg":"#ef5353"},{"class":"kf7oyh1kc81","name":"鼻竇壓","row":"14","type":"effect","beginTxt":"25","endTxt":"28","bg":"#f1ae45"}]'
str = JSON.parse(str);
let lineArr = [];
let setLine = [];
// 這里是用來畫線的,一個(gè)標(biāo)簽可以和多個(gè)標(biāo)簽有關(guān)系
for(let i = 0; i<str.length; i++){
if(str[i].relationId&&str[i].relationId.length>0){
setLine.push(str[i].class,str[i].relationId)
setLine = Array.from(new Set(setLine))
}
// 這里開始才是回顯標(biāo)注的文本的
var newNode = document.createElement("span");
newNode.setAttribute("class",`${str[i].class}`);
newNode.setAttribute("style",`background:${str[i].bg}`);
newNode.setAttribute("data-type",`${str[i].type}`);
newNode.setAttribute("data-bg",`${str[i].bg}`);
newNode.setAttribute("data-coverText",`${str[i].name}`);
newNode.setAttribute("data-beginTxt",`${str[i].beginTxt}`);
newNode.setAttribute("data-endTxt",`${str[i].endTxt}`);
newNode.setAttribute("data-rowNum",`${str[i].row}`);
newNode.setAttribute("data-relationId",`${str[i].relationId}`);
var divName=document.getElementById(str[i].row);
var content;
// 判斷在此之前有無添加過標(biāo)簽
let strLength = 0;
// 判斷是文本節(jié)點(diǎn)還是元素節(jié)點(diǎn)
if(divName.childNodes.length>1){
for(let j = 0; j<divName.childNodes.length; j++){
if(divName.childNodes[j].nodeType===3){
strLength += divName.childNodes[j].length;
}else if(divName.childNodes[j].nodeType===1){
strLength += divName.childNodes[j].dataset.covertext.length;
}
if(str[i].beginTxt<strLength&&divName.childNodes[j].className!==str[i].class){
let strToTalLenght = divName.childNodes[j].length;
let num1 = (strToTalLenght - (strLength - str[i].beginTxt));
let num2 = num1 + str[i].name.length;
if(num1>0&&num2>num1){
content = divName.childNodes[j];
let range = document.createRange();
range.setStart(content,num1);
range.setEnd(content,num2);
range.surroundContents(newNode);
createdTag(str[i])
}
}
}
}else{
content= divName.firstChild;
let range = document.createRange();
range.setStart(content,str[i].beginTxt);
range.setEnd(content,str[i].endTxt);
range.surroundContents(newNode);
createdTag(str[i])
}
}
// 畫線
createdLine(setLine)
}