概要
在后台管理系统中有的数据是用富文本编辑器编写的,然后在前台页面渲染出来,这时会有搜索高亮富文本内容的相应功能,以及点击查找下一个类似的。
例如:网站自带这种搜索关键字功能
整体架构流程
该功能使用mask.js实现富文本高亮,简易的使用了下mark的标记方法!
技术细节
该功能使用简单的html的demo来演示,首先引入mark.js,可以去下载或者直接通过bootcdn搜索引入库
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>富文本搜索高亮</title>
<script src="https://cdn.bootcdn.net/ajax/libs/mark.js/8.11.1/mark.min.js"></script>
</head>
编写一个测试富文本html内容
<div id="editor">
<span>我的亚<span>索很6</span></span>
<p>我的瞎子也不赖啊88一起啊</p>
<span>89我开了</span>
<span>8我开了9</span>
<span>8.9</span>
<span>8</span>
<span>9</span>
</div>
获取editor节点,使用Mark实例节点,然后使用mark方法标记文本内容,这样就能标记相应的文本信息
const searchValue = '8.9'
const editor = document.getElementById('editor')
const mark = new Mark(editor)
const searchList = searchValue.split('')
// 标记文本 先按照8和9标记,保证相关文本都标记为mark标签,标记完后默认是黄色背景
searchList.forEach(value => {
mark.mark(value)
})
这边为什么把标记的内容拆开然后在多次分别标记?
- 一些富文本内容可能由多个标签组成,例如如下89分开,但是如果直接调用mark方法高亮89就会不生效,所以分开高亮内容保证所有文本都能被选取到然后再做处理
调用mark方法后,会把高亮的内容嵌套一个mark标签,并且给了相应的默认黄色背景高亮色
接下来就是获取所有的mark标签,然后先暂时将这些的mark标签高亮背景色去掉,在去处理真正高亮的mark标签,循环获取的mark节点,每次增加数为高亮文本的长度,然后获取该长度之间每每首尾之间节点的文本内容,然后对比和高亮的文本内容是否相等,相等就给这个区间的mark节点高亮!
const marks = document.querySelectorAll('mark')
// 先将选中的mark背景色去掉
marks.forEach(markDom => markDom.style.background = 'transparent')
const len = searchList.length
for(let i = 0;i<marks.length;i += len) {
const startNode = marks[i]
const endNode = marks[i+len-1]
const betweenText = getDodeBetweenText(editor,startNode,endNode)
// 两个mark之间的文本信息和搜索的文本信息一致在给这之间的所有mark标记
if(betweenText === searchValue) {
for(let j=i;j<i+len;j++) {
const markDom = marks[j]
markDom && (markDom.style.background = 'yellow')
}
}
}
/**
* @description: 获取两元素之间的文本信息,包含头尾
* @param {*} rootNode 父节点
* @param {*} startNode 开始节点
* @param {*} endNode 结束节点
* @return {*}
*/
function getDodeBetweenText(rootNode,startNode,endNode) {
let pastStartNode = false, reachedEndNode = false, textNodes = []
function getTextNodes(node) {
if(node == startNode) {
pastStartNode = true
textNodes.push(node)
}else if(node == endNode) {
reachedEndNode = true
textNodes.push(node)
}else if(node.nodeType == 3) {
if(pastStartNode && !reachedEndNode && !/^\s*$/.test(node.nodeValue)) {
textNodes.push(node)
}
}else {
const len = [...node.childNodes].length
for(let i = 0;!reachedEndNode && i < len;i++) {
getTextNodes(node.childNodes[i])
}
}
}
if(startNode == endNode) {
return startNode.innerText
}else {
getTextNodes(rootNode)
return textNodes.map(i => i.textContent).join('')
}
}
整体代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>富文本搜索高亮</title>
<script src="https://cdn.bootcdn.net/ajax/libs/mark.js/8.11.1/mark.min.js"></script>
</head>
<body>
<div id="editor">
<span>我的亚<span>索很6</span></span>
<p>我的瞎子也不赖啊88一起啊</p>
<span>89我开了</span>
<span>8我开了9</span>
<span>8.9</span>
<span>8</span>
<span>9</span>
</div>
<script>
const searchValue = '8.9'
const editor = document.getElementById('editor')
const mark = new Mark(editor)
const searchList = searchValue.split('')
// 标记文本 先按照8和9标记,保证相关文本都标记为mark标签,标记完后默认是黄色背景
searchList.forEach(value => {
mark.mark(value)
})
const marks = document.querySelectorAll('mark')
// 先将选中的mark背景色去掉
marks.forEach(markDom => markDom.style.background = 'transparent')
const len = searchList.length
for(let i = 0;i<marks.length;i += len) {
const startNode = marks[i]
const endNode = marks[i+len-1]
const betweenText = getDodeBetweenText(editor,startNode,endNode)
// 两个mark之间的文本信息和搜索的文本信息一致在给这之间的所有mark标记
if(betweenText === searchValue) {
for(let j=i;j<i+len;j++) {
const markDom = marks[j]
markDom && (markDom.style.background = 'yellow')
}
}
}
/**
* @description: 获取两元素之间的文本信息,包含头尾
* @param {*} rootNode 父节点
* @param {*} startNode 开始节点
* @param {*} endNode 结束节点
* @return {*}
*/
function getDodeBetweenText(rootNode,startNode,endNode) {
let pastStartNode = false, reachedEndNode = false, textNodes = []
function getTextNodes(node) {
if(node == startNode) {
pastStartNode = true
textNodes.push(node)
}else if(node == endNode) {
reachedEndNode = true
textNodes.push(node)
}else if(node.nodeType == 3) {
if(pastStartNode && !reachedEndNode && !/^\s*$/.test(node.nodeValue)) {
textNodes.push(node)
}
}else {
const len = [...node.childNodes].length
for(let i = 0;!reachedEndNode && i < len;i++) {
getTextNodes(node.childNodes[i])
}
}
}
if(startNode == endNode) {
return startNode.innerText
}else {
getTextNodes(rootNode)
return textNodes.map(i => i.textContent).join('')
}
}
</script>
</body>
</html>
小结
富文本内容存在多种多样,以上处理并不完善,遇到特殊情况可以特殊处理。同时可以在该功能上实现查找下一个上一个功能。菜鸟一枚,感谢观看!