JS實現(xiàn)@功能


最近公司的PC端即時通訊工具需要添加@功能莉测,整體軟件采用的是Electron+Node.js來編寫的熙涤,其實功能并不難惋砂,困難的是前段界面上的體驗,說白了就是怎樣利用js來實現(xiàn)@功能厢洞。

為了做這個也是踩了不少的坑笛匙,目前來說界面已經(jīng)做得差不多了吧,也算能用犀变,還是總結(jié)一下吧,對別人來講秋柄,總歸是有點用的获枝。

@之前.png
@之后.png

整個界面的CSS樣式還是很好寫的,這里就不再詳述了骇笔,首先遇到的一個問題就是輸入@的時候彈出一個@列表省店,像圖一那樣的,雖然樣式丑了些笨触,但是功能算是實現(xiàn)了懦傍。

彈出一個窗口還是很簡單的,無非就是一個div的顯示隱藏嗎芦劣,但最主要的是彈出div的位置粗俱,要緊挨著@字符,也就是如何計算@字符的位置虚吟,這是遇到的第一個難題寸认。

通過萬能的Google签财,我發(fā)現(xiàn)了Caret.js這個庫,下面是作者的描述

Get caret position or offset from inputor
This is the core function that working in At.js.Now, It just become an simple jquery plugin so that everybody can use it.And, of course, At.js is using this plugin too.
support iframe context

描述中還提到了At.js這個玩意兒偏塞,一看名字就能夠想到這肯定就是專門為@功能做的一個插件唱蒸,事實也確實是這樣的,這個庫很強大灸叼,基本上幾行代碼就實現(xiàn)了一個@功能神汹,但是我并沒有直接用這個庫,而是只用了Caret.js這個庫來進行定位古今,為啥不直接用At.js呢屁魏,簡單粗暴,省時省力沧卢。
說實話蚁堤,我確實試了試,結(jié)果不容樂觀但狭,出現(xiàn)了幾個錯誤披诗,在簡單嘗試解決未果之后,果斷決定自己做一個立磁,然后就是呈队,自己走的路,跪著也要走完唱歧。宪摧。。欲哭無淚
好了颅崩,不多說了几于,上代碼

<!-- 整個聊天界面的布局 -->
<div class="chat-record-area"></div>
<div class="chat-drag-area"></div>
<div class="chat-function-area">
    <a href="#" id="smile" data-placement="vertical">
        ![](../../assets/icon/face.png)
    </a>
    <a href="#" id="upload-file" data-placement="vertical">
        ![](../../assets/icon/folder.png)
    </a>
    ![](../../assets/icon/record.png)
    <div id="smile-container" style="display:none; width: 480px;"></div>
</div>
<div id="at-container"><ul></ul></div>
<div id="chat-input-area" contenteditable="true"></div>

其中,at-container就是@彈出框的div沿后,整個輸入框采用的是div沿彭,然后設(shè)置contenteditable屬性為true,這樣就可以實現(xiàn)一個簡單的輸入框效果了尖滚,但是這還只是一個渣渣喉刘,啥都不能干,還得用現(xiàn)成的基于contenteditable的編輯器才行漆弄,我這里選擇了wysiwygjs這個編輯器睦裳,雖然坑也挺多,但是用起來還可以撼唾。


// 初始化輸入框
editor = wysiwyg({
    element: 'chat-input-area', // or: document.getElementById('chat-input-area')
    onKeyPress: function (key, character, shiftKey, altKey, ctrlKey, metaKey) {
                
        // 只要有按鍵廉邑,就關(guān)閉@彈窗
        $('#at-container').css('display', 'none');
                
        // 這里省略一坨代碼
        if (character === '@') {

            // 獲取當(dāng)前光標(biāo)的位置信息
            let offset = $('#chat-input-area').caret('offset');
            let position = $('#chat-input-area').caret('position');
            // console.log(offset);
            // console.log(position);

            let editorWidth = $('#chat-input-area').width();
            let atWidth = $('#at-container').width();
            // console.log('editor width = ' + editorWidth);
            // console.log('at div width = ' + atWidth);
            
            // 當(dāng)右側(cè)的空間不夠顯示@彈窗時,顯示在左側(cè)
            // 所以需要對右側(cè)剩余空間的大小進行判斷
            if (editorWidth - position.left < atWidth) {
                $('#at-container').css('left', offset.left - atWidth);
            } else {
                $('#at-container').css('left', offset.left + 20);
            }
            $('#at-container').css('top', offset.top - 185);
            $('#at-container').css('display', 'block');
            $('#at-container').css("position", "absolute");
            $('#at-container').scrollTop(0);
        }
    }
});

這樣,就解決了彈窗的位置問題鬓催,其他問題也就出現(xiàn)了肺素,比如說點擊非彈窗區(qū)域自動關(guān)閉彈窗,按刪除鍵刪除@時宇驾,自動關(guān)閉彈窗倍靡,wysiwyg編輯器的onKeyPress事件大部分按鍵都能夠監(jiān)聽,但是Backspace(或delete)鍵監(jiān)聽不到课舍,原因沒有細查塌西,只能額外的去監(jiān)聽Backspace(或delete)鍵,然后關(guān)閉彈窗筝尾。


// 按下Backspace(或delete)鍵時
$('#chat-input-area').keyup(function (e) {
    if (process.platform === 'darwin') {
        if (e.keyCode == 8) {
            $('#at-container').css('display', 'none');
        }
    } else {
        if (e.keyCode == 46) {
            $('#at-container').css('display', 'none');
        }
    }
});

// 點擊@窗體外的區(qū)域時捡需,關(guān)閉@彈窗
$(document).mouseup(function(e) { 
    var pop = $('#at-container');    
    if(!pop.is(e.target) && pop.has(e.target).length === 0) { 
        $('#at-container').css('display', 'none');
    }  
}); 

完成之后,整個彈窗的位置和顯示問題基本上已經(jīng)解決了筹淫,當(dāng)然站辉,@彈窗里面的內(nèi)容加載以及布局都比較容易,這里就不再詳述了损姜。

點擊@彈窗里面的列表時饰剥,需要在下邊的輸入框中顯示@了某人,這里就遇到了一個問題摧阅,一般來說汰蓉,你@了某人,那這個 @+用戶名 應(yīng)該算作一個整體棒卷,一個塊顾孽,刪除的話需要整體刪除,所以輸入框中的內(nèi)容不能簡單的是 @+用戶名 這種寫法比规,需要做某種處理才行若厚。
可以參考下面這篇博文,講的很清楚蜒什,遇到的各種問題都有對應(yīng)的解決辦法盹沈,就是有些復(fù)雜。

js實現(xiàn)@提到好友

由于我自己所用的環(huán)境是Electron吃谣,只是基于Chromium引擎,所以需不要考慮Firefox以及IE和Edge瀏覽器做裙,瞬間感覺輕松了很多岗憋。

當(dāng)你輸入了@之后,彈窗才會顯示锚贱,這時@字符已經(jīng)顯示在輸入框中了仔戈,而我們要做的就是當(dāng)選擇了@對象之后,需要在輸入框中插入@的數(shù)據(jù),這個數(shù)據(jù)作為一個整體监徘,是可以整體被刪除的晋修,看了上面的那篇博文之后,你會知道凰盔,需要在輸入框中插入<button contenteditable="false">@someone</button>這樣的代碼墓卦,contenteditable="false"可以保證button不會被編輯,即可以實現(xiàn)整體刪除的效果户敬,但是隨之而來的是兩個問題:

  1. 由于@已經(jīng)輸入了落剪,需要被刪除掉,否則會出現(xiàn) @@someone這種情況
  2. document.execCommand('insertHTML', false, '<button contenteditable="false" onclick="return false;" class="at" >@${name}</button>')
    這種寫法插入html之后尿庐,contenteditable="false"會被Chromium自動過濾掉忠怖,也就是最終插入的數(shù)據(jù)中是沒有該屬性的,這就會導(dǎo)致這個button中的內(nèi)容可以被編輯抄瑟,這可能是Chromium的一個bug吧

針對上面的兩個問題凡泣,有人會說,那我直接把@someone中的someone用button包裹起來不得了嗎皮假,這樣就不會去刪除@了鞋拟,這樣也是可以的,但是如果這樣做的話钞翔,就會出現(xiàn)其他體驗上的問題严卖,比如說你刪掉someone之后,只剩下了@布轿,那@彈窗應(yīng)該會立即彈出哮笆,要不然你就沒辦法再次觸發(fā)彈窗了,只能將@手動刪除汰扭,然后再輸入稠肘,而且如果你@完之后,將輸入框焦點點到@字符后面萝毛,要不要重新彈出呢项阴,如果重新彈出的話,那之前已經(jīng)@成功的那個呢笆包,要不要取消掉呢环揽,隨之而來的是一系列的問題,倒不如直接將@someone作為一個整體庵佣,要刪一起刪歉胶,要留一起留,這樣整體簡單了很多巴粪,不用考慮太多的情況通今。

針對第一種情況的解決方案:

// @完之后自動刪除@字符
if (window.getSelection) {
        let range = window.getSelection();
        if (range.rangeCount > 0) {
            // let sel = range.getRangeAt(0);
            // let startOffset = sel.startOffset;
            let startOffset = range.extentOffset;
            // if (range.extentNode) {
            range.extentNode.replaceData(startOffset - 1, 1, '');
            // } else if(range.anchorNode) {
            // range.anchorNode.replaceData(startOffset - 1, 1, '');
            // }
        }
    }

第二種情況的解決方案:

// 插入html之后粥谬,找到該節(jié)點,添加contenteditable屬性
let html = `<button contenteditable="false" onclick='return false;' class="at" data-id="${id}">@${name}</button>`;
document.execCommand('insertHTML', false, html)
$(`#chat-input-area button[data-id="${id}"]`).attr('contenteditable', false)

但是解決完這兩個問題后辫塌,最頭痛的問題又來了:@完之后漏策,輸入框焦點不見了,這個問題如果你看了上面提到的博客之后應(yīng)該會有了解臼氨,由于我比較懶掺喻,也沒有去下載他的demo來看,只是單純的看博文一也,所以代碼片段上有些斷片巢寡,在嘗試了他的方案之后,問題還是沒有解決椰苟,怎么辦抑月,自己想辦法吧。

問題的原因就在于Chromium只會在文本中才會顯示焦點舆蝴,如果你輸入框里面只輸入純文本谦絮,那沒問題,插入了button洁仗,而且button還不能被編輯层皱,能顯示焦點就怪了,無論你怎么點赠潦,焦點都不會出現(xiàn)叫胖,除非你再輸入純文本,焦點才會出現(xiàn)她奥。

知道原因了瓮增,問題也就好解決了,在插入html的時候給它后面添加一個文本內(nèi)容就好了哩俭,順便在前面也添加一個绷跑,這樣無論后退還是前進都可以顯示焦點了,上完整代碼:

$('#at-container ul li a').unbind('click').click(function (e) {
    e.stopPropagation();
    let peer = $(this).data('peer');
    $('#at-container').css('display', 'none');
        if (window.getSelection) {
            let range = window.getSelection();
            if (range.rangeCount > 0) {
                let sel = range.getRangeAt(0);
                let startOffset = sel.startOffset;
                range.anchorNode.replaceData(startOffset - 1, 1, '');
            }
        }
        let html = `<button contenteditable="false" onclick='return false;' class="at" data-peerid="${peer.peerid}">@${peer.name}</button>`;
        // 在插入的html前后分別插入一個 ? 防止焦點丟失的問題
        document.execCommand('insertHTML', false, '?' + html + '?')
        $(`#chat-input-area button[data-peerid="${peer.peerid}"]`).attr('contenteditable', false)
        $('#chat-input-area').focus();
    });

今天就先寫到這里吧凡资,一個@功能竟然能夠出現(xiàn)這么多的坑砸捏,而且還只是界面上的,后臺功能實現(xiàn)上估計也不會少隙赁。

希望本篇文章會對有同樣需求的小伙伴們有所幫助垦藏!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市伞访,隨后出現(xiàn)的幾起案子膝藕,更是在濱河造成了極大的恐慌,老刑警劉巖咐扭,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡蝗肪,警方通過查閱死者的電腦和手機袜爪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來薛闪,“玉大人辛馆,你說我怎么就攤上這事』硌樱” “怎么了昙篙?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長诱咏。 經(jīng)常有香客問我苔可,道長,這世上最難降的妖魔是什么袋狞? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任焚辅,我火速辦了婚禮,結(jié)果婚禮上苟鸯,老公的妹妹穿的比我還像新娘同蜻。我一直安慰自己,他們只是感情好早处,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布湾蔓。 她就那樣靜靜地躺著,像睡著了一般砌梆。 火紅的嫁衣襯著肌膚如雪默责。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天么库,我揣著相機與錄音傻丝,去河邊找鬼。 笑死诉儒,一個胖子當(dāng)著我的面吹牛葡缰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播忱反,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼泛释,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了温算?” 一聲冷哼從身側(cè)響起怜校,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎注竿,沒想到半個月后茄茁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體魂贬,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年裙顽,在試婚紗的時候發(fā)現(xiàn)自己被綠了付燥。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡愈犹,死狀恐怖键科,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情漩怎,我是刑警寧澤勋颖,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站勋锤,受9級特大地震影響饭玲,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜怪得,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一咱枉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧徒恋,春花似錦蚕断、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至径筏,卻和暖如春葛假,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留礁竞,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓带斑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親勋拟。 傳聞我的和親對象是個殘疾皇子勋磕,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 《裕語言》速成開發(fā)手冊3.0 官方用戶交流:iApp開發(fā)交流(1) 239547050iApp開發(fā)交流(2) 10...
    葉染柒丶閱讀 26,000評論 5 19
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,502評論 25 707
  • 一個四歲孩子,家長發(fā)朋友圈說晚上把孩子揍了一頓敢靡,總算老老實實寫拼音作業(yè)了挂滓,接著反思說畢竟才上幼兒園啊,要求事事應(yīng)對...
    王明鵬閱讀 274評論 0 2
  • 今天是星期四啸胧,晴天赶站。 病魔纏身的日子快要到頭了幔虏。今天是昨天壞習(xí)慣的改正的開始,也是發(fā)現(xiàn)自己做出自己的開始贝椿。今天晚上...
    吉一木閱讀 106評論 0 0
  • 最近室友要離開所计,忍不住先在校友群發(fā)了招合租的郵件。于是被“夸”語言能力好以及萌……于是我默默的再把自己的郵件看了一...
    墨韻書香閱讀 933評論 10 8