手写 editor web 富文本编辑器

蚀纹容器
• 阅读 3051

手写富文本编辑器,需要使用 react 中自带的 FragmentcreateRef,以及 execCommand 方法,这两个到底有什么作用呢。那么,在演示代码前先了解下 FragmentcreateRefexecCommand 是什么,分别有什么作用?

Fragment

React 中一个常见模式是 一个组件返回多个元素。Fragment 相当于一个 React 组件,它可以聚合一个子元素列表,并且不在 DOM 中增加额外节点。在 react 中返回的元素必须有父元素进行包裹,但特殊情况下,我们不想使用多余的标签,此时可以使用 Fragment 包裹标签。Fragment 更像是一个空的 jsx 标签 <></>

class FragmentDemo extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            list: [
                {
                    type: '姓名',
                    text: 'wqjiao'
                },
                {
                    type: '性别',
                    text: '女'
                }
            ]
        }
    }

    render () {
        let { list } = this.state;

        return (
            <table>
                <tbody>
                    <tr>
                        { list && list.map(item => {
                            return (
                                <React.Fragment key={'list' + item}>
                                    <td>{ item.type }</td>
                                    <td>{ item.text }</td>
                                </React.Fragment>
                            )
                        }) }
                    </tr>
                </tbody>
            </table>
        )
    }
}

其中,key 是唯一可以传递给 Fragment 的属性。

createRef

React 官网中是这么解释 Refs and the DOM

Refs are created using React.createRef() and attached to React elements via the ref attribute. 
Refs are commonly assigned to an instance property when a component is constructed so they can
be referenced throughout the component.

使用 React.createRef() 创建 refs,通过 ref 属性来获得 React 元素。当构造组件时,refs 通常被赋值给实例的一个属性,这样你可以在组件中任意一处使用它们。通过 current 属性取得 DOM 节点

execCommand

当一个 HTML 文档切换到设计模式时,document 暴露 execCommand 方法,该方法允许运行命令来操纵可编辑内容区域的元素。

大多数命令影响 document 的 selection(粗体,斜体等),当其他命令插入新元素(添加链接)或影响整行(缩进)。当使用 contentEditable 时,调用 execCommand() 将影响当前活动的可编辑元素。

但是 document.execCommand(xxxx) 是 IE 独家提供的,有些功能在 Chrome/FrieFox 中是不支持的,比如 粘贴功能 document.execCommand("paste", "false", null)

富文本编辑器

在了解以上两个 React 属性之后,附上手写 editor web 富文本编辑器的 js 代码

  • js 代码
import React, { Component, Fragment, createRef } from "react";
import { Select } from 'antd';
import './index.less';

const Option = Select.Option;

class WqjiaoEditor extends Component {
    
    constructor(props) {
        super(props);
        this.state = {
            editorIcons: [{
                id: 'choose-all',
                text: '全选',
                event: this.chooseAll
            }, {
                id: 'copy',
                text: '复制',
                event: this.copy
            }, {
                id: 'cut',
                text: '剪切',
                event: this.cut
            }, {
                id: 'bold',
                text: '加粗',
                event: this.bold
            }, {
                id: 'italic',
                text: '斜体',
                event: this.italic
            }, {
                id: 'font-size',
                text: '字体大小',
                event: this.fontSize
            }, {
                id: 'underline',
                text: '下划线',
                event: this.underline
            }, {
                id: 'background-color',
                text: '背景色',
                event: this.backgroundColor
            }],
            fontSizeOption: [],
            isShow: false,
            fontSize: '7'
        }
    }

    document = createRef(null);

    componentDidMount() {
        this.editor = this.document.current.contentDocument;
        this.editor.designMode = 'On';
        this.editor.contentEditable = true;

        let fontSizeOption = [];
        // 字体大小数组
        for (let i = 1; i <= 7; i ++) {
            fontSizeOption.push(i);                                                
        }

        this.setState({
            fontSizeOption
        });
    }

    // 全选
    chooseAll = () => {
        this.editor.execCommand('selectAll');
    }

    // 复制
    copy = () => {
        this.editor.execCommand('copy');
    }

    // 剪切
    cut = () => {
        this.editor.execCommand('cut');
    }

    // 加粗
    bold = () => {
        this.editor.execCommand('bold');
    }

    // 斜体
    italic = () => {
        this.editor.execCommand('italic');
    }

    // 字体大小
    fontSize = () => {
        let me = this;     
    }

    onClick(id) {
        if (id === 'font-size') {
            this.setState({
                isShow: true
            });
        }
    }

    onChange(value) {
        this.setState({
            fontSize: value,
            isShow: false
        })
        this.editor.execCommand('fontSize', true, value);
    }

    // 下划线
    underline = () => {
        this.editor.execCommand('underline');
    }

    // 背景色
    backgroundColor = () => {
        this.editor.execCommand('backColor', true, '#e5e5e5');
    }

    render() {
        let me = this;
        let { editorIcons, isShow, fontSize, fontSizeOption } = me.state;

        return (
            <Fragment>
                <div className="wqjiao-editor">
                    <div className="wqjiao-editor-icon">
                        <ul className="wqjiao-icon-list clearfix">
                            { editorIcons && editorIcons.map((item, index) => {
                                return (
                                    <li
                                        className="wqjiao-icon-item"
                                        onClick={item.event}
                                        key={'editor' + index}
                                    >
                                        <i
                                            className={"wqjiao-i i-" + item.id}
                                            title={item.text}
                                            alt={item.text}
                                            onClick={me.onClick.bind(me, item.id)}
                                        />
                                        { (item.id === 'font-size' && isShow) && <div className="wqjiao-editor-select">
                                            <Select
                                                value={fontSize}
                                                onChange={me.onChange.bind(me)}
                                            >
                                                { fontSizeOption && fontSizeOption.map((i, k) => {
                                                    return (
                                                        <Option
                                                            key={'fontSize' + k}
                                                            value={i}
                                                        >{i}</Option>
                                                    );
                                                }) }
                                            </Select>
                                        </div> }
                                    </li>
                                );
                            }) }
                        </ul>
                    </div>
                    <iframe ref={this.document} className="wqjiao-editor-textarea"></iframe>
                </div>
            </Fragment>
        )
    }
}

export default WqjiaoEditor;
  • css 样式,图片本地添加
// wqjiao editor web 富文本编辑器
::-webkit-scrollbar {
    display: none;
}

.wqjiao-editor {
    // width: 100%;
    width: 300px;
    
    // 样式重置
    * {
        margin: 0;
        padding: 0;
        list-style: none;
        font-style: normal;
    }

    // editor 图标
    .wqjiao-editor-icon {
        width: 100%;
        border: 1px solid #e5e5e5;
        border-top-left-radius: 4px;
        border-top-right-radius: 4px;
        box-sizing: border-box;
        .wqjiao-icon-list {
            padding: 0 5px;
        }
        .wqjiao-icon-item {
            float: left;
            font-size: 10px;
            padding: 5px 10px;
            position: relative;
        }
        .wqjiao-editor-select {
            position: absolute;
            top: 20px;
            left: -3px;
            width: 40px;
        }
        .ant-select {
            width: 100%;
        }
        .ant-select-selection__rendered,
        .ant-select-selection--single {
            height: 20px;
            line-height: 20px;
        }
        .ant-select-arrow {
            top: 4px;
            right: 4px;
        }
        .ant-select-selection-selected-value {
            padding: 0;
        }
        .ant-select-selection__rendered {
            margin: 0;
            margin-left: 4px;
        }
    }

    // editor 文本区域
    .wqjiao-editor-textarea {
        width: 100%;
        min-height: 200px;
        font-size: 14px;
        padding: 10px;
        border: 1px solid #e5e5e5;
        border-top: none;
        border-bottom-left-radius: 4px;
        border-bottom-right-radius: 4px;
        box-sizing: border-box;
        &:hover,
        &:focus {
            outline: none;
            box-shadow: none;
        }
    }

    // 清除浮动元素带来的影响
    .clearfix {
        zoom: 1;
    }
    .clearfix:after {
        content: "";
        clear: both;
        height: 0;
        visibility: hidden;
        display: block;
    }

    // 图标背景
    .wqjiao-i {
        display: block;
        width: 14px;
        height: 14px;
        cursor: pointer;
        &.i-choose-all {
           background: url('./img/i-choose-all.png');
        }
        &.i-copy {
           background: url('./img/i-copy.png'); 
        }
        &.i-cut {
           background: url('./img/i-cut.png'); 
        }
        &.i-bold {
            background: url('./img/i-bold.png'); 
        }
        &.i-italic {
            background: url('./img/i-italic.png'); 
        }
        &.i-font-size {
            background: url('./img/i-font-size.png'); 
        }
        &.i-underline {
            background: url('./img/i-underline.png'); 
        }
        &.i-background-color {
            background: url('./img/i-background-color.png'); 
        }
        &:hover,
        &.active {
            &.i-choose-all {
                background: url('./img/i-choose-all-active.png');
            }
            &.i-copy {
                background: url('./img/i-copy-active.png'); 
            }
            &.i-cut {
                background: url('./img/i-cut-active.png'); 
            }
            &.i-bold {
                background: url('./img/i-bold-active.png'); 
            }
            &.i-italic {
                background: url('./img/i-italic-active.png'); 
            }
            &.i-font-size {
                background: url('./img/i-font-size-active.png'); 
            }
            &.i-underline {
                background: url('./img/i-underline-active.png'); 
            }
            &.i-background-color {
                background: url('./img/i-background-color-active.png'); 
            }
        }
    }
}
  • 效果演示
  • execCommand 兼容性
点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
4年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Jacquelyn38 Jacquelyn38
4年前
一篇文章带你使用Typescript封装一个Vue组件
搭建项目以及初始化配置vue create tsvuebtn这里使用了vueCLI3自定义选择的服务,我选择了ts、stylus等工具。然后创建完项目之后,进入项目。使用快捷命令code.进入Vscode编辑器(如果没有code.,需要将编辑器的「bin文件目录地址」放到环境变量的path中)。然后,我进入编辑器之后,进入设置工作区,随便设置一个
编程范儿 编程范儿
4年前
项目中的富文本编辑器该如何选择?
项目中经常需要用到富文本编辑器的时候,而常见的富文本编辑器都有哪些?该如何选择?先看看市面上都有哪些可用的富文本编辑器:(插件式的,支持Vue,React,Angular框架)(Typescript开发的Web富文本编辑器,轻量、简洁、易用、开源免费,支持JS直接引入使用,或者Vue2/3,React)(开源,插件多,功能齐全,支持
Wesley13 Wesley13
4年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
4年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
4年前
PowerDesigner列名、注释内容互换
在用PowerDesigner时,常常在NAME或Comment中写中文在Code中写英文,Name只会显示给我们看,Code会使用在代码中,但Comment中的文字会保存到数据库TABLE的Description中,有时候我们写好了Name再写一次Comment很麻烦,以下两段代码就可以解决这个问题。在PowerDesigner中PowerDesig
新增富文本单元格和XSS过滤器
一.富文本单元格        皕杰设计器新增了单元格富文本类型,我们在一些网站编辑文章的时候经常可以看到富文本和markdown等编辑器,其中以Word为例,输入文字后,选择不同的功能(通常是通过点击某个图标),例如加粗或者调整字体大小,处理
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
GeorgeGcs GeorgeGcs
8个月前
【HarmonyOS】富文本编辑器RichEditor详解
【HarmonyOS】富文本编辑器RichEditor详解一、前言在信息化高速发展的今天,普通的文本容器,已经不能够承载用户丰富的表达欲。富文本展示已经是移动开发中,必备要解决的问题,在鸿蒙中,通过在系统层提供RichEditor控件,来解决富文本展示的问
蚀纹容器
蚀纹容器
Lv1
慊慊思归恋故乡,君为淹留寄他方。
文章
6
粉丝
0
获赞
0