zoukankan      html  css  js  c++  java
  • 自定义编辑器实现光标处插入内容的功能


    如图所示,需求是在光标处插入一个占位符,前台展示的时候将占位符替换为需要的内容。

    思路

    在文本输入框中插入占位符,首先想到的是 textarea,但是 textarea 有个问题:只能插入文本,就算插入了自定义的占位符,但是只是普通文本,用户可以对占位符进行编辑。而我们更希望的是插入的占位符用户不能编辑,删除也是整个占位符一起删除,而不是一个一个字符的删除。
    所以,只能用富文本输入框来实现了。
    富文本输入框的基本实现就是将一个 div 设置属性 contenteditable="true",这个 div 就可以在里面进行输入编辑了。
    要插入占位符,首先需要的就是要获取到光标所在位置,那么该怎么做呢?这里主要就是使用 window.getSelection() 方法,这个方法主要用来获取光标选择的文本内容,就是按住鼠标不放在文本上拖动那种。但是即使光标没选择内容,也是具有光标信息的,可以通过这些信息来对光标进行操作。

    下面是简单的获取光标 range 的代码,接收富文本框容器 dom 元素:

    
    // 获取光标位置相关信息
    function getCursorPosition(ele) {
      const doc = ele.ownerDocument || ele.document;
      const win = doc.defaultView || doc.parentWindow;
      const sel = win.getSelection();
      let range;
      if (sel.rangeCount > 0) {
        range = sel.getRangeAt(0);  // 获取到当前光标所在的元素区域对象
    
        // 光标不在富文本框内,则将 range 改为 undefined
        if (!ele.contains(range.startContainer)) {
          range = undefined;
        }
      }
      return range;
    }
    

    然后使用 range.insertNode() 方法就能在光标处插入自定义节点了,当然有些特殊情况要进行处理:

      const insertPlaceholder = (type) => {
        const range = getCursorPosition(editorRef);
    
        // 创建文本占位符
        const createPh = (type) => {
          let spanDom = document.createElement('span');
          spanDom.setAttribute('contentEditable', false);  // 占位符不能编辑
          spanDom.classList.add('editor_placeholder');
          spanDom.classList.add(typeMap[type].className);
          spanDom.innerText = `{&${type}&}`;
          return spanDom;
        }
    
        const placeholderDom = createPh(type);
    
        if (range) {  // 光标在富文本框内,插入到光标位置
          const rangeData = range.startContainer.data || '';
          if (/{&w+&}/.test(rangeData)) {  // 光标在占位符上
            const focusPh = range.startContainer.parentElement;  // 获取占位符 dom
            setCursorAfter(focusPh);  // 光标设置到占位符后面
            range.insertNode(placeholderDom);
          } else {
            range.insertNode(placeholderDom);
          }
        } else {  // 插入到末尾
          editorRef.appendChild(placeholderDom);
        }
    
        // 光标移到插入的元素后面
        editorRef.focus();
        setCursorAfter(placeholderDom);
      }
    

    想让占位符不能编辑只需将其 contenteditable 设为 false 即可。

    完整 demo

    
    const typeMap = {
      text: {
        // img: noData,
        className: 'editor_text',
      },
      num: {
        // img: img2,
        className: 'editor_num',
      },
      time: {
        // img: noData,
        className: 'editor_time',
      },
      percent: {
        // img: noData,
        className: 'editor_percent',
      },
    };
    
    // 获取光标位置相关信息
    function getCursorPosition(ele) {
      const doc = ele.ownerDocument || ele.document;
      const win = doc.defaultView || doc.parentWindow;
      const sel = win.getSelection();
      let range;
      if (sel.rangeCount > 0) {
        range = sel.getRangeAt(0);  // 获取到当前光标所在的元素区域对象
    
        // 光标不在富文本框内,则将 range 改为 undefined
        if (!ele.contains(range.startContainer)) {
          range = undefined;
        }
      }
      return range;
    }
    
    // 设置光标为 ele 元素之后
    function setCursorAfter(ele) {
      const sle = window.getSelection();
      const r = sle.getRangeAt(0)
      r.setStartAfter(ele);
      r.setEndAfter(ele)
    }
    
    export default (props) => {
      const [editorRef, setEditorRef] = useState(null);
      const [editorContent, setEditorContent] = useState('');
    
      const deleteListener = e => {
        if (e.key === 'Backspace' || e.key === 'Delete') {
          window.getSelection().getRangeAt(0).deleteContents();
        }
      }
    
      useEffect(() => {
        if (editorRef) {
          editorRef.addEventListener('keydown', deleteListener, false);
          return () => {
            editorRef.removeEventListener('keydown', deleteListener, false);
          }
        }
      }, [editorRef]);
    
      const onEditorChange = e => {
        setEditorContent(e.target.outerHTML);
      }
    
      const insertPlaceholder = (type) => {
        const range = getCursorPosition(editorRef);
    
        // 创建文本占位符
        const createPh = (type) => {
          let spanDom = document.createElement('span');
          spanDom.setAttribute('contentEditable', false);  // 占位符不能编辑
          spanDom.classList.add('editor_placeholder');
          spanDom.classList.add(typeMap[type].className);
          spanDom.innerText = `{&${type}&}`;
          return spanDom;
        }
    
        const placeholderDom = createPh(type);
    
        if (range) {  // 光标在富文本框内,插入到光标位置
          const rangeData = range.startContainer.data || '';
          if (/{&w+&}/.test(rangeData)) {  // 光标在占位符上
            const focusPh = range.startContainer.parentElement;  // 获取占位符 dom
            setCursorAfter(focusPh);  // 光标设置到占位符后面
            range.insertNode(placeholderDom);
          } else {
            range.insertNode(placeholderDom);
          }
        } else {  // 插入到末尾
          editorRef.appendChild(placeholderDom);
        }
    
        // 光标移到插入的元素后面
        editorRef.focus();
        setCursorAfter(placeholderDom);
      }
    
      return (
        <div className={styles.container}>
          <div
              id="editor"
              className={styles.editor}
              contentEditable={true}
              onInput={onEditorChange}
              ref={ref => setEditorRef(ref)}
          />
          <div className={styles.btnsWrapper}>
            <div className={styles.insertBtn}>
              <Button type="primary" onClick={() => insertPlaceholder('text')}>插入文本填空</Button>
            </div>
            <div className={styles.insertBtn}>
              <Button type="primary" onClick={() => insertPlaceholder('num')}>插入数字填空</Button>
            </div>
            <div className={styles.insertBtn}>
              <Button type="primary" onClick={() => insertPlaceholder('time')}>插入时间填空</Button>
            </div>
            <div className={styles.insertBtn}>
              <Button type="primary" onClick={() => insertPlaceholder('percent')}>插入百分数填空</Button>
            </div>
          </div>
        </div>
      )
    }
    
  • 相关阅读:
    免费素材下载:淡蓝色的PSD格式UI套件
    分享一个CSS3的网格系统架构 ResponsiveAeon
    最新收集的超棒Mobile/Web UI和用户体验设计
    一个帮助你针对不同标签自动填入内容的轻量级javascript类库 fixiejs
    发现任何VB函数、插件、甚至按键精灵对“文件下载”窗口后台失效
    android 界面 滑入 效果
    分布式HeadLoop
    VB ListView
    android 下载保存图片
    网址
  • 原文地址:https://www.cnblogs.com/3body/p/14831914.html
Copyright © 2011-2022 走看看