使用 Webpack + TypeScript 实现简易的富文本编辑器
分类:javascript
图片来源 unsplash.com/photos/b18T…
前言
写这篇文章的目的是为了记录自己第一次实现一个非常简易的富文本编辑器的过程,项目使用到了 Webpack 和 TypeScript。
项目介绍
项目目录文件
|-- 富文本编辑器,
|-- README.md,
|-- index.html,
|-- package-lock.json,
|-- package.json,
|-- tsconfig.json,
|-- webpack.build.config.js,
|-- webpack.config.js,
|-- dist,// 打包生成的文件
| |-- yangEditor.js,
|-- lib,
| |-- index.css,
| |-- index.js,
| |-- yangEditor.ts,
|-- src,
|-- index.js,
项目安装的依赖
"clean-webpack-plugin": "^3.0.0",
"css-loader": "^5.2.0",
"html-webpack-plugin": "^5.3.1",
"style-loader": "^2.0.0",
"ts-loader": "^8.1.0",
"typescript": "^4.2.3",
"webpack": "^5.30.0",
"webpack-cli": "^4.6.0",
"webpack-dev-server": "^3.11.2"
webpack.config.js
开发环境的 Webpack
配置
const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
mode: "development",
entry: "./src/index.js",
devServer: {
port: 8089, // 设置端口号
},
module: {
rules: [
{
test: /\.ts$/,
use: "ts-loader",
exclude: /node_modules/
},
{
test: /\.css$/,
use: ["style-loader", "css-loader"]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: "./index.html"
})
]
}
webpack.build.config.js
生产环境的 Webpack
配置
const path = require("path")
module.exports = {
mode: "production",
entry: "./lib/index.js",
output: {
filename: "yangEditor.js",
path: path.resolve(__dirname, "./dist"),
libraryTarget: 'umd'
},
module: {
rules: [
{
test: /\.ts$/,
use: "ts-loader",
exclude: /node_modules/
},
{
test: /\.css$/,
use: ["style-loader", "css-loader"]
}
]
}
}
项目运行命令
"scripts": {
"dev": "webpack serve --open Chrome.exe",
"build": "webpack --config webpack.build.config.js"
},
document.execCommand
document.execCommand 已废弃
当一个HTML文档切换到设计模式时,document
暴露 execCommand 方法,该方法允许运行命令来操纵可编辑内容区域的元素,本次项目实现富文本编辑器就是使用该它。
语法
bool = document.execCommand(aCommandName, aShowDefaultUI, aValueArgument)
,返回值是一个Boolean
,如果是false
则表示操作不被支持
或未被启用
。
参数
aCommandName
:一个 DOMString ,命令的名称。aShowDefaultUI
:是否展示用户界面,一般为 false。aValueArgument
:一些命令(例如insertImage)需要额外的参数(insertImage需要提供插入image的url),默认为null。
简易富文本具体实现
yangEditor.ts
该文件在lib/yangEditor.ts
首先我们创建一个类,传入一个DOM
节点字符串,获取DOM
节点。
export default class YangEditor {
constructor(dom: string) {
if (!dom) {
throw new Error('请传入DOM节点!!!');
}
let editWrapper: HTMLElement = document.getElementById(dom);
}
}
Command 指令
指令有设置标题,设置字体颜色,设置字体加粗。
// 指令数组
const actions = [
{
command: 'formatblock',
values: [
{ text: '- 设置标题大小 -', value: 'selected' },
{ text: 'H1标题', value: 'h1' },
{ text: 'H2标题', value: 'h2' },
{ text: 'H3标题', value: 'h3' },
{ text: 'H4标题', value: 'h4' },
{ text: 'H5标题', value: 'h5' },
],
},
{
command: 'forecolor',
values: [
{ text: '- 设置字体颜色 -', value: 'selected' },
{ text: '红色', value: 'red' },
{ text: '蓝色', value: 'blue' },
{ text: '绿色', value: 'green' },
{ text: '黑色', value: 'black' },
],
},
{
command: 'bold',
values: [
{ text: '- 设置字体粗体 -', value: 'selected' },
{ text: '粗体', value: '' },
],
},
];
创建DOM
节点
// 创建DOM节点
private createDOM(type: string, className?: string): HTMLElement {
let dom = document.createElement(type);
dom.className = className || '';
return dom;
}
动态创建 select
标签
// 创建select节点
private createSelectDOM(commandItem: ActionsItem): HTMLSelectElement {
let select = document.createElement('select');
commandItem.values.forEach((item) => {
select.add(new Option(item.text, item.value));
select.id = `${commandItem.command}`;
});
select.onchange = () => {
// select标签onchange事件,调用execCommand方法
this.execCommand(commandItem.command, select.options[select.selectedIndex].value);
};
return select;
}
使用 document.execCommand
方法
在select标签调佣onchange事件,调用execCommand方法。
private execCommand(cmd: string, value: string): void {
//execCommand bool = document.execCommand(aCommandName, aShowDefaultUI, aValueArgument)
// aCommandName 一个 DOMString ,命令的名称。
//aShowDefaultUI 一个 Boolean, 是否展示用户界面,一般为 false
//aValueArgument 一些命令(例如insertImage)需要额外的参数(insertImage需要提供插入image的url),默认为null。
document.execCommand(cmd, false, value);
}
点击编辑区域以外后点击指令依然有效
let range: Range = null; // 用于缓存Range对象
// 鼠标在编辑区域移动
editContent.onmousemove = _.debounce(() => {
let selection = window.getSelection();
if (!selection.isCollapsed) {
range = selection.getRangeAt(0); // 返回选区包含的指定区域(Range)的引用
}
}, 100);
// 创建select节点
private createSelectDOM(commandItem: ActionsItem): HTMLSelectElement {
let select = document.createElement('select');
commandItem.values.forEach((item) => {
select.add(new Option(item.text, item.value));
select.id = `${commandItem.command}`;
});
// select标签onchange事件
select.onchange = () => {
let selection = window.getSelection();
selection.removeRange(selection.getRangeAt(0)); // 将Range对象将从选区当中移除。
selection.addRange(range); // 一个区域(Range)对象将被加入选区。
this.execCommand(commandItem.command, select.options[select.selectedIndex].value);
};
return select;
}
使用方法
import { YangEditor } from "../lib/index"
new YangEditor("yangEdit")
实现效果
项目源码
编辑器源码地址
参考资料
MDN execCommand