富文本編輯器,可以使web
頁面像word
同樣,實現對文本的編輯,一般應用在一些文本處理比較多的系統中。如今業界有不少成熟的富文本編輯器,好比功能齊全啊TinyMCE、輕量高效的wangEditor、百度出品的UEditor等。富文本編輯器不少,可是卻不多思考如何從零開始,實現一個富文本編輯器。本文主要簡述如何從零開始,實現一個簡易的富文本編輯器。html
普通的HTML
標籤,可以輸入的一般只是表單,表單輸入的是純文本,不帶格式的內容。富文本相對於表單,可以給輸入文本內容增長一些自定義內容樣式,好比加粗、字體顏色、背景...。富文本的實現,主要是給HTML標籤,好比div
增長一個contenteditable
屬性,擁有該屬性的HTML
標籤,就可以對該標籤裏的內容,實現自定義的編輯。最簡單的富文本編輯器以下:web
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app" style="width: 200px;height: 200px;background-color: antiquewhite;" contenteditable='true'></div>
</body>
</html>
複製代碼
富文本相似於Word,有不少操做文本選項,好比文本的加粗、添加背景顏色、段落縮進等,使用方式是命令式的,只須要執行document.execCommand(aCommandName, aShowDefaultUI, aValueArgument)
,其中aCommandName
是命令名稱,aShowDefaultUI
一個 Boolean
, 是否展現用戶界面,通常爲 false
。Mozilla 沒有實現。aValueArgument
,額外參數,通常爲null
。瀏覽器
如下簡單列舉一些富文本操做命令,下面給出一些例子的簡單使用bash
命令 | 值 | 說明 |
---|---|---|
backcolor | 顏色字符串 | 設置文檔的背景顏色 |
bold | null | 將選擇的文本加粗 |
createlink | URL字符串 | 將選擇的文本轉換成一個連接,指向指定的URL |
indent | null | 縮進文本 |
copy | null | 將選擇的文本複製到剪切板 |
cut | null | 將選擇文本剪切到剪切板 |
inserthorizontalrule | null | 在插入字符處插入一個hr元素 |
Example:app
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello World!</title>
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
<style>
html, body{
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
#app{
display: flex;
flex-direction: column;
justify-content: flex-start;
width: calc(100% - 100px);
height: calc(100% - 100px);
padding: 50px;
}
.operator-menu{
display: flex;
justify-content: flex-start;
align-items: center;
width: 100%;
min-height: 50px;
background-color: beige;
padding: 0 10px;
}
.edit-area{
width: 100%;
min-height: 600px;
background-color: blanchedalmond;
padding: 20px;
}
.operator-menu-item{
padding: 5px 10px;
background-color: cyan;
border-radius: 10px;
cursor: pointer;
margin: 0 5px;
}
</style>
</head>
<body>
<div id="app">
<div class="operator-menu">
<div class="operator-menu-item" data-fun='fontBold'>加粗</div>
<div class="operator-menu-item" data-fun='textIndent'>縮進</div>
<div class="operator-menu-item" data-fun='inserthorizontalrule'>插入分隔符</div>
<div class="operator-menu-item" data-fun='linkUrl'>連接百度</div>
</div>
<div class="edit-area" contenteditable="true"></div>
</div>
<script>
let operationItems = document.querySelector('.operator-menu')
// 事件監聽採用mousedown,click事件會致使富文本編輯框失去焦點
operationItems.addEventListener('mousedown', function(e) {
let target = e.target
let funName = target.getAttribute('data-fun')
if (!window[funName]) return
window[funName]()
// 要阻止默認事件,不然富文本編輯框的選中區域會消失
e.preventDefault()
})
function fontBold () {
document.execCommand('bold')
}
function textIndent () {
document.execCommand('indent')
}
function inserthorizontalrule () {
document.execCommand('inserthorizontalrule')
}
function linkUrl () {
document.execCommand('createlink', null, 'www.baidu.com')
}
</script>
</body>
</html>
複製代碼
Selection
和Range
對象。用比較官方的說法是,Selection
對象,表示用戶選擇的文本範圍或光標的當前位置,Range
對象表示一個包含節點與文本節點的一部分的文檔片斷。簡單來講,Selection
是指頁面中,咱們鼠標選中的全部區域,Range
是指頁面中咱們鼠標選中的單個區域,屬於一對多的關係。好比,咱們要獲取當前頁面的選區對象,能夠調用var selection = window.getSelection()
,若是想要獲取到第一個文本選區信息,能夠調用var rang = selection.getRangeAt(0)
,獲取到選區文本信息,採用range.toString()
。(paste)
,在粘貼文本時,將剪切板內容替換掉。這一個裏面也是有坑的,粘貼時操做剪切板是不生效的。在實現功能需求時,最初採用的是正則匹配,去除HTML標籤。奈何文本格式五花八門,常常出現各類奇奇怪怪的字符,問題比較多,並且複製大文本時,頁面存在性能問題,這並非一種好的處理方式,直到後來真正理解了文本範圍與選區,才發現這個設置,真香。 富文本選區的處理邏輯大體思路以下:let $editArea = document.querySelector('.edit-area')
$editArea.addEventListener('paste', e => {
// 阻止默認的複製事件
e.preventDefault()
let txt = ''
let range = null
// 獲取複製的文本
txt = e.clipboardData.getData('text/plain')
// 獲取頁面文本選區
range = window.getSelection().getRangeAt(0)
// 刪除默認選中文本
range.deleteContents()
// 建立一個文本節點,用於替換選區文本
let pasteTxt = document.createTextNode(txt)
// 插入文本節點
range.insertNode(pasteTxt)
// 將焦點移動到複製文本結尾
range.collapse(false)
})
複製代碼
除此以外,還有不少操做能夠藉助於選區來實現,好比光標的定位、選中區域內容包裹其餘樣式等。electron
function keepLastIndex(element) {
if (element && element.focus){
element.focus();
} else {
return
}
let range = document.createRange();
range.selectNodeContents(element);
range.collapse(false);
let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
複製代碼
function addCode () {
let selection = window.getSelection()
// 暫時處理第一個選區
let range = selection.getRangeAt(0)
// 拷貝一份原始選中數據
let cloneNodes = range.cloneContents()
// 移除選區
range.deleteContents()
// 建立內容容器
let codeContainer = document.createElement('code')
codeContainer.appendChild(cloneNodes)
// 往選區內添加文本
range.insertNode(codeContainer)
}
複製代碼
如下爲測試代碼編輯器
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello World!</title>
<!-- https://electronjs.org/docs/tutorial/security#csp-meta-tag -->
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
<style>
html, body{
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
#app{
display: flex;
flex-direction: column;
justify-content: flex-start;
width: calc(100% - 100px);
height: calc(100% - 100px);
padding: 50px;
}
.operator-menu{
display: flex;
justify-content: flex-start;
align-items: center;
width: 100%;
min-height: 50px;
background-color: beige;
padding: 0 10px;
}
.edit-area{
width: 100%;
min-height: 600px;
background-color: blanchedalmond;
padding: 20px;
}
.operator-menu-item{
padding: 5px 10px;
background-color: cyan;
border-radius: 10px;
cursor: pointer;
margin: 0 5px;
}
</style>
</head>
<body>
<div id="app">
<div class="operator-menu">
<div class="operator-menu-item" data-fun='fontBold'>加粗</div>
<div class="operator-menu-item" data-fun='textIndent'>縮進</div>
<div class="operator-menu-item" data-fun='inserthorizontalrule'>插入分隔符</div>
<div class="operator-menu-item" data-fun='linkUrl'>連接百度</div>
<div class="operator-menu-item" data-fun='addCode'>code</div>
</div>
<div class="edit-area" contenteditable="true"></div>
</div>
<script>
let operationItems = document.querySelector('.operator-menu')
// 事件監聽採用mousedown,click事件會致使富文本編輯框失去焦點
operationItems.addEventListener('mousedown', function(e) {
let target = e.target
let funName = target.getAttribute('data-fun')
if (!funName) return
window[funName]()
// 要阻止默認事件,不然富文本編輯框的選中區域會消失
e.preventDefault()
})
let $editArea = document.querySelector('.edit-area')
$editArea.addEventListener('paste', e => {
// 阻止默認的複製事件
e.preventDefault()
let txt = ''
let range = null
// 獲取複製的文本
txt = e.clipboardData.getData('text/plain')
// 獲取頁面文本選區
range = window.getSelection().getRangeAt(0)
// 刪除默認選中文本
range.deleteContents()
// 建立一個文本節點,用於替換選區文本
let pasteTxt = document.createTextNode(txt)
// 插入文本節點
range.insertNode(pasteTxt)
// 將焦點移動到複製文本結尾
range.collapse(false)
keepLastIndex($editArea)
})
function fontBold () {
document.execCommand('bold')
}
function textIndent () {
document.execCommand('indent')
}
function inserthorizontalrule () {
document.execCommand('inserthorizontalrule')
}
function linkUrl () {
document.execCommand('createlink', null, 'www.baidu.com')
}
function addCode () {
let selection = window.getSelection()
// 暫時處理第一個選區
let range = selection.getRangeAt(0)
// 拷貝一份原始選中數據
let cloneNodes = range.cloneContents()
// 移除選區
range.deleteContents()
// 建立內容容器
let codeContainer = document.createElement('code')
codeContainer.appendChild(cloneNodes)
// 往選區內添加文本
range.insertNode(codeContainer)
}
function keepLastIndex(element) {
if (element && element.focus){
element.focus();
} else {
return
}
let range = document.createRange();
range.selectNodeContents(element);
range.collapse(false);
let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
</script>
</body>
</html>
複製代碼