使用typescript&webpack从0搭建
实现功能
- 加粗
- 设置标题
- 设置颜色
如何启用
- js提供了一个 api:contenteditable 允许我们对内容进行编辑。即在标签中加入
contenteditable - document对象暴露出一个execCommand方法去操纵当前的可编辑区域。即
document.execCommand()
实现步骤
- 搭建工程目录
- 新建目录: editor
- 进入目录 执行
npm init初始化目录默认即可 - 安装所需插件
npm install webpack webpack-cli dev-server clean-webpack-plugin html-webpack-plugin cross-env typescript ts-loader style-loader css-loader less-loader less --save-dev
# 这里踩了个坑 后来把webpack版本改为了^4.43.0 webpack-cli版本改为了^3.3.11
# 重新npm i了
- 初始化ts文件
tsc --init会出现 - webpack配置 新增目录build并创建webpack.config.js文件
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
//使用node的模块
module.exports = {
//这就是我们项目编译的入口文件
entry: "./src/index.ts",
output: {
filename: "main.js"
},
resolve: {
extensions: ['.ts', '.js', '.json'],
},
//这里可以配置一些对指定文件的处理
//这里匹配后缀为ts或者tsx的文件
//使用exclude来排除一些文件
module:{
rules:[
{
test:/\.tsx?$/,
use:'ts-loader',
exclude: /node_modules/
},
{
test:/\.less$/,
use:['style-loader','css-loader','less-loader']
}
]
},
//这个参数就可以在webpack中获取到了
devtool: process.env.NODE_ENV === 'production'? false : 'inline-source-map',
devServer:{
//这个本地开发环境运行时是基于哪个文件夹作为根目录
contentBase:'./dist',
//当你有错误的时候在控制台打出
stats: 'errors-only',
//不启动压缩
compress: false,
host: 'localhost',
port: 8081
},
//这里就是一些插件
plugins:[
new CleanWebpackPlugin({
cleanOnceBeforeBuildPatterns: ['./dist']
}),
new HtmlWebpackPlugin({
template: './src/index.html'
})
]
}
- 新建文件 新建src 并创建index.ts,index.html 新建css文件夹 并创建index.less 目录结构如下:
├── README.md
├── build
│ └── webpack.config.js
├── package.json
├── src
│ ├── css
│ │ └── index.less
│ ├── index.html
│ └── index.ts
└── tsconfig.json
- 在package.json文件中的scripts下新增一条打包命令
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "cross-env NODE_ENV=production webpack --config ./build/webpack.config.js"
},
至此 目录搭建完成
2.编辑页面 在index.html中编写页面骨架
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>富文本编辑器</title>
</head>
<body>
<div>
<div class="btn_box" id="blod">加粗</div>
<div class="btn_box">
<div>设置标题</div>
<ul>
<li class="title_name">h1</li>
<li class="title_name">h2</li>
<li class="title_name">h3</li>
<li class="title_name">h4</li>
<li class="title_name">正文</li>
</ul>
</div>
<div class="btn_box">
<div>设置颜色</div>
<ul>
<li class="color_name">red</li>
<li class="color_name">blue</li>
<li class="color_name">yellow</li>
<li class="color_name">black</li>
</ul>
</div>
</div>
<div id="editor" contenteditable>
</body>
</html>
- index.less样式
ul li {
list-style-type: none;
}
#editor {
width: 400px;
height: 200px;
border: 1px solid #ddd;
margin-top: 10px;
}
.btn_box {
display: inline-block;
border: 1px solid #ddd;
padding: 5px 10px;
position: relative;
cursor: pointer;
margin-right: 10px;
ul {
display: none;
position: absolute;
left: 0;
top: 15px;
padding: 0;
border: 1px solid #ddd;
z-index: 99;
background: #fff;
width: 100%;
text-align: center;
li {
.title_name, .color_name {
cursor: pointer;
}
&:hover {
background: #eee;
}
}
}
&:hover {
ul {
display: block;
}
}
}
- 实现ts中的逻辑
踩坑指南
最早我是尝试使用botton实现加粗功能一切正常
后来因为增加了标题和设置颜色的下拉菜单 统一更改为div组件 这个时候div的click事件其实是正常执行了
但是click事件会导致富文本编辑框失去焦点,因此execCommand函数无能为力
最后改用了mousedown来作为监听同时还要组织默认事件,否则富文本编辑框的选中区域会消失
对了还要阻止默认事件,否则富文本编辑框的选中区域会消失`e.preventDefault()`否则
code
import './css/index.less'
class Menus {
public element: HTMLElement
public value: string
public target: string
constructor(element: HTMLElement, target: string, value: string) {
this.element = element as HTMLDivElement
this.value = value
this.target = target
}
// 初始化
init () {
this.element.onmousedown = (e: Event) => {
document.execCommand(this.target, false, this.value)
e.preventDefault()
}
}
}
window.onload = () => {
let blod = document.querySelector('#blod') as HTMLElement
let title = document.querySelectorAll('.title_name')
let color = document.querySelectorAll('.color_name')
// 加粗
let blodMenu = new Menus(blod, 'bold', 'null')
blodMenu.init()
// 设置标题
title.forEach(item => {
let li = item as HTMLElement
let titleMenu: Menus
if (li.innerHTML === '正文') {
titleMenu = new Menus(li, 'formatblock', '<p>')
} else {
titleMenu = new Menus(li, 'formatblock', li.innerHTML)
}
titleMenu.init()
})
// 设置颜色
color.forEach(item => {
let li = item as HTMLElement
let colorMenu = new Menus(li, 'forecolor', li.innerHTML)
colorMenu.init()
})
}
记录选区
以上有个缺陷
- 在编辑区写入内容然后选中一段文本,俗称拖蓝
- 鼠标在编辑区外点击
- 点击加粗
加粗功能无效了
解决思路
- 当
onmouseup,onkeyup,onmouseout编辑区的时候获取选区并存储range
saveSelection () {
const selection = window.getSelection() as Selection
if (selection.rangeCount === 0) {
return
}
const range: Range | null | undefined = selection.getRangeAt(0)
let elem: Node
let containerElem
if (range) {
elem = range.commonAncestorContainer
containerElem = elem.nodeType === 1 ? elem : elem.parentNode
}
if (!containerElem) return
// 编辑区是否包含选区内容
if(this.editor.contains(containerElem)) {
this.range = range
}
}
- 当点击加粗按钮时读取上次的range并判断该range内容是否存在于当前的编辑器区的内容中存在则我们回复选区
resetSelection () {
const selection = window.getSelection() as Selection
selection.removeAllRanges()
if (this.range) {
selection.addRange(this.range)
}
}
Menus中的init
// 初始化
init (): void {
this.element.onmousedown = (e: Event) => {
this.selectionRange.resetSelection()
document.execCommand(this.target, false, this.value)
e.preventDefault()
}
this.element.onmouseup = (e: Event) => {
this.selectionRange.saveSelection()
}
}
重复以下动作
1. 在编辑区写入内容然后选中一段文本,俗称拖蓝
2. 鼠标在编辑区外点击
3. 点击加粗
拖蓝区已可以加粗了。