<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>

          前端web實現(@、At、艾特)選人或引用數據

          共 22037字,需瀏覽 45分鐘

           ·

          2022-06-15 05:02

          前言

          在我們日常的網絡社交中,@XXX 功能可以說是一個比較常見的功能了。本文將結合實踐,介紹一種可以快速實現 @ 選人或引用數據的方式。

          功能需求

          簡單說一下需求:

          1、在輸入框中輸入 @ ,彈出浮窗,然后可以選擇浮窗中相關的數據;2、在輸入框中輸入 # ,彈出浮窗,然后可以選擇浮窗中相關的數據;3、@# 引用的數據要包含名稱和id等,最終要傳給后端;4、刪除 @# 引用的數據時,需要整體刪除;5、@# 引用的數據需要被標注成不同的顏色。

          大致就是這樣。

          技術方案

          在網上參考了不少大佬的文章,也大致了解了一些社交平臺的實現方式,有興趣的朋友可以看看文末的參考。

          最終因為功能的契合度和時間原因,我選擇了開源庫: tributejs 。這個開源庫有原生,Vue 等例子,就是沒有 React 的例子,但是問題不大,使用方式都是大同小異。

          具體實現

          本文的 @XXX 功能是 tributejs + React實現的,所以 React 技術棧的同學可以直接參考后面的例子,其他技術棧的同學可以參考 tributejs 官方的實現。

          @功能實現

          首先當然是要下載 tributejs:

          yarn add tributejs
          或者
          npm install tributejs

          然后就是引入 tributejs,對想要的功能進行配置,具體各項配置的意義,可以直接到 tributejs 的 GitHub 上查看。最后可以給編輯器加一些自定義的樣式:

          index.tsx

          import React, { useEffect, useState, useRef  } from 'react';
          import Tribute from "tributejs";
          import './index.less';

          const AtDemo = () => {
            const [atList, setAtList] = useState([
              {
                key"1",
                value"小明",
                position"前端開發(fā)工程師"
              },
              {
                key"2",
                value"小李",
                position"后端開發(fā)工程師"
              }
            ]);
            const [poundList, setpoundList] = useState([
              { name"JavaScript"explain"前端開發(fā)語言" },
              { name"Java"explain"后端開發(fā)語言之一" }
            ]);

            useEffect(() => {
              renderEditor(atList, poundList);
            }, [])

            const renderEditor = (_atList: any[], _poundList: any[]) => {
              let tributeMultipleTriggers = new Tribute({
                allowSpacestrue,
                noMatchTemplatefunction (return null; },
                collection: [
                  {
                    selectTemplatefunction(item{
                      if (this.range.isContentEditable(this.current.element)) {
                        return (
                          `<span contenteditable="false">
                            <span
                              class="at-item"
                              title="${item.original.value}"
                            >
                              @${item.original.value}
                            </span>
                          </span>`

                        );
                      }

                      return "@" + item.original?.value;
                    },
                    values: _atList,
                    menuItemTemplatefunction (item{
                      return item.original.value;
                    },
                  },
                  {
                    trigger"#",
                    selectTemplatefunction(item{
                      if (this.range.isContentEditable(this.current.element)) {
                        return (
                          `<span contenteditable="false">
                            <span
                              class="pound-item"
                            >
                              #${item.original.name}
                            </span>
                          </span>`

                        );
                      }

                      return "#" + item.original.name;
                    },
                    values: _poundList,
                    lookup"name",
                    fillAttr"name"
                  }
                ]
              });

              tributeMultipleTriggers.attach(document.getElementById("editorMultiple"as HTMLElement);
            }
            
            return (
                <div className="at-demo">
                    <div
                      id="editorMultiple"
                      className="tribute-demo-input"
                      placeholder="請輸入"
                    >
          </div>
                </div>

            )
          }

          export default AtDemo;

          index.less

          .at-demo {
              background-color: #fff;
              padding: 24px;
              .at-item, .pound-item {
                  color: #2ba6cb;
              }
          }
          .tribute-container {
              position: absolute;
              top: 0;
              left: 0;
              height: auto;
              overflow: auto;
              display: block;
              z-index: 999999;
            }
            .tribute-container ul {
              margin: 0;
              margin-top: 2px;
              padding: 0;
              list-style: none;
              background: #fff;
              border: 1px solid #3c98fa;
              border-radius: 4px;
            }
            .tribute-container li {
              padding: 5px 5px;
              cursor: pointer;
              border-radius: 4px;
            }
            .tribute-container li.highlight {
              background: #eee;
            }
            .tribute-container li span {
              font-weight: bold;
            }
            .tribute-container li.no-match {
              cursor: default;
            }
            .tribute-container .menu-highlighted {
              font-weight: bold;
          }
          .tribute-demo-input {
            outline: none;
            border: 1px solid #d9d9d9;
            padding: 4px 11px;
            border-radius: 2px;
            font-size: 15px;
            min-height: 100px;
            cursor: text;
          }
          .tribute-demo-input:hover {
            border-color: #3c98fa;
            transition: all 0.3s;
          }
          .tribute-demo-input:focus {
            border-color: #3c98fa;
          }
          [contenteditable="true"]:empty:before {
            content: attr(placeholder);
            display: block;
            color: #ccc;
          }
          #test-autocomplete-container {
            position: relative;
          }
          #test-autocomplete-textarea-container {
            position: relative;
          }
          .float-right {
            float: right;
          }


          我們可以看看效果,還是很不錯的:

          被引用的數據也是被整體刪除的:

          獲取編輯器中的數據

          我們在編輯器中輸入了我們想要的數據,那最終都是要獲取其中的數據并且傳遞給后端的:


          ...

          import { Button } from 'antd';
          // 轉義HTML
          const htmlEscape = (html: string) => {
            return html.replace(/[<>"&]/g,function(match,pos,originalText){
              switch(match){
                case "<":
                    return "&lt;";
                case ">":
                    return "&gt;"
                case "&":
                    return "&amp;";
                case "\"":
                    return "&quot;";
                default:
                  return match;
              }
            });
          }

          const AtDemo = () => {

              ...
              
              const getDataOfEditorMultiple = () => {
                  const childrenData = document.getElementById('editorMultiple')?.innerHTML;
                  console.log('childrenData', childrenData)
                  const toServiceData = htmlEscape(childrenData);
                  console.log('toServiceData', toServiceData)
              }
            
              return (
                <div className="at-demo">
                    <div
                      id="editorMultiple"
                      className="tribute-demo-input"
                      placeholder="請輸入"
                    >
          </div>
                    <Button onClick={getDataOfEditorMultiple}>獲取輸入框中所有元素</Button>
                </div>

              )
          }

          我們可以直接通過 getDataOfEditorMultiple 方法直接獲取編輯器中的數據,并且轉義之后發(fā)送給后端。

          實時獲取編輯器中被引用的數據

          我們有時候可能需要實時的監(jiān)聽編輯器中所數據的數據,或者是被引用的數據。這時我們可以調用 oninput 這個方法。當然也可以在其他情況調用 onbluronfocus 這兩個方法,顧名思義就是失去焦點時和獲取焦點時。

          完整的代碼如下:

          import React, { useEffect, useState, useRef  } from 'react';
          import './index.less';
          import Tribute from "tributejs";
          import { Button } from 'antd';

          const htmlEscape = (html: string) => {
            return html.replace(/[<>"&]/g,function(match,pos,originalText){
              switch(match){
                case "<":
                    return "&lt;";
                case ">":
                    return "&gt;"
                case "&":
                    return "&amp;";
                case "\"":
                    return "&quot;";
                default:
                  return match;
              }
            });
          }

          const AtDemo = () => {
            const [atList, setAtList] = useState([
              {
                key"1",
                value"小明",
                position"前端開發(fā)工程師"
              },
              {
                key"2",
                value"小李",
                position"后端開發(fā)工程師"
              }
            ]);
            const [poundList, setpoundList] = useState([
              { name"JavaScript"explain"前端開發(fā)語言" },
              { name"Java"explain"后端開發(fā)語言之一" }
            ]);

            useEffect(() => {
              renderEditor(atList, poundList);
            }, [])

            const renderEditor = (_atList: any[], _poundList: any[]) => {
              let tributeMultipleTriggers = new Tribute({
                allowSpacestrue,
                noMatchTemplatefunction (return null; },
                collection: [
                  {
                    selectTemplatefunction(item{
                      if (this.range.isContentEditable(this.current.element)) {
                        return (
                          `<span contenteditable="false">
                            <span
                              class="at-item"
                              title="${item.original.value}"
                              data-atkey="${item.original.key}"
                              data-atvalue="${item.original.value}"
                            >
                              @${item.original.value}
                            </span>
                          </span>`

                        );
                      }

                      return "@" + item.original?.value;
                    },
                    values: _atList,
                    menuItemTemplatefunction (item{
                      return item.original.value;
                    },
                  },
                  {
                    trigger"#",
                    selectTemplatefunction(item{
                      if (this.range.isContentEditable(this.current.element)) {
                        return (
                          `<span contenteditable="false">
                            <span
                              class="pound-item"
                              data-poundname="${item.original.name}"
                            >
                              #${item.original.name}
                            </span>
                          </span>`

                        );
                      }

                      return "#" + item.original.name;
                    },
                    values: _poundList,
                    lookup"name",
                    fillAttr"name"
                  }
                ]
              });

              tributeMultipleTriggers.attach(document.getElementById("editorMultiple"as HTMLElement);
            }
            
            const getDataOfEditorMultiple = () => {
              const childrenData = document.getElementById('editorMultiple')?.innerHTML || '';
              console.log('childrenData', childrenData)
              const toServiceData = htmlEscape(childrenData);
              console.log('toServiceData', toServiceData)
            }

            const onInput = () => {
              const atItemList = document.getElementsByClassName('at-item');
              Array.prototype.forEach.call(atItemList, function(el{
                console.log(el.dataset.atkey);
                console.log(el.dataset.atvalue);
              });
            }
            return (
                <div className="at-demo">
                    <div
                      id="editorMultiple"
                      className="tribute-demo-input"
                      placeholder="請輸入"
                      onInput={onInput}
                    >
          </div>
                    <Button onClick={getDataOfEditorMultiple}>獲取輸入框中所有元素</Button>
                </div>

            )
          }

          export default AtDemo;

          幾個關鍵點的實現

          這里提一下幾個關鍵功能點的實現原理。

          • 編輯器的輸入框利用的是普通的 div 標簽,然后采用 contenteditable="true" 這個屬性來實現的;
          • 引用數據的浮窗定位可以利用 Selection對象來獲取;
          • 被 @ 或 # 引用的數據,想要被一次性刪除,可以在被 @ 或 #的數據外包含一個 <span contenteditable="false"></span>,表示不可編輯的標簽;
          • 把被引用的數據定義為特定的顏色,這個因為我們在輸入框中插入引用數據時,被引用的數據是被HTML標簽包裹著的,所以我們只需要對相關的HTML進行樣式設置就好了;
          • 想要獲取被引用數據中的多個屬性的值,可以和上面的例子一樣,利用HTML5的自定義屬性  data-xxx 來保存我們想要的屬性值,然后通過遍歷標簽 el.dataset.xxx 獲取我們想要的屬性的值。

          最后

          本文介紹了一種可以在前端快速實現 @xxx 選人或引用數據的功能,在部分情景下也算是比較好的解決方案了。有興趣的同學可以看看文末參考文章中其他大佬們的實現方式。

          參考

          https://github.com/zurb/tribute https://segmentfault.com/a/1190000037660531 https://segmentfault.com/a/1190000007846897 https://juejin.cn/post/6982251438332182542 https://mp.weixin.qq.com/s/YP6H6CHkUd97ThDtEoXzaw


          瀏覽 135
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  韩国一区视频在线观看 | 国产欧美操逼 | 在线免费看黄 | www.成人高清 | 囯产精品久久久久久久久久乐趣播 |