draft.js--富文本编辑器框架的实践(一)

来源:互联网 发布:一搜网络同志建站 编辑:程序博客网 时间:2024/05/17 18:17

市面上大多数的富文本编辑器都是现成的,很难根据自己的需求进行无论是功能亦或是渲染格式的修改。
而由脸书开源的这款draft.js在富文本编辑器中简直是一股清流般的存在。draft在英文中是“草稿”的意思,如它名字所言,它并不是一款现成的富文本编辑器,而是一款富文本编辑器框架,这意味着你可以在此基础上进行二次开发,写出适合你自己应用场景的富文本编辑器。
下面会写出一些我个人对这款富文本编辑器的实践路线。

  1. 安装
  2. 初始化一个draft编辑器的实例
  3. 对编辑器进行样式修改
  4. 增加格式按钮
  5. 行渲染以及行样式修改
  6. 默认块以及块样式修改
  7. 格式以及对应格式按钮之间的高亮联系
  8. 自定义块的元素渲染以及修改默认块所对应的映射元素
  9. 插入行元素,如emoji表情
  10. 插入块元素,如图片或者视频
  11. 数据存储
  12. 数据回显

(一)安装:

npm install --save draft-js

(二)初始化一个draft编辑器的实例:

//Editor.js//Editor是一个自定义的组件   import React, {findDOMNode, Component} from 'react';import {    convertFromRaw,    convertToRaw,    CompositeDecorator,    DefaultDraftBlockRenderMap,    ContentState,    Editor,    EditorState,    Entity,    RichUtils,    getDefaultKeyBinding,    KeyBindingUtil,    Modifier} from 'draft-js';import style from './css.css';//这里使用了css-Moudlesexport default class componentName extends Component{    constructor(props){        super(props);        this.state = {            editorState:EditorState.createEmpty()        };        this.focus = () => this.refs.editor.focus();        this.onChange = (editorState) => this.setState({editorState});    }    render(){        const {editorState} = this.state;        const {        } = this.props;        return(            <Editor                  editorState={editorState}                  onChange = {this.onChange}                  ref="editor"              >              </Editor>        )    }}       

这样,就能渲染出一个最简单的文本编辑器,你现在可以在里面输入点什么。
(三)对编辑器进行样式修改。
我的做法是,在<Editor/> 的最外层加几个样式DIV。

<div className={style.editorRoot} onClick={this.focus}>       <Editor            editorState={editorState}            onChange = {this.onChange}            blockStyleFn={getBlockStyle}            ref="editor"        >        </Editor></div>

(四)增加格式按钮:光能输入不行,得有什么东西来控制输入文字的格式。按钮和编辑器是分开的,所以我写了一个编辑器按钮的组件,让这些组件与<editor/> 同级。

<div className={style.init}>                <div className={style.operate}>                    <InlineStyle/>                    <BlockStyle/>                    <ColorStyle/>                </div>                <div className={style.editorRoot} onClick={this.focus}>                    <Editor                        editorState={editorState}                        onChange = {this.onChange}                        ref="editor"                    >                    </Editor>                </div>                <Post storeHandle={this.storeHandle}>保存</Post>            </div>

至于里面具体的样式,你可以自己调。现在我的编辑器长这样:
这里写图片描述

但是现在只有格式,按钮并没有具体的功能,下面我们把功能加上去。
功能无非就是为输入的文字加上相应的格式。
一共有两种格式:行和块。

(五)行样式渲染以及修改: 这里最好分成几个小步骤

  1. 确定行格式需要的功能以及对应的类名
  2. 确定行样式的映射类
  3. 使用RichUtils.toggleBlockType() 方法获取新的editorState
  4. 在组件<Editor/> 上添加映射类

开始步骤:

1)确定行格式需要的功能以及对应的类名:

// 行按钮,关键属性 styleName//这个数组可以用于渲染按钮,在点击事件中把styleName传给父方法_toggleInlineStyle()const InlineType = [    {key:1,label:'B',styleName:'Bold',title:'加粗',iconClassName:''},    {key:2,label:'I',styleName:'Italic',title:'斜体',iconClassName:''},];

2)确定行样式的映射类:

//行样式映射const editorStyleMap = {    //字体    Bold:{        fontWeight: '600',    },    Italic:{        fontStyle: 'italic',    },};

3)使用RichUtils.toggleBlockType() 方法获取新的editorState:

 //接受按钮传过来的styleName, RichUtils.toggleInlineStyle()返回一个新的editorState.使用this.onChange()进行更新。    _toggleInlineStyle = (inlineStyle)=>{        this.onChange(            RichUtils.toggleInlineStyle(                this.state.editorState,                inlineStyle            )        )    };

4)在组件<Editor/> 上添加映射类:

 <Editor                        customStyleMap = {editorStyleMap}                        editorState={editorState}                        onChange = {this.onChange}                        ref="editor"                    >                    </Editor>

这样,当你选中一段文字,再点击相应的格式按钮时,你选中的文字内容就会被加上相应的css样式。
这里写图片描述

(六)默认块以及默认块样式修改:
draft.js提供了几个默认块的styleName以及对应渲染的元素:

这里写图片描述
也就是说,如果styleName使用了右侧这些名字,他们会被渲染成左侧这些标签。而不是行元素的span。
还是分成几个步骤吧:
1. 确定行格式需要的功能以及对应的类名
2. 使用RichUtils.toggleBlockType() 方法获取新的editorState

1)确定行格式需要的功能以及对应的类名:

//和行样式一样,可以用于渲染块格式按钮。//在点击事件中,把styleName传给父的RichUtils.toggleBlockType()中。//有一点必须注意,这里的styleName只能是Draft.js默认的样式名,也就是上图右侧的名字。因为我们现在就是使用默认块。const BlockType = [    {key:1,label: 'H', styleName: 'header-two',title:'小标题',iconClassName:''},    {key:2,label: '“ ”', styleName: 'blockquote',title:'引用',iconClassName:''},    {key:3,label: '</>', styleName: 'code-block',title:'代码块',iconClassName:''},    {key:4,label: '', styleName: 'unordered-list-item',title:'有序列表',iconClassName:'iconfont icon-other editorButtonIcon'},    {key:5,label: '', styleName: 'ordered-list-item',title:'无序列表',iconClassName:'iconfont icon-other editorButtonIcon'},];

2)使用RichUtils.toggleBlockType() 方法获取新的editorState

//块格式//按钮把styleName传给这个方法,得到新的editorState并更新。    _toggleBlockType = (blockType)=>{        this.onChange(            RichUtils.toggleBlockType (                this.state.editorState,                blockType            )        );    };

这样就成功使用了draft.js默认的块格式了。下面是默认格式blockquote的效果图:
这里写图片描述
也许你觉得默认格式太丑了,我想修改一下。draft.js的组件<Editor/>提供了一个属性blockStyleFn,就是用来读取自定义样式的。

  1. 在css中自定义格式。
  2. 声明一个方法,把自定义格式的css的类名和styleName对应上。
  3. <Editor/>上增加属性blockStyleFn。

1)在css中自定义格式。

//css//我使用了css-Moudles,所以加上了:global():global(.RichEditor-blockquote){    display:block;    border-left: 5px solid #d2d2d2;    color: #666;    margin: 0 0;    padding: 5px 20px;    font-size: 15px;    font-style: italic;    background-color: #eff9ff;}

2)声明一个方法,把自定义格式的css和styleName对应上:

function getBlockStyle(blockName){    switch(blockName.getType()){        case 'blockquote' :            return 'RichEditor-blockquote';        default:            return null;    }}

2)在<Editor/>上增加属性blockStyleFn:

<Editor      customStyleMap = {editorStyleMap}      editorState={editorState}      onChange = {this.onChange}      blockStyleFn={getBlockStyle}      ref="editor"/>

在css中可以发现,我把背景色由原来的灰色换成了浅蓝色。现在引用格式长这样:
这里写图片描述

(七)格式以及对应格式按钮之间的高亮联系:
draft.js的demo给出了当光标在有格式的文本时对应的格式按钮高亮的实现方法。在这里稍微介绍一下。
核心思路是,通过editorState 这个对象的一些方法获取当前光标所在的“标签”(也就是“格式”)的styleName,然后和按钮的styleName进行对比,如果===,则表示现在光标所在的文本格式中有用到这个styleName。这时加上对应的className即可。
行和块获取当前的styleName的方法是不一样的。
1)行:

//按钮组件与editor组件有一层中介组件InlineStyleControl组件,用以渲染按钮。//@InlineType就是由父组件Editor传进来的那个行按钮格式数组。    render(){        const {            editorState,            InlineType        } = this.props;        //获取所有的行样式名        const inlineType = editorState.getCurrentInlineStyle();        return(            <div className={style.init}>                {InlineType.map((obj)=>{                    return <ButtonType                        key={obj.key}                        //进行对比。                        active={inlineType.has(obj.styleName)}                        title={obj.title}                        label={obj.label}                        iconClassName = {obj.iconClassName}                        styleName={obj.styleName}                        onToggle = {this.props.onToggle}                    />                })}            </div>        )    }
//按钮组件ButtonType的renderrender(){        const {            title,            label,            active,            iconClassName,        } = this.props;        let classNames;        if(active){        //有的话,加上这个CSS类名            classNames = 'editorActiveButton'        }        return(            <span className={style.init + ' ' + classNames + ' ' + iconClassName}                  title={title}                  onClick =  {this.onToggle}            >                {label}            </span>        )    }

现在的效果是这样,当光标在斜体内容里时,斜体按钮”I”会有红色高亮。
这里写图片描述

块格式实现相同的效果和行格式有一点点区别。区别在获取当前有哪些格式的方法上。

//BlockStyleControl组件render(){        const {            editorState,            BlockType        } = this.props;        // 这里开始获取当前光标所在的格式的类名        let selection = editorState.getSelection();        let blockStyle = editorState            .getCurrentContent()            .getBlockForKey(selection.getStartKey())            .getType();        return(            <div className={style.init}>                {BlockType.map((obj)=>{                    return <ButtonType                        key={obj.key}                        //检查                        active = {blockStyle === obj.styleName}                        title={obj.title}                        label={obj.label}                        iconClassName = {obj.iconClassName}                        styleName={obj.styleName}                        onToggle = {this.props.onToggle}                    />                })}            </div>        )    }

效果图如下:
这里写图片描述
这里写图片描述

有了这个小功能。用户就可以再点击一次按钮,把格式取消或者加上。

原创粉丝点击