最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • react-quill 富文本编辑器&采坑

    正文概述 掘金(龙大LLong)   2020-12-15   2002

    [原文地址](www.yuque.com/docs/share/… 《react-quill 富文本编辑器&采坑》)

    由于业务功能,需要实现当用户在富文本里进行可以格式化操作,内容粘贴操作的时候,如果用户复制的是图片,需要将图片上传服务器后,插入到文本内;看似合情合理的要求,却有很多坑。

    react-quill 富文本编辑器&采坑

    开始使用

    安装 react-quill

    npm install react-quill @ beta

    资源:

    github地址

    quill 中文文档地址

    quill 文档地址

    直接上手练练 Codeopen

    1、自定义富文本格式刷功能

    quill是不具备格式刷的功能,那么也就没有对应的icon,这时就需要去自定义工具栏,quill工具栏控件即可以用一个格式名称数组定义,也可以用一般的HTML容器定义。这里采用的是HTML容器定义。

    先自定义一个格式刷的icon。这里的icon采用的是iconfont的unicode引用,支持按字体的方式去动态调整图标大小,颜色等等。可以实现选中高亮的效果,与原来的工具栏交互保持一致。

    const CustomButton = () => <i className="iconfont">&#xe608;</i>;
    

    自定义HTML容器CustomToolbar

    const CustomToolbar = () => (
      <div id="toolbar">
        <select className="ql-header" defaultValue={''} onChange={(e) => e.persist()}>
          <option value="1" />
          <option value="2" />
          <option value="3" />
          <option value="4" />
          <option value="5" />
          <option selected />
        </select>
        <select className="ql-size" defaultValue={''} onChange={(e) => e.persist()}>
          <option value="small" />
          <option selected />
          <option value="large" />
          <option value="huge" />
        </select>
        <button className="ql-formatBrush">
          <CustomButton />
        </button>
        <button className="ql-bold" />
        <button className="ql-italic" />
        <button className="ql-underline" />
        <button className="ql-strike" />
        <select className="ql-color" />
        <select className="ql-background" />
        <select className="ql-align" />
        <button className="ql-list" value="ordered" />
        <button className="ql-list" value="bullet" />
        <button className="ql-indent" value="+1" />
        <button className="ql-indent" value="-1" />
        <button className="ql-image" />
        <button className="ql-clean" />
      </div>
    );
    

    CustomToolbar就是加入原有的工具栏与格式刷工具栏。把自定义的工具栏放到return里。

    return (
        <div className={styles['rich-text']}>
          <CustomToolbar />
          <ReactQuill
            ref={editorRef}
            placeholder={placeholder || '请输入内容'}
            modules={modules}
            theme="snow"
            {...props}
            value={value}
          />
        </div>
      );
    

    效果:

    react-quill 富文本编辑器&采坑

    格式刷功能

    接下来是格式刷功能,在modules完成格式刷。formatBrush与工具栏 className="ql-formatBrush" 保持一致。

    //记得声明变量
    
    let quillEditor = null;
    const copyFormatting = {
      value: 'un-active',
      format: {},
    };
    
    
    //modules 方法
    const modules = useMemo(() => {
        return {
          toolbar: {
            // container: toolbarContainer,
            container: '#toolbar',
            handlers: {
              image: imageHandler,
              formatBrush: () => {
                copyFormatBrush(quillEditor, copyFormatting);
              },
            },
          },
        };
      }, []);
    

    关键的格式设置样式方法,点击格式刷,如果选中的区域有样式,则保存样式,格式刷功能为选中状态,再次点击删除样式,取消选中;设置格式样式。可以把这3个方法放到单独文件导出管理。

    /* eslint-disable @typescript-eslint/no-use-before-define */
    export const copyFormatBrush = (quillEditor, copyFormatting) => {
      // 点击格式刷,如果有选中区域且有样式,则保存其样式,按键状态改为选中。
      // 再次点击,删除样式,按键取消选中。
      if (copyFormatting.value === 'un-active') {
        const range = quillEditor.getSelection(true);
        if (range == null || range.length === 0) return;
        const format = quillEditor.getFormat(range);
        if (Object.keys(format).length === 0) return;
        setCopyFormatting(quillEditor, copyFormatting, 'active', format);
      } else {
        setCopyFormatting(quillEditor, copyFormatting, 'un-active', null);
      }
    };
    
    // 设置copyFormatting: 修改保存的样式、按键状态、粘贴样式的处理程序
    export const setCopyFormatting = (quill, copyFormatting, value, format) => {
      copyFormatting.value = value;
      copyFormatting.format = format;
      const toolbar = quill.getModule('toolbar').container;
      const brushBtn = toolbar.querySelector('.ql-formatBrush');
      if (value === 'active') {
        brushBtn.classList.add('ql-formatBrushactive');
        quill.on('selection-change', pasteFormatHandler);
      } else {
        brushBtn.classList.remove('ql-formatBrushactive');
        quill.off('selection-change', pasteFormatHandler);
      }
      function pasteFormatHandler(range, oldRange, source) {
        return pasteFormat(range, oldRange, source, quill, copyFormatting);
      }
    };
    // 粘贴样式的处理程序: 如果选中范围且有保存样式,则粘贴样式,并初始化copyFormatting
    export const pasteFormat = (range, oldRange, source, quill, copyFormatting) => {
      if (range && copyFormatting.format) {
        if (range.length === 0) {
        } else {
          quill.formatText(range.index, range.length + 1, copyFormatting.format);
          setCopyFormatting(quill, copyFormatting, 'un-active', null);
        }
      } else {
        // console.log('Cursor not in the editor')
      }
    };
    

    本人测试使用默认的 ql-active 高亮样式,不起作用,只有自定义格式刷的高亮样式:

      .ql-toolbar.ql-snow .ql-formatBrushactive {
          color: #06c;
        }
    

    效果:

    react-quill 富文本编辑器&采坑

    以上格式刷功能已经实现。

    2、内容粘贴操作-图片直接预览

    用户复制的内容里包含跨域的图片,需要将图片上传服务器后,插入到文本内显示。

    目前image功能是自定义封装了,用户插入图片会走antd的Upload上传,把图片资源上传服务器后再插入我们本服务器的URL图片。

    自定义Quill的行为和功能

    主要方法是imageHandler,使用 Quill.register 模块可以自定义Quill的行为和功能,

    const modules = useMemo(() => {
        return {
          toolbar: {
            // container: toolbarContainer,
            container: '#toolbar',
            handlers: {
              image: imageHandler,
            },
          },
        };
      }, []);
    

    具体的弹窗逻辑就不过多展示,在拿到上传完的fileList数据,就在富文本插入图片。

    react-quill 富文本编辑器&采坑

    //在index.jsx内引入自定义的模块image功能
    import './ImageBlot';
    
    
    
    // 插入图片
      const insertImages = (fileList = []) => {
        if (quillEditor && typeof quillEditor.insertEmbed === 'function') {
          quillEditor.focus();
          const unprivilegedEditor = editorRef.current.makeUnprivilegedEditor(quillEditor);
          fileList.forEach((file) => {
            setTimeout(() => {
              const range = unprivilegedEditor.getSelection();
              const position = range ? range.index : 0;
              quillEditor.insertEmbed(position, 'image', {
                ossid: file.ossId,
                url: file.url,
              });
              quillEditor.setSelection(position + 1, 0);
            }, 0);
          });
        }
      };
    
    
    // return 
    <ImageUploadModal
        onCancel={() => {
          setShowImageUpload(false);
        }}
        onCreate={(fileList) => {
          setShowImageUpload(false);
          insertImages(fileList);
        }}
      />
    

    复制内容图片的上传拿到新的本服务器的url数据也是在自定义image模块内实现。

    import { Quill } from 'react-quill';
    import { getTmpUrl } from '@/components/AliyunOSSUpload/service';
    import { isExpired } from '@/components/AliyunOSSUpload/utils';
    import { saveAliOssImg } from '@/services/global';
    import errorImg from './error-img.png';
    
    const BlockEmbed = Quill.import('blots/block/embed');
    
    const getExpireFromUrl = (url) => {
      const index = url.indexOf('?');
      if (index !== -1) {
        const reg = new RegExp('(^|&)Expires=([^&]*)(&|$)', 'i');
        const str = url.substring(index + 1, url.length);
        const expire = str.match(reg)[2];
        return expire || 0;
      }
      return 0;
    };
    
    const replacedImage = (ossId, node) => {
      getTmpUrl({ ossIds: [ossId] }).then((data) => {
        if (data && data.length > 0 && data[0].tmpUrl) {
          const url = data[0].tmpUrl;
          node.setAttribute('src', url);
        }
      });
    };
    
    /**
     * 自定义图片标签,增加ossId参数
     */
    class ImageBlot extends BlockEmbed {
      static create(value) {
        console.log('ImageBlot :>> ', value);
        const node = super.create();
        if (value.ossid && value.url) {
          node.setAttribute('ossid', value.ossid);
          node.setAttribute('src', value.url);
          const expire = getExpireFromUrl(value.url);
          if (expire && isExpired(parseInt(expire, 10))) {
            replacedImage(value.ossid, node);
          }
        }
        
        //对URL图片资源进行装换
        if (value.url) {
          let sourceType = 'URL';
          let content = value.url;
          if (value.url.indexOf('base64') > -1) {
            sourceType = 'BASE64';
            content = value.url.split('base64,')[1];
          }
          
          // 跨域图片装换为本服务器资源url
          saveAliOssImg({ sourceType, content })
            .then((data) => {
              if (data && data.url) {
                node.setAttribute('src', data.url);
              }
            })
            .catch(() => {
              node.setAttribute('src', errorImg);
            });
        }
       console.log('ImageBlot --node-:>> ', node);
    
        return node;
      }
    
      static value(node) {
        const ossId = node.getAttribute('ossid');
        const url = node.getAttribute('src');
        return {
          ossid: ossId,
          url,
        };
      }
    }
    ImageBlot.blotName = 'image';
    ImageBlot.tagName = 'img';
    
    Quill.register(ImageBlot);
    

    实际操作

    1、复制域外资源:

    react-quill 富文本编辑器&采坑

    2、粘贴富文本效果:

    react-quill 富文本编辑器&采坑

    从输出看最后替换结果是没有问题

    react-quill 富文本编辑器&采坑

    react-quill 富文本编辑器&采坑

    加入图片加载失败、本地路径图片资源加载失败的处理,使用图片占位符。

    react-quill 富文本编辑器&采坑

    react-quill 富文本编辑器&采坑

    已知问题

    1、“直接复制本地文件夹内的图片,在mac下ok,在windows下失效”,找了下原来windows下复制出来的是bolb流(后端服务器接受失败),而mac下是file流。进一步探索发现在windows文件夹系统中,复制文本类的东西,是在剪切板中,可以获得之;但是,复制的图片文件,不论是右键复制,还是Ctrl + C复制都不行。目前,windows复制图片可以选择富文本的插入图片功能实现。(欢迎留言讨论)

    2、粘贴后页面滚到底部,解决方案如下:

    less文件里添加如下样式。

    .ql-clipboard {
          position: fixed;
          // display: none;
          left: 50%;
          top: 50%;
        }
    

    欢迎多多留言讨论,有好的方案可以留下宝贵意见。


    起源地下载网 » react-quill 富文本编辑器&采坑

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元