IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> React 拖拽 插入自定义标签 -> 正文阅读

[JavaScript知识库]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>

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-02-22 20:29:58  更:2022-02-22 20:32:08 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 9:22:31-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码