實(shí)現(xiàn)標(biāo)注文本

需求:

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)簽也是如此

image.png
image.png
image.png

實(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è)淀弹。


image.png
// 隨機(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)到這里了


image.png

image.png

但是這里有一個(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)前的文本索引


image.png
// 隨機(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;
    })
文本的結(jié)構(gòu)

這樣就能拿到文本的索引值了蹬碧,當(dāng)然我們這里不考慮換行舱禽。


image.png
image.png

畫線

我覺得這里沒什么好講的,就是獲取兩個(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)

image.png

回顯問題

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)
  }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末盈匾,一起剝皮案震驚了整個(gè)濱河市腾务,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌削饵,老刑警劉巖岩瘦,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件未巫,死亡現(xiàn)場離奇詭異,居然都是意外死亡启昧,警方通過查閱死者的電腦和手機(jī)叙凡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來密末,“玉大人握爷,你說我怎么就攤上這事⊙侠铮” “怎么了新啼?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長刹碾。 經(jīng)常有香客問我燥撞,道長,這世上最難降的妖魔是什么迷帜? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任物舒,我火速辦了婚禮,結(jié)果婚禮上戏锹,老公的妹妹穿的比我還像新娘冠胯。我一直安慰自己,他們只是感情好锦针,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布荠察。 她就那樣靜靜地躺著,像睡著了一般伞插。 火紅的嫁衣襯著肌膚如雪割粮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天媚污,我揣著相機(jī)與錄音舀瓢,去河邊找鬼。 笑死耗美,一個(gè)胖子當(dāng)著我的面吹牛京髓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播商架,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼堰怨,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了蛇摸?” 一聲冷哼從身側(cè)響起备图,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后揽涮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抠藕,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年蒋困,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了盾似。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡雪标,死狀恐怖零院,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情村刨,我是刑警寧澤告抄,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站烹困,受9級(jí)特大地震影響玄妈,放射性物質(zhì)發(fā)生泄漏乾吻。R本人自食惡果不足惜髓梅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望绎签。 院中可真熱鬧枯饿,春花似錦、人聲如沸诡必。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽爸舒。三九已至蟋字,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間扭勉,已是汗流浹背鹊奖。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留涂炎,地道東北人忠聚。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像唱捣,于是被迫代替她去往敵國和親两蟀。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355