手寫 editor web 富文本編輯器

手寫富文本編輯器,需要使用 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 兼容性
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章