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

市面上大多数的富文本编辑器都是现成的,很难根据自己的需求进行无论是功能亦或是渲染格式的修改。
而由脸书开源的这款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-Moudles
export 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的render
render(){
        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>
        )
    }

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

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

  • 6
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值