手寫富文本編輯器,需要使用 react
中自帶的 Fragment
和 createRef
,以及 execCommand
方法,這兩個到底有什麼作用呢。那麼,在演示代碼前先了解下 Fragment
、 createRef
及 execCommand
是什麼,分別有什麼作用?
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 兼容性