React 拖拽 插入自定义标签

前端组件几大坑中必有富文本一席之地!!!

先来看哈长神马样子---

这个组件简单的分为了三部分:

1:运算符部分

js:

import { Button } from 'antd';
import React from 'react';

import './index.less';

const OPERTATOR = ['+', '-', '×', '÷', '%', '(', ')'];

export default function Operator({ onClick, ...othters }) {
  return (
    <div className="operator_wrapper" {...othters}>
      {OPERTATOR.map((operator, index) => (
        <Button
          className="operator_item"
          key={index}
          onClick={() => onClick?.(operator)}
        >
          {operator}
        </Button>
      ))}
    </div>
  );
}

css:

.operator_wrapper {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  grid-gap: 4px;
  .operator_item {
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid #edf1f5;
    border-radius: 2px;
    width: 28px;
    height: 28px;
    cursor: pointer;
    &:hover {
      background: rgba(@primary-color, 0.2);
    }
  }
}

2:可拖拽的树组件

树组件的实现就不贴代码了,其实就是一个递归组件

主要实现代码:

       <div
          draggable={!isArray(data)}
          className={classnames(className, 'node_item', {
            isActive: active === currentKey,
          })}
          id={currentKey}
          data-key={currentKey}
          onDragStart={(event) => drag(event)}
          onClick={(e) => handleClick(currentKey, data, e)}
          {...others}
        >
          {isArray(data) && !isEmpty(data) && (
            <RightOutlined
              className={classnames('expandIcon', { expand })}
              style={{ marginRight: 6 }}
              onClick={(e) => hanldeToggleExpand(currentKey, e)}
            />
          )}
          {children}
        </div>

最主要的两个属性:

draggable:是一个枚举类型的属性,用于标识元素是否允许使用拖放操作

它的取值如下:

  • true,表示元素可以被拖动
  • false,表示元素不可以被拖动

ondragstart :在用户开始拖动元素或选择的文本时触发

在这里 ondragstart 主要是用来传输文本的,如下:

 const drag = (ev) => {
    ev.dataTransfer.setData('text', ev.target.innerText);
  };

DataTransfer - Web API 接口参考 | MDNDataTransfer 对象用于保存拖动并放下(drag and drop)过程中的数据。它可以保存一项或多项数据,这些数据项可以是一种或者多种数据类型。关于拖放的更多信息,请参见 Drag and Drop.https://developer.mozilla.org/zh-CN/docs/Web/API/DataTransfer

3: 简单富文本

思来想去,决定用contentEditable

contenteditable  是一个枚举属性,表示元素是否可被用户编辑。 如果可以,浏览器会修改元素的部件以允许编辑。它是很多富文本插件的底层api

js:

     <div
        className="expression_content"
        id="expression_content"
        contentEditable
        data-contentbox="expression_content"
        ref={contentRef}
        placeholder="点击或拖拽右侧内容编辑表达式"
        onDrop={(event) => drop(event)}
        onDragOver={(event) => allowDrop(event)}
      />

css:

.expression_content {
  width: 100%;
  height: calc(100% - 40px);
  min-height: 300px;
  outline: 1px solid rgb(239 239 239);
  padding: 6px;
  &:empty::before {
    color: lightgrey;
    content: attr(placeholder);
  }
  &:focus {
    outline: 1px solid rgb(97 160 255);
  }
}

设置placeholder属性

 &:empty::before {
    color: lightgrey;
    content: attr(placeholder);
  }
  &:focus {
    outline: 1px solid rgb(97 160 255);
  }

主要逻辑:


  const allowDrop = (event) => {
    event.preventDefault();
  };

  // 拖拽放置
  const drop = (event) => {
    contentRef.current.focus();
    const { contentbox } = event?.target.dataset;
    if (contentbox !== 'expression_content') return;
    event.preventDefault();
    const text = event.dataTransfer.getData('text');
    insertHtmlAtCursor(renderText(text));
  };

  // 插入自定义节点
  const insertHtmlAtCursor = (html) => {
    let node;
    if (window.getSelection) {
      const selection = window.getSelection();
      if (selection.getRangeAt && selection.rangeCount) {
        let range = selection.getRangeAt(0);
        // if (range.startContainer !== contentRef.current) return;
        range = window.getSelection().getRangeAt(0);
        node = range.createContextualFragment(html);
        range.insertNode(node);
        range = range.cloneRange();
        range.collapse(false);
        let sel = window.getSelection();
        sel.removeAllRanges();
        sel.addRange(range);
      }
    }
  };

  // 文本渲染
  const renderText = (text) => {
    return `<span
    data-itemkey=${value}
    class="customNode"
  >
    ${text}
  </span>`;
  };

  // 插入文本
  const insertTextAtCursor = (text) => {
    let sel, range;
    if (window.getSelection) {
      sel = window.getSelection();
      if (sel.getRangeAt && sel.rangeCount) {
        range = sel.getRangeAt(0);
        range.deleteContents();

        const el = document.createElement('div');
        el.innerHTML = `<span data-itemkey=${value}>${text}</span>`;
        let frag = document.createDocumentFragment(),
          node,
          lastNode;
        while ((node = el.firstChild)) {
          lastNode = frag.appendChild(node);
        }
        range.insertNode(frag);

        if (lastNode) {
          range = range.cloneRange();
          range.setStartAfter(lastNode);
          range.collapse(true);
          sel.removeAllRanges();
          sel.addRange(range);
        }
      }
    } else if (document.selection && document.selection.type !== 'Control') {
      document.selection.createRange().pasteHTML(text);
    }
  };

 // 因为要插入自定义节点和文本 所以这里做了判断
  useEffect(() => {
    if (text === '') return;
    if (type === 'operator') {
      contentRef.current.focus();
      insertTextAtCursor(text);
    } else {
      contentRef.current.focus();
      insertHtmlAtCursor(renderText(text));
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [type, text]);

return (
      <div
        className="expression_content"
        id="expression_content"
        contentEditable
        data-contentbox="expression_content"
        ref={contentRef}
        placeholder="点击或拖拽右侧内容编辑表达式"
        onDrop={(event) => drop(event)}
        onDragOver={(event) => allowDrop(event)}
      />
  );

window.getSelection: 返回一个 Selection 对象,表示用户选择的文本范围或光标的当前位置。

知识点都不难,可自行问度娘

总结下在光标处插入文本 自定义节点的方法

方法一:在光标处插入文本

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <div class="box" contenteditable style="border: 1px solid red;">
      我是可编辑的div
    </div>
    <button onclick="pasteHtmlAtCaret('我是插入的内容')">插入</button>
  </body>
  <script type="text/javascript">
    function pasteHtmlAtCaret(html) {
      var sel, range;
      if (window.getSelection) {
        // IE9 and non-IE
        sel = window.getSelection();
        if (sel.getRangeAt && sel.rangeCount) {
          range = sel.getRangeAt(0);
          range.deleteContents();

          // Range.createContextualFragment() would be useful here but is
          // only relatively recently standardized and is not supported in
          // some browsers (IE9, for one)
          var el = document.createElement('div');
          el.innerHTML = html;
          var frag = document.createDocumentFragment(),
            node,
            lastNode;
          while ((node = el.firstChild)) {
            lastNode = frag.appendChild(node);
          }
          range.insertNode(frag);

          // Preserve the selection
          if (lastNode) {
            range = range.cloneRange();
            range.setStartAfter(lastNode);
            range.collapse(true);
            sel.removeAllRanges();
            sel.addRange(range);
          }
        }
      } else if (document.selection && document.selection.type != 'Control') {
        // IE < 9
        document.selection.createRange().pasteHTML(html);
      }
    }
  </script>
</html>

 方法二:在光标处插入html

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <div id="box" contenteditable>我是p标签的内容</div>
  </body>
  <script type="text/javascript">
    document.getElementById('box').onmouseup = function () {
      insertHtmlAtCursor('<strong>666</strong>');
    };

    //插入一个HTML字符串:
    function insertHtmlAtCursor(html) {
      var range, node;
      range = window.getSelection().getRangeAt(0);
      node = range.createContextualFragment(html);
      range.insertNode(node);
    }
  </script>
</html>

 方法三:在光标处插入一段文本

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <div id="box" contenteditable>我是p标签的内容</div>
  </body>
  <script type="text/javascript">
    document.getElementById('box').onmouseup = function () {
      insertTextAtCursor('<strong>666</strong>');
    };

    function insertTextAtCursor(txt) {
      var sel = window.getSelection();
      var iEnd = sel.anchorOffset;
      var htmldata = sel.anchorNode.data;

      if (htmldata) {
        var finaldata =
          htmldata.substring(0, iEnd) + txt + htmldata.substring(iEnd);
        sel.anchorNode.textContent = finaldata;
      } else {
        sel.anchorNode.textContent = txt;
      }
    }
  </script>
</html>

 方法四: 在光标处插入自定义节点

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <div id="box" contenteditable>我是p标签的内容</div>
  </body>
  <script type="text/javascript">
    document.getElementById('box').onmouseup = function () {
      insertTextAtCursor('<span style="color:red">666</span>');
    };

    function insertTextAtCursor(html) {
      var sel = window.getSelection();
      if (sel.getRangeAt && sel.rangeCount) {
        var range = sel.getRangeAt(0);
        var node = range.createContextualFragment(html);
        range.deleteContents();
        range.insertNode(node);
      }
    }
  </script>
</html>

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

superTiger_y

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值