<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【實(shí)戰(zhàn)】我是如何在輸入框?qū)崿F(xiàn) @ 功能的

          共 10257字,需瀏覽 21分鐘

           ·

          2021-12-29 05:24

          以下內(nèi)容來自公眾號逆鋒起筆,關(guān)注每日干貨及時送達(dá)

          作者:InfinityTomorrow 授權(quán)轉(zhuǎn)載

          鏈接:https://juejin.cn/post/6982251438332182542

          一、前言

          最近接手了一個需求,在評論框中實(shí)現(xiàn) @At通知用戶的功能。這個可以說是我的知識盲點(diǎn)了,但是其實(shí)很多應(yīng)用都有這類功能了,例如:QQ空間、微博搜索、企業(yè)微信的TAPD...但是一看就不想不做~??(產(chǎn)品經(jīng)理ps:為什么別人可以做你不可以做?)

          明確目標(biāo)

          ?


          二、技術(shù)方案分析

          在尋求我們的技術(shù)方案的時候、我們首先要明確我們想要的功能是什么
          你知道自己想要什么,知道要去哪兒、當(dāng)我們把需求、功能、拆解的很細(xì)的時候可以節(jié)約我們走彎路的時間(ps:不要問我怎么知道的)

          當(dāng)前需求的拆解

          1. 按住shift + @ 的時候,彈出通知列表
          2. 選擇時 @的用戶標(biāo)簽插入當(dāng)前的光標(biāo)位置中
          3. 生成@的用戶標(biāo)簽的規(guī)則是:高亮、攜帶用戶ID、一鍵刪除信息、不可以編輯。
          4. 文本框要隨內(nèi)容自適應(yīng)高度
          5. Android、IOSWeb顯示多端一致。
          6. 具有擴(kuò)張性,未來評論可能插入圖片文件等....

          市面流行方案對比

          ps: 方案有很多種方式,適合自己、適合團(tuán)隊(duì)的才是最佳實(shí)踐。沒有完美的方案(ps:只有不聽話的產(chǎn)品經(jīng)理??????) 的產(chǎn)品經(jīng)理??????)
          • textareainput(例:新浪微博)

            • 流程大概都是(監(jiān)聽keyup, 獲取光標(biāo)位置拆入@的節(jié)點(diǎn)...), 但是...相信我如果你手寫,你不會快樂的?。?!所以推薦下面的庫給大家、只要稍作改動就可以使用啦~~
            • Tribute.js(推薦, ES6)
            • At.js JQ)
          • contenteditable (例:QQ空間, 掘金)

            • HTML5新屬性規(guī)定元素內(nèi)容是否可編輯、可以做為編輯器使用,由于時間原因并沒有深入體會、感興趣的小伙伴可以看一下以下內(nèi)容
            • contenteditable-MDN
            • contenteditable實(shí)現(xiàn)編輯器,光標(biāo)、輸入法處理
            • 基于contenteditable技術(shù)實(shí)現(xiàn)@選人功能
          • 富文本 (例:企業(yè)微信TAPD)

            • 支持 文本、富文本、圖片、擁有豐富的配置與強(qiáng)大的API。
            • 因?yàn)榭紤]到擴(kuò)展性與踩坑的深淺、api的豐富程度最終選擇 wangeditor富文本 做為最終的方案。
          既然選擇好了方向,那就開沖吧、沖沖沖?。?!

          三、準(zhǔn)備工作

          本功能是基于wangeditor富文本編輯器來實(shí)現(xiàn)的,本文wangeditor版本4.3.0
          npm?i?wangeditor?--save
          初始化一下項(xiàng)項(xiàng)目結(jié)構(gòu)~
          <template>
          ????<div?ref="editor">div>
          template>

          <script>
          import?E?from?'wangeditor'
          export?default?{
          ????data()?{
          ????????return?{
          ????????????editor:?''
          ????????}
          ????},
          ????mounted()?{
          ????????this.initEditor()?//?初始化編輯器
          ????},
          ????methods:?{
          ????????initEditor()?{
          ????????????let?editor?=?new?E(this.$refs.editor)
          ????????????editor.config.placeholder?=?'寫評論~可手動輸入@通知其他人'
          ????????????editor.config.menus?=?[]?//?顯示菜單按鈕
          ????????????editor.config.showFullScreen?=?false?//?不顯示全屏按鈕
          ????????????editor.config.pasteIgnoreImg?=?true?//?如果復(fù)制的內(nèi)容有圖片又有文字,則只粘貼文字,不粘貼圖片。
          ????????????editor.config.height?=?'100'
          ????????????editor.config.focus?=?false??//?取消自動?focus
          ????????????editor.create()
          ????????????this.editor?=?editor
          ????????????//?銷毀編輯器,定義與銷毀應(yīng)該在同一個地方,增加閱讀性,方便后期維護(hù)。
          ????????????this.$once('hook:beforeDestroy',?()?=>?{?
          ????????????????this.editor.destroy()
          ????????????????this.editor?=?null
          ????????????})
          ????????}
          ????}
          }
          script>
          拓展知識:
          1. 為什么在上文中使用 ”new E(this.$refs.editor)“ 使用ref的方式而不是ID的方式呢?
          • 使用ref的好處是具有良好的可重用性和范圍。因?yàn)閞ef只留在這個組件中,所以當(dāng)您操作這個ref時,它不會干擾其他組件。
          • 如果您使用id,它就有重復(fù)的問題,這就意味著你不可能重用某個元素。
          • 例:我再生成一個富文本組件就會初始化失敗、因?yàn)閕d是唯一的。這就是為什么很多人推薦盡量少用ID的原因。(不要問我為什么知道這個問題?。?!)。
          1. wangeditor的配置只支持固定高度,如果我們想支持文本框最小高度、文字隨內(nèi)容到最大高度xx時自適應(yīng)滑動怎么做呢?

          ?editor.config.height?=?'100'
          ?
          <style?lang="scss"?scoped>
          ::v-deep?.w-e-text-container?{
          ????min-height:?100px;
          ????max-height:?300px;
          ????height:?auto?!important;
          ????border:?1px?solid?#dbdbdb?!important;
          ????border-radius:?4px;
          ????overflow-y:?auto;
          }
          style>

          四、@的功能的實(shí)現(xiàn)

          按住shift + @ 的時候,彈出通知人列表
          • 通過$event 可以獲取鍵盤的keyCode 達(dá)到監(jiān)聽的目的
          • e.preventDefault 可以阻止我輸入的@字符的默認(rèn)事件
          • getSelection 可以獲取光標(biāo)的位置、給插入標(biāo)簽一個坐標(biāo)。
          • 要兼容中文輸入法的時候@的事件判斷(如:中文輸入法打“哈哈哈@” 這個時候不能監(jiān)聽@的事件 )
          • 中文輸入法的時候單獨(dú)輸入@的時
          怎么判斷中文輸入?
          當(dāng)用戶使用中文輸入法開始輸入中文時,compositionstart事件就會被觸發(fā)。當(dāng)文中文輸入完成或取消時, compositionend 事件將被觸發(fā)。利用這個機(jī)制我們就可以判斷是否中文狀態(tài)了
          • positionstart 事件,當(dāng)用戶使用拼音輸入法開始輸入漢字時,這個事件就會被觸發(fā)。
          • compositionend 事件, 當(dāng)文中文輸入完成時, compositionend 事件將被觸發(fā)。
          <template>
          ????<div
          ????????ref="editor"
          ????????@compositionstart="compositionstart"
          ????????@compositionend="compositionend"
          ????????@keydown="onKeyDownInput($event)"
          ????>div
          >
          template>

          <script>
          export?default?{
          ????data()?{
          ????????return?{
          ????????????isChineseInputMethod:?false?//?是否中文輸入法狀態(tài)中
          ????????}
          ????},

          ????methods:?{
          ????????//?...code
          ????????//?中文輸入觸發(fā)
          ????????compositionstart()?{
          ????????????this.isChineseInputMethod?=?true
          ????????},

          ????????//?中文輸入關(guān)閉
          ????????compositionend()?{
          ????????????this.isChineseInputMethod?=?false
          ????????}
          ????}
          }
          記錄我們當(dāng)前的光標(biāo)位置
          <template>
          ????<div
          ????????ref="editor"
          ????????@keydown="onKeyDownInput($event)"
          ????????@click="onClickEditor"
          ????>div
          >
          template>
          <script>
          export?default?{
          ????data()?{
          ????????return?{
          ????????????position:?''
          ????????}
          ????},

          ????methods:?{
          ????????//?初始化編輯器
          ????????initEditor()?{
          ????????????//?...?init?code
          ????????????//?編輯的文本的時候記錄光標(biāo)。
          ????????????editor.config.onchange?=?html?=>?{
          ????????????????//?生成@的標(biāo)簽的時候會觸發(fā)渲染、此時不要記錄光標(biāo)坐標(biāo)
          ????????????????if?(this.isRendering?==?false)?{
          ????????????????????this.setRecordCoordinates()?//?記錄坐標(biāo)
          ????????????????}
          ????????????}
          ????????},
          ????????
          ????????//?每次點(diǎn)擊獲取更新坐標(biāo)
          ????????onClickEditor()?{
          ????????????this.setRecordCoordinates()
          ????????},
          ????????
          ????????//?keydown觸發(fā)事件?記錄光標(biāo)
          ????????onKeyDownInput(e)?{
          ????????????const?isCode?=?((e.keyCode?===?229?&&?e.key?===?'@')?||?(e.keyCode?===?229?&&?e.code?===?'Digit2')?||?e.keyCode?===?50)?&&?e.shiftKey
          ????????????if?(!this.isChineseInputMethod?&&?isCode)?{
          ????????????????this.setRecordCoordinates()?//?保存坐標(biāo)
          ????????????}
          ????????},
          ????????
          ????????//?獲取當(dāng)前光標(biāo)坐標(biāo)
          ????????setRecordCoordinates()?{
          ????????????try?{
          ????????????????// getSelection()?返回一個 Selection 對象,表示用戶選擇的文本范圍或光標(biāo)的當(dāng)前位置。
          ????????????????const?selection?=?getSelection()
          ????????????????this.position?=?{
          ????????????????????range:?selection.getRangeAt(0),
          ????????????????????selection:?selection
          ????????????????}
          ????????????}?catch?(error)?{
          ????????????????console.log(error,?'光標(biāo)獲取失敗了~')
          ????????????}
          ????????}
          ????}
          }
          script>
          @的功能的監(jiān)聽ps:鍵盤的@字符
          • 英文code是 50, 判斷是否按住shift + @鍵
          • 中文輸入法下標(biāo)點(diǎn)符號keyCode都是一樣的:229,推薦使用event.code或event.key作為@的判斷。
          //?editor?keydown觸發(fā)事件
          onKeyDownInput(e)?{
          ????//?@的鍵盤時間判斷
          ????const?isCode?=?((e.keyCode?===?229?&&?e.key?===?'@')?||?(e.keyCode?===?229?&&?e.code?===?'Digit2')?||?e.keyCode?===?50)?&&?e.shiftKey
          ????//?判斷狀態(tài)是否不是中文輸入法,并且監(jiān)聽到了@的事件
          ????if?(!this.isChineseInputMethod?&&?isCode)?{
          ????????//?記錄當(dāng)前文本光標(biāo)坐標(biāo)位置
          ????????this.setRecordCoordinates()?//?保存坐標(biāo)
          ????????//?打開彈窗的方法xxxx,這里就省略了
          ????????//?this.openXXX..
          ????}
          }
          說完@的事件的監(jiān)聽、現(xiàn)在我們可以聊聊怎么生成 @的標(biāo)簽了,而且 @的標(biāo)簽又是再怎么一鍵刪除的?
          • 生成@的用戶標(biāo)簽的規(guī)則是:高亮、攜帶用戶ID、一鍵刪除信息、不可以編輯
          /**
          *?數(shù)據(jù)結(jié)構(gòu):
          *?userList:?[{name:?'壞女人',?uid:?18},?{name:?'好男人',?uid:?888}]
          */


          //彈窗列表?-?選人?-?生成@的內(nèi)容
          createSelectElement(name,?id,?type?=?'default')?{
          ????//?獲取當(dāng)前文本光標(biāo)的位置。
          ????const?{?selection,?range?}?=?this.position
          ????//?生成需要顯示的內(nèi)容
          ????let?spanNodeFirst?=?document.createElement('span')
          ????spanNodeFirst.style.color?=?'#409EFF'
          ????spanNodeFirst.innerHTML?=?`@${name} `?//?@的文本信息
          ????spanNodeFirst.dataset.id?=?id?//?用戶ID、為后續(xù)解析富文本提供
          ????spanNodeFirst.contentEditable?=?false?//?當(dāng)設(shè)置為false時,富文本會把成功文本視為一個節(jié)點(diǎn)。
          ????
          ????//?需要在字符前插入一個空格否則、在換行與兩個@標(biāo)簽連續(xù)的時候?qū)е聼o法刪除標(biāo)簽
          ????let?spanNode?=?document.createElement('span');
          ????spanNode.innerHTML?=?' ';

          ????//創(chuàng)建一個新的空白的文檔片段,拆入對應(yīng)文本內(nèi)容
          ????let?frag?=?document.createDocumentFragment()
          ????frag.appendChild(spanNode);
          ????frag.appendChild(spanNodeFirst);

          ????//?如果是鍵盤觸發(fā)的默認(rèn)刪除面前的@,前文中我們沒有阻止@的生成所以要刪除@的再插入ps:如果你是數(shù)組遍歷的請傳入type 不然會一直刪除你前面的字符。
          ????if?(type?===?'default')?{
          ????????const?textNode?=?range.startContainer;
          ????????range.setStart(textNode,?range.endOffset?-?1);
          ????????range.setEnd(textNode,?range.endOffset);
          ????????range.deleteContents();
          ????????this.isKeyboard?=?false?//?針對多選的邏輯
          ????}
          ????
          ????//?判斷是否有文本、是否有坐標(biāo)
          ????if?((this.editor.txt.text()?||?type?===?'default')&&?this.position?&&?range)?{
          ????????range.insertNode(frag)
          ????}?else?{
          ????????//?如果沒有內(nèi)容一開始就插入數(shù)據(jù)特別處理
          ????????this.editor.txt.append(`${id}"?style="color:?#409EFF"?contentEditable="false">@${name} `)
          ????}
          },
          擴(kuò)展知識:
          • getSelection() 表示用戶選擇的文本范圍或光標(biāo)的當(dāng)前位置。
          • Event.returnValue 兼容IE取消默認(rèn)事件
          到現(xiàn)在我們的核心功能已經(jīng)完成了。通過@人的監(jiān)聽事件,通過我們自定義的標(biāo)簽插入,通過getSelection獲取到的光標(biāo)位置。我就就可以做到:隨時@ 隨時插入的功能拉~

          五、Android、IOS、Web顯示多端一致

          每個端使用富文本都是不一樣的、那我們應(yīng)該如何做到統(tǒng)一數(shù)據(jù)統(tǒng)一呢?
          • 現(xiàn)在采取的方案是通過解析富文本內(nèi)容生成評論數(shù)組列表。
          • 通過各端解析數(shù)組列表、生成富文本...
          • 兼容換行字符...
          • 雖然不能做到完全統(tǒng)一但是能做到數(shù)據(jù)至少是一致的(現(xiàn)在又覺得、textarea、inpu方案的好了)
          /**
          *?例?富文本:?

          @小明?
          ?喂三點(diǎn)幾拉?@飲茶哥?出來飲茶
          *?生成數(shù)組:?
          *?[
          *????{segment:?'@小明?\n',?userId:?'idxxxxx'},
          *????{segment:?'喂三點(diǎn)幾拉',?userId:?null},
          *????{segment:?'@飲茶哥',?userId:?'idxxxx'},
          *????{segment:?'出來飲茶',?userId:?null}
          *?]
          */

          //?解析編輯器富文本?生成文本信息數(shù)組
          fetchGenerateContentsArray()?{
          ????//?獲取編輯器的JSON對象
          ????const?data?=?this.editor.txt.getJSON()
          ????
          ????let?contents?=?[]
          ????//?解析html列表JSON,生成文本對象。
          ????const?generateArray?=?nodeList?=>?{
          ????????if?(Array.isArray(nodeList))?{
          ????????????nodeList.forEach(item?=>?{
          ????????????????//?對于換行符號處理?處理
          ?

          等標(biāo)簽保障顯示一致性
          ????????????????if?(item?&&?item.tag)?{
          ????????????????????//?針對富文本列表是特殊換行處理?{tag:p,?children:?[{tag:?'br'}]}?這件換行過濾
          ????????????????????const?notSpecialLabel?=?item.tag?==?'p'?&&?item.children[0]?&&?item.children[0].tag?==?'br'
          ????????????????????if?(!notSpecialLabel?&&?['p',?'br'].includes(item.tag)?&&?contents.length)?{
          ????????????????????????const?index?=?contents.length?-?1
          ????????????????????????//?在文本中拆入換行符號兼容android、ios的換行字符
          ????????????????????????contents[index].segment?+=?'\n'?
          ????????????????????}
          ????????????????}
          ????????????????
          ????????????????//??如果遍歷的屬性是?data-id?有ID的
          ????????????????if?(item?&&?item.attrs?&&?item.attrs.find(e?=>?e.name?===?'data-id'))?{
          ????????????????????const?id?=?item.attrs.find(e?=>?e.name?===?'data-id').value
          ????????????????????const?content?=?item.children?&&?item.children[0]?||?''
          ????????????????????contents.push({?segment:?content.replaceAll(/(
          )|()/g
          ,?''),?userId:?id?})
          ????????????????????return
          ????????????????}
          ????????????????
          ????????????????//?如果children?是數(shù)組則繼續(xù)遞歸遍歷下一層
          ????????????????if?(Array.isArray(item.children))?{
          ????????????????????generateArray(item.children)
          ????????????????????return
          ????????????????}
          ????????????????
          ????????????????//?如果沒有數(shù)組了、就是文本的內(nèi)容
          ????????????????//?刪除文本中的?
          ?字符

          ????????????????if?(item.trim())?{
          ????????????????????contents.push({segment:?item.replaceAll(/(
          )|()/g
          ,?''),?userId:?''})
          ????????????????}
          ????????????})
          ????????}
          ????}
          ????generateArray(data)
          ????return?contents
          }

          將生成的數(shù)組解析成為富文本
          //?生成換行符號
          createLineBreaks(str,?target)?{
          ????let?label?=?[]
          ????while(str.match(target)){
          ????????str?=?str.replace(target,'')
          ????????label.push('
          '
          )
          ????}
          ????return?label.join('')
          },

          //?解析數(shù)組內(nèi)容?生成富文本
          createCommentHtml(data)?{
          ????//?生成@的標(biāo)簽
          ????const?anchorPoint?=?(id,?value)?=>?{
          ????????let?defaultSpan?=?` ${id}"?style="color:?#409EFF"?contentEditable="false">${value}`
          ????????//?判斷android?ios的換行符,替換為富文本的?

          ????????if?(/(\r\n)|(\n)/g.test(value))?{
          ????????????value.replaceAll(/(\r\n)|(\n)/g,?'')
          ????????????defaultSpan?=?defaultSpan?+?this.createLineBreaks(value,?'\n')
          ????????}
          ????????return?defaultSpan
          ????}
          ????
          ????//?將處理的文本放入數(shù)組中、通過join生成字符串。
          ????const?createHtml?=?[]
          ????data?&&?data.forEach(item?=>?{
          ????????//?如果有id用?錨點(diǎn)樣式
          ????????if?(item.userId)?{
          ????????????const?json?=?anchorPoint(item.userId,?item.segment)
          ????????????createHtml.push(json)
          ????????}?else?{
          ????????????createHtml.push(item.segment.replaceAll(/(\r\n)|(\n)/g,?''))
          ????????}
          ????})
          ????//?清除文本數(shù)據(jù)
          ????this.resetQuery()
          ????//?生成內(nèi)容插入到edito中
          ????this.editor.txt.html(`

          ${createHtml.join('')}

          `
          )
          },

          //?清除文本數(shù)據(jù)
          resetQuery()?{
          ????this.editor.txt.clear()
          }

          六. 獲取光標(biāo)的坐標(biāo)在文本中的位置

          caret-postextarea、contentedtiableiframe 正文中獲取插入符號/光標(biāo)的位置/偏移量
          import?{?position,?offset?}?from?'caret-pos'
          //?獲取當(dāng)前光標(biāo)位置
          getPosition?()?{
          ??const?ele?=?this.editor.$textElem.elems[0]
          ??const?pos?=?position(ele)
          ??const?off?=?offset(ele)
          ??const?parentW?=?ele.offsetWidth
          ??//?這個是彈窗列表
          ??const?childEle?=?document.getElementsByClassName("userPopupList")
          ??const?childW?=?childEle.offsetWidth
          ??//?彈框偏移超出父元素的寬高
          ??if?(parentW?-?pos.left?????this.left?=?off.left?-?childW
          ??}?else?{
          ????this.left?=?off.left
          ??}
          ??this.top?=?off.top?+?20
          }
          <div?class="userPopupList"?:style="{left:?left?+?'px',?top:?top?+?'px'}">
          ????...?you?@?popup?list
          div>

          七、總結(jié)

          不要放棄探尋、探究問題的本質(zhì)。不要小看那些看似“無用”的知識、如果這份只是曾經(jīng)擺在你的面前你沒有拒絕它、此時你的學(xué)習(xí)成本又該降低多少呢?
          這個功能只是在開發(fā)中擠出來的、很多東西寫的不夠好、不夠完善,希望本文能幫助您在開發(fā)中節(jié)約一點(diǎn)時間。也歡迎大家提出踴躍的反饋、希望能與大家共進(jìn)步,加油~

          作者:InfinityTomorrow 授權(quán)轉(zhuǎn)載 鏈接:https://juejin.cn/post/6982251438332182542

          逆鋒起筆專注于程序員圈子,你不但可以學(xué)習(xí)到java、python等主流技術(shù)干貨,還可以第一時間獲悉最新技術(shù)動態(tài)、內(nèi)測資格BAT大佬的經(jīng)驗(yàn)、精品視頻教程、副業(yè)賺錢經(jīng)驗(yàn),微信搜索readdot關(guān)注!

          送你 10 個盤,速度比某網(wǎng)盤快 100 倍!

          Vue3 相比于 Vue2 有哪些 “與眾不同”?

          MySQL、Redis、MongoDB 網(wǎng)絡(luò)抓包工具

          如何搭建一臺永久運(yùn)行的個人服務(wù)器?

          Linux 中的 13 個基本 cat 命令示例


          ???“分享、點(diǎn)贊、在看” 支持一波??
          瀏覽 131
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本成人片在线免费观看 | 四虎精品| 大香蕉操B | 超碰97人妻 | 超碰免费国产 |