1. 项目概述
在Web开发中,实现页面内容的搜索高亮功能是一个常见需求。mark.js是一个轻量级的JavaScript库,专门用于在网页中高亮显示搜索关键词。它不依赖jQuery,支持多种匹配选项,并且性能优异。
我在最近的一个Vue项目中使用了mark.js来实现文档内容的搜索高亮功能。这个项目是一个企业内部的知识管理系统,用户需要快速查找文档中的特定内容。通过mark.js,我们实现了以下功能:
- 关键词实时高亮显示
- 匹配结果计数
- 结果导航(上一个/下一个)
- 快捷键支持(Ctrl+F)
- 深色主题适配
2. 核心功能实现
2.1 安装与初始化
首先需要通过npm安装mark.js:
bash复制npm install mark.js
在Vue组件中,我们需要导入mark.js并初始化实例:
javascript复制import Mark from 'mark.js'
// 在setup函数中
const container = document.querySelector('.content-area')
const markInstance = new Mark(container)
这里有几个关键点需要注意:
- 容器选择:应该选择包含所有可搜索内容的最小容器,避免选择过大范围影响性能
- 实例创建:mark.js实例应该在组件挂载时创建,并在卸载时清理
- 性能考虑:对于大型文档,可以考虑延迟初始化或按需初始化
2.2 搜索功能实现
核心搜索函数需要处理以下几个步骤:
- 清除之前的高亮标记
- 执行新的搜索
- 更新结果计数
- 定位到第一个匹配项
javascript复制const handleSearch = () => {
if (!markInstance) return
markInstance.unmark({
done: () => {
if (!searchText.value) {
totalCount.value = 0
currentIndex.value = 0
return
}
markInstance.mark(searchText.value, {
className: 'search-mark',
done: (count) => {
totalCount.value = count
currentIndex.value = count > 0 ? 1 : 0
if (count > 0) scrollToMatch(0)
}
})
}
})
}
注意:unmark操作是异步的,必须在其回调函数中执行mark操作,否则可能会出现标记残留问题。
2.3 结果导航功能
为了方便用户在多个匹配结果间导航,我们实现了上一个/下一个功能:
javascript复制const nextMatch = () => {
if (totalCount.value === 0) return
currentIndex.value = currentIndex.value >= totalCount.value ? 1 : currentIndex.value + 1
scrollToMatch(currentIndex.value - 1)
}
const prevMatch = () => {
if (totalCount.value === 0) return
currentIndex.value = currentIndex.value <= 1 ? totalCount.value : currentIndex.value - 1
scrollToMatch(currentIndex.value - 1)
}
导航功能的关键在于维护当前索引和滚动到对应元素。滚动函数需要考虑页面布局和用户体验:
javascript复制const scrollToMatch = (index) => {
const marks = document.querySelectorAll('.search-mark')
if (marks.length === 0) return
// 移除之前激活的样式
marks.forEach(m => m.classList.remove('is-active'))
const target = marks[index]
target.classList.add('is-active')
target.scrollIntoView({
behavior: 'smooth',
block: 'center'
})
}
3. 用户界面实现
3.1 搜索框组件
搜索框需要提供以下功能:
- 输入关键词
- 显示匹配结果计数
- 导航按钮
- 关闭搜索功能
html复制<div class="search-bar-wrapper" v-if="showSearch">
<div class="search-bar">
<input
v-model="searchText"
@input="handleSearch"
placeholder="搜索页面内容..."
@keydown.enter="nextMatch"
/>
<span class="count">{{ currentIndex }} / {{ totalCount }}</span>
<div class="btns">
<button @click="prevMatch">↑</button>
<button @click="nextMatch">↓</button>
<button @click="showSearch = false; searchText = ''; handleSearch()">✕</button>
</div>
</div>
</div>
3.2 快捷键支持
为了提升用户体验,我们添加了Ctrl+F快捷键来快速打开搜索框:
javascript复制const handleKeydown = (e) => {
if (e.ctrlKey && e.key === 'f') {
e.preventDefault()
showSearch.value = true
nextTick(() => {
document.querySelector('.search-bar input')?.focus()
})
}
}
onMounted(() => {
window.addEventListener('keydown', handleKeydown)
})
onUnmounted(() => {
window.removeEventListener('keydown', handleKeydown)
})
4. 样式与主题适配
4.1 搜索框样式
搜索框需要醒目但不突兀,特别是在深色主题下:
css复制.search-bar-wrapper {
position: sticky;
top: 0;
z-index: 9999;
display: flex;
justify-content: center;
margin-bottom: 10px;
}
.search-bar {
background: rgba(0, 40, 90, 0.95);
border: 1px solid #00e5ff;
padding: 8px 15px;
border-radius: 4px;
display: flex;
align-items: center;
gap: 12px;
box-shadow: 0 0 15px rgba(0, 229, 255, 0.3);
}
4.2 高亮样式
高亮样式需要考虑可读性和视觉反馈:
css复制.search-mark {
background-color: rgba(255, 255, 0, 0.8) !important;
color: #000 !important;
border-radius: 2px;
padding: 0 2px;
transition: all 0.2s;
}
.search-mark.is-active {
background-color: #ff9800 !important;
box-shadow: 0 0 8px #fff;
font-weight: bold;
}
提示:在Vue的scoped样式中,需要使用:deep()选择器来修改子组件中的样式
5. 高级功能与优化
5.1 动态内容处理
如果页面内容是异步加载的,需要在数据加载完成后重新执行搜索:
javascript复制const fetchData = async () => {
// 原有数据获取逻辑
await nextTick()
if (searchText.value) handleSearch()
}
5.2 性能优化
对于大型文档,可以考虑以下优化措施:
- 防抖处理搜索输入
- 限制搜索范围
- 分批处理标记操作
- 使用web worker进行后台搜索
javascript复制const debouncedSearch = debounce(handleSearch, 300)
// 在模板中
<input @input="debouncedSearch" />
5.3 高级搜索选项
mark.js支持多种搜索选项,可以根据需求进行配置:
javascript复制markInstance.mark(searchText.value, {
className: 'search-mark',
separateWordSearch: true, // 允许单词分开匹配
diacritics: true, // 忽略音标差异
accuracy: 'partially', // 部分匹配
done: (count) => {
// 回调处理
}
})
6. 常见问题与解决方案
6.1 高亮不显示
可能原因及解决方案:
- 容器选择错误 - 确保选择正确的DOM容器
- 样式冲突 - 检查CSS特异性,必要时使用!important
- 内容未加载 - 在数据加载完成后执行搜索
6.2 性能问题
对于大型文档:
- 减少搜索范围
- 使用更简单的标记样式
- 考虑虚拟滚动技术
6.3 特殊字符处理
如果搜索内容包含正则表达式特殊字符,需要先转义:
javascript复制function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}
markInstance.mark(escapeRegExp(searchText.value), {
// 选项
})
7. 实际应用中的经验分享
在实际项目中应用mark.js时,我总结了以下几点经验:
-
容器选择要精确:选择过大的容器会导致性能下降,选择过小可能会遗漏内容。最佳实践是选择包含所有可搜索内容的最小容器。
-
样式设计要考虑对比度:特别是在深色主题下,要确保高亮颜色与背景有足够的对比度,同时也要考虑当前选中项的特殊样式。
-
异步处理要小心:在Vue中,数据加载和DOM更新是异步的,确保在正确的时机执行搜索操作。使用nextTick可以确保DOM已经更新。
-
移动端适配:在移动设备上,要考虑虚拟键盘的影响,可能需要调整搜索框的位置或添加额外的关闭按钮。
-
无障碍访问:为搜索功能添加适当的ARIA属性,确保屏幕阅读器用户可以理解和使用搜索功能。
-
性能监控:对于内容特别多的页面,建议添加性能监控,记录搜索操作的耗时,必要时进行优化。
-
备选方案:虽然mark.js很好用,但在某些特殊场景下(如需要服务端搜索或全文索引),可能需要考虑其他解决方案如Elasticsearch或Algolia。
通过这个项目,我发现mark.js是一个非常实用的库,它简单易用但功能强大,能够满足大多数前端搜索高亮的需求。特别是在Vue等现代前端框架中,通过合理的封装和集成,可以创建出用户体验良好的搜索功能。