react富文本编辑器react-quill的使用

关于写此文的目的:

第一次接触富文本编辑器,尤其是react-quill这种,百度不到的各种坑,踩了无数的坑终于搞明白了。

1.安装

npm i react-quill --save
需要用到emjo的话还需要npm i quillEmoji --save
安装完之后页面引进

import ReactQuill, { Quill } from 'react-quill';
import 'react-quill/dist/quill.snow.css';
import quillEmoji from 'quill-emoji';
import "quill-emoji/dist/quill-emoji.css"; //这个不引入的话会出现emoji框一直在输入框下面的情况
import { ImageDrop } from './plugin/quill-image-drop-module'; //讲图片拖进文本框,可以直接安装quill-image-drop-module;但由于我的webpack版本过低,无法兼容es6,所以把插件拿出来了
//注册ToolbarEmoji,将在工具栏出现emoji;注册TextAreaEmoji,将在文本输入框处出现emoji。VideoBlot是我自定义的视频组件,后面会讲,
const { EmojiBlot, ShortNameEmoji, ToolbarEmoji, TextAreaEmoji } = quillEmoji;
Quill.register({
  'formats/emoji': EmojiBlot,
  'formats/video': VideoBlot,
  'modules/emoji-shortname': ShortNameEmoji,
  'modules/emoji-toolbar': ToolbarEmoji,
  'modules/emoji-textarea': TextAreaEmoji,
  // 'modules/ImageExtend': ImageExtend, //拖拽图片扩展组件
  'modules/ImageDrop': ImageDrop, //复制粘贴组件
}, true);

next。

初始化富文本实例,我写在constructor里,module也是写在这里边

constructor(props) { super(props); this.reactQuillRef = null; }

富文本组件react-quill参数

<ReactQuill
      ref={(el) => { this.reactQuillRef = el }}
      defaultValue={postRichText}
       key="1"
       id="textDiv1" theme="snow" modules={this.modules}  />

工具栏modules的定义基本属性如下:

 this.modules = {
      toolbar: {
        container: [
          [{ 'size': ['small', false, 'large', 'huge'] }], //字体设置
          // [{ 'header': [1, 2, 3, 4, 5, 6, false] }], //标题字号,不能设置单个字大小
          ['bold', 'italic', 'underline', 'strike'],  
          [{ 'list': 'ordered' }, { 'list': 'bullet' }, { 'indent': '-1' }, { 'indent': '+1' }],
          ['link', 'image'], // a链接和图片的显示
          [{ 'align': [] }],
          [{
            'background': ['rgb(  0,   0,   0)', 'rgb(230,   0,   0)', 'rgb(255, 153,   0)',
              'rgb(255, 255,   0)', 'rgb(  0, 138,   0)', 'rgb(  0, 102, 204)',
              'rgb(153,  51, 255)', 'rgb(255, 255, 255)', 'rgb(250, 204, 204)',
              'rgb(255, 235, 204)', 'rgb(255, 255, 204)', 'rgb(204, 232, 204)',
              'rgb(204, 224, 245)', 'rgb(235, 214, 255)', 'rgb(187, 187, 187)',
              'rgb(240, 102, 102)', 'rgb(255, 194, 102)', 'rgb(255, 255, 102)',
              'rgb(102, 185, 102)', 'rgb(102, 163, 224)', 'rgb(194, 133, 255)',
              'rgb(136, 136, 136)', 'rgb(161,   0,   0)', 'rgb(178, 107,   0)',
              'rgb(178, 178,   0)', 'rgb(  0,  97,   0)', 'rgb(  0,  71, 178)',
              'rgb(107,  36, 178)', 'rgb( 68,  68,  68)', 'rgb( 92,   0,   0)',
              'rgb(102,  61,   0)', 'rgb(102, 102,   0)', 'rgb(  0,  55,   0)',
              'rgb(  0,  41, 102)', 'rgb( 61,  20,  10)']
          }],
          [{
            'color': ['rgb(  0,   0,   0)', 'rgb(230,   0,   0)', 'rgb(255, 153,   0)',
              'rgb(255, 255,   0)', 'rgb(  0, 138,   0)', 'rgb(  0, 102, 204)',
              'rgb(153,  51, 255)', 'rgb(255, 255, 255)', 'rgb(250, 204, 204)',
              'rgb(255, 235, 204)', 'rgb(255, 255, 204)', 'rgb(204, 232, 204)',
              'rgb(204, 224, 245)', 'rgb(235, 214, 255)', 'rgb(187, 187, 187)',
              'rgb(240, 102, 102)', 'rgb(255, 194, 102)', 'rgb(255, 255, 102)',
              'rgb(102, 185, 102)', 'rgb(102, 163, 224)', 'rgb(194, 133, 255)',
              'rgb(136, 136, 136)', 'rgb(161,   0,   0)', 'rgb(178, 107,   0)',
              'rgb(178, 178,   0)', 'rgb(  0,  97,   0)', 'rgb(  0,  71, 178)',
              'rgb(107,  36, 178)', 'rgb( 68,  68,  68)', 'rgb( 92,   0,   0)',
              'rgb(102,  61,   0)', 'rgb(102, 102,   0)', 'rgb(  0,  55,   0)',
              'rgb(  0,  41, 102)', 'rgb( 61,  20,  10)']
          }],
          ['clean'], //清空
          ['emoji'], //emoji表情,设置了才能显示
          ['video2'], //我自定义的视频图标,和插件提供的不一样,所以设置为video2
        ],
        handlers: {
          'image': this.imageHandler.bind(this), //点击图片标志会调用的方法
          'video2': this.showVideoModal.bind(this),
        },
      },
      // ImageExtend: {
      //   loading: true,
      //   name: 'img',
      //   action: RES_URL + "connector?isRelativePath=true",
      //   response: res => FILE_URL + res.info.url
      // },
      ImageDrop: true,
      'emoji-toolbar': true,  //是否展示出来
      "emoji-textarea": false, //我不需要emoji展示在文本框所以设置为false
      "emoji-shortname": true, 
    }

想要的最终效果如下图:


image.png

到这一步,界面是能正常显示了,但功能不完善,next

3.功能的开发

1.上传本地图片到服务器,代码如下:

//这是点击图片图标触发的事件
imageHandler() {
    const input = document.createElement('input')
    input.setAttribute('type', 'file')
    input.setAttribute('accept', 'image/*')
    input.setAttribute('multiple', 'multiple')
    input.click()
    const that = this;
    input.onchange = async () => {
      Array.from(input.files).forEach(item => {
      //业务需求安装了压缩图片的插件,可忽略
        new Compressor(item, {
          quality: 0.8,
          convertSize: 1024 * 1024 * 8,
          success(result) {
          //很很很很重要的一步
            const formData = new FormData();
            formData.append('file', result, result.name);
            Axios({
              method: 'post',
              data: formData,
              url: config.RES_URL + 'connector?isRelativePath=true',//图片上传的接口
            }).then(res => {
              if (res.data.success) {
                let quill = that.reactQuillRef.getEditor();//获取到编辑器本身
                const cursorPosition = quill.getSelection().index;//获取当前光标位置
                const link = config.RES_URL + res.data.info.url;
                quill.insertEmbed(cursorPosition, "image", link);//插入图片
                quill.setSelection(cursorPosition + 1);//光标位置加1
              }
            })
          },
        });
      })
    }
  }

到这里已经能实现本地图片上传到服务器的需求了
2.视频自定义

 //上传视频处理
  addVideoItem = (img, url) => {
    let quill = this.reactQuillRef.getEditor();//获取到编辑器本身
    let cursorPosition = quill.selection.savedRange.index
    quill.insertEmbed(cursorPosition, 'Video', {
      url,
      controls: 'controls',
      poster: img,
      width: '100%',
      controlslist: 'nodownload noremoteplayback',
      oncontextmenu: 'return false'
    })
    // 光标不加1的话视频删不掉
    quill.setSelection(cursorPosition + 1);//光标位置加1
    this.setState({
      upVideoShow: false
    })
  }

这段代码功能实现的前提是,我刚开始引入的自定义视频组件,创建视频标签,代码如下:


const Quill = require('quill');

const BlockEmbed = Quill.import('blots/block/embed')
export class VideoBlot extends BlockEmbed {
  static create(value) {
    let node = super.create()
    node.setAttribute('src', value.url)
    node.setAttribute('controls', value.controls)
    node.setAttribute('width', value.width)
    node.setAttribute('poster', value.poster)
    node.setAttribute('controlslist', 'nodownload noremoteplayback')
    node.setAttribute('oncontextmenu', 'return false')
    return node;
  }
  // 富文本初始化取参数,如果有编辑富文本的功能的话,这段代码就需要加上
  static value(node) {
    return {
      url: node.getAttribute('src'),
      controls: node.getAttribute('controls'),
      width: node.getAttribute('width'),
      poster: node.getAttribute('poster'),
      controlslist: node.getAttribute('controlslist'),
      oncontextmenu: node.getAttribute('oncontextmenu')
    };
  }
}
VideoBlot.blotName = 'Video';
VideoBlot.tagName = 'video';
VideoBlot.className = 'ql-video';

完结
踩坑无数搞了两个星期才弄的明明白白,隔了一个月再来写当初怎么艰难开始的也都忘了,写不出来什么了。如有遇到什么问题欢迎留言

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342