vue自定义组件——search-box
pre { overflow-y: auto; max-height: 300px }
github地址: https://github.com/lxmghct/my-vue-components
组件介绍
- props:
- value/v-model: 检索框的值, default: ''
- boxStyle: 检索框的样式, default: 'position: fixed; top: 0px; right: 100px;'
- highlightColor: 高亮颜色, default: 'rgb(246, 186, 130)'
- currentColor: 当前高亮颜色, default: 'rgb(246, 137, 31)'
- selectorList: 检索的选择器列表, default: []
- iFrameId: 检索的iframe的id, default: null, 若需要搜索iframe标签中的内容, 则将该参数设为目标iframe的id
- beforeJump: 跳转前的回调函数, default: () => {}
- afterJump: 跳转后的回调函数, default: () => {}
- (注: 上述两个回调函数参数为currentIndex, currentSelector, lastIndex, lastSelector)
- events:
- @search: 检索时触发, 参数为input和total
- @goto: 跳转时触发, 参数为index
- @close: 关闭时触发
- methods:
- clear() 清空检索框
- search() 检索
效果展示

设计思路
完整代码见github: https://github.com/lxmghct/my-vue-components
在其中的src/components/SearchBox下。
1. 界面
界面上比较简单, 输入框、当前/总数、上一个、下一个、关闭按钮。
<div class="search-box" :style="boxStyle">
<input
v-model="input"
placeholder="请输入检索内容"
class="search-input"
type="text"
@input="search"
>
<!--当前/总数、上一个、下一个、关闭-->
<span class="input-append">
{{ current }}/{{ total }}
</span>
<span class="input-append" @click="searchPrevious">
<div class="svg-container">
<svg width="100px" height="100px">
<path d="M 100 0 L 0 50 L 100 100" stroke="black" fill="transparent" stroke-linecap="round"/>
</svg>
</div>
</span>
<span class="input-append" @click="searchNext">
<div class="svg-container">
<svg width="100px" height="100px" transform="rotate(180)">
<path d="M 100 0 L 0 50 L 100 100" stroke="black" fill="transparent" stroke-linecap="round"/>
</svg>
</div>
</span>
<span class="input-append" @click="searchClose">
<div class="svg-container">
<svg width="100%" height="100%">
<line x1="0" y1="0" x2="100%" y2="100%" stroke="black" stroke-width="1" />
<line x1="100%" y1="0" x2="0" y2="100%" stroke="black" stroke-width="1" />
</svg>
</div>
</span>
</div>
2. 检索与跳转
这部分是search-box的核心功能,一共有以下几个需要解决的问题:
- 获取待搜索的容器
- 为提高组件的通用性,可以通过传入选择器列表来获取容器,如
['.container', '#containerId'],使用document.querySelector()获取容器。
- 为提高组件的通用性,可以通过传入选择器列表来获取容器,如
- 获取所有文本
- 不能单独对某个dom节点获取文本, 因为某个待搜索词可能被分割在多个节点中, 例如
<span>hello</span><span>world</span>,所以需要获取整个容器内的所有文本拼接起来, 然后再进行检索。 - 使用
innetText获取文本会受到样式影响, 具体见文章最后的其它问题。所以需要遍历所有节点将文本拼接起来。 - 遍历文本节点时, 可以用
node.nodeType === Node.TEXT_NODE判断是否为文本节点。
if (node.nodeType === Node.TEXT_NODE) { // text node
callback(node)
} else if (node.nodeType === Node.ELEMENT_NODE) { // element node
for (let i = 0; i < node.childNodes.length; i++) {
traverseTextDom(node.childNodes[i], callback)
}
}
- 不能单独对某个dom节点获取文本, 因为某个待搜索词可能被分割在多个节点中, 例如
- 检索结果的保存
- 由于查找完之后需要实现跳转, 所以为方便处理, 将检索到的结果所在的dom节点保存起来, 以便后续跳转时使用。每个结果对应一个domList。
- 高亮检索词
- 使用span标签包裹检索词, 并设置样式, 实现高亮。
- 为了避免检索词被html标签分割, 可以对检索词的每个字符都用span标签包裹, 例如检索词为
hello,则可以将其替换为<span>h</span><span>e</span><span>l</span><span>l</span><span>o</span>。 - 样式设置可以给span设置background-color, 为了方便修改并减小整体html长度, 可以改为给span设置class, 注意这种情况下在style标签设置的样式未必有效, 可以采用动态添加样式的方式。
function createCssStyle (css) {
const style = myDocument.createElement('style')
style.type = 'text/css'
try {
style.appendChild(myDocument.createTextNode(css))
} catch (ex) {
style.styleSheet.cssText = css
}
myDocument.getElementsByTagName('head')[0].appendChild(style)
}
- 将span标签插入到原先文本节点的位置, 若使用innerHtml直接进行替换, 处理起来略有些麻烦。可以考虑使用insertBefore和removeChild方法。
const tempNode = myDocument.createElement('span')
tempNode.innerHTML = textHtml
const children = tempNode.children
if (children) {
for (let i = 0; i < children.length; i++) {
domList.push(children[i])
}
}
// 将节点插入到parent的指定位置
// insertBofore会将节点从原来的位置移除,导致引错误,所以不能用forEach
while (tempNode.firstChild) {
parent.insertBefore(tempNode.firstChild, textNode)
}
parent.removeChild(textNode)
- 跳转
由于结果对应的dom节点已保存,所以跳转起来比较容易。跳转时修改当前高亮的dom节点的类名, 然后将其滚动到可视区域。setCurrent (index) {
const lastSelector = this.searchResult[this.currentIndex] ? this.searchResult[this.currentIndex].selector : null
const currentSelector = this.searchResult[index] ? this.searchResult[index].selector : null
if (this.currentIndex >= 0 && this.currentIndex < this.searchResult.length) {
this.searchResult[this.currentIndex].domList.forEach((dom) => {
dom.classList.remove(this.currentClass)
})
this.searchResult[this.currentIndex].domList[0].scrollIntoView({ behavior: 'smooth', block: 'center' })
}
this.currentIndex = index
if (this.currentIndex >= 0 && this.currentIndex < this.searchResult.length) {
this.searchResult[this.currentIndex].domList.forEach((dom) => {
dom.classList.add(this.currentClass)
})
}
}
- 移除高亮效果
- 由于高亮效果是通过给text节点添加span标签实现, 所以需要将span标签移除, 并替换为原先的文本节点。
- 使用
insertBefore和removeChild方法。 - 替换完节点后需要调用
normalize()方法, 将相邻的文本节点合并为一个文本节点。
function convertHighlightDomToTextNode (domList) {
if (!domList || !domList.length) { return }
domList.forEach(dom => {
if (dom && dom.parentNode) {
const parent = dom.parentNode
const textNode = myDocument.createTextNode(dom.textContent)
parent.insertBefore(textNode, dom)
parent.removeChild(dom)
parent.normalize() // 合并相邻的文本节点
}
})
}
3. 添加对iframe的支持
有时候页面中可能会包含iframe标签, 如果需要检索iframe中的内容, 直接使用当前的document是无法获取到iframe中的内容的, 需要拿到iframe的document对象。
const myIframe = document.getElementById(this.iframeId)
if (myIframe) {
myDocument = myIframe.contentDocument || myIframe.contentWindow.document
} else {
myDocument = document
}
if (myIframe && this.lastIframeSrc !== myIframesrc) {
const css = `.${this.highlightClass} { background-color: ${this.highlightColor}; } .${this.currentClass} { background-color: ${this.currentColor}; }`
createCssStyle(css)
this.lastIframeSrc = myIframe.src
}
同一个iframe, 如果src发生变化, 则需要重新给其生成样式, 否则样式会失效。
其他问题
- 使用svg画按钮图标时,双击svg按钮会自动触发全选
- 解决方法: 在svg标签所在容器上添加
user-select: none;样式
- 解决方法: 在svg标签所在容器上添加
- 使用
node.nodeType === Node.TEXT_NODE判断文本节点时,会遇到一些空节点,导致检索错误- 解决方法: 在判断文本节点时,加上
node.textContent.trim() !== ''的判断, 获取所有元素的文本时。 - 后续修改: 可以不单独处理这些空的文本节点, 只要保证所有使用到获取文本的地方都统一使用或不使用
trim()即可。尽量都不使用trim(), 如果随意使用trim(),可能会导致部分空白字符被误删。
- 解决方法: 在判断文本节点时,加上
vue自定义组件——search-box的更多相关文章
- vue自定义组件(vue.use(),install)+全局组件+局部组件
相信大家都用过element-ui.mintui.iview等诸如此类的组件库,具体用法请参考:https://www.cnblogs.com/wangtong111/p/11522520.html ...
- Vue自定义组件实现v-model指令
Tips: 本文所描述的Vue均默认是Vue2版本 在我们初次接触Vue的时候,一定会了解到一个语法糖,那就是v-model指令,它带给我们的第一印象就是它可以实现双向绑定 那么,什么是双向绑定?通俗 ...
- 如何运用Vue自定义组件以及组件的传值
Vue自定义组件 引入组件 首先在项目内的components新建.vue文件. 创建完成之后搭建完整的框架.其实就是新建组件,在此之前,需要在VScode中引入一个插件(vue 2 snippets ...
- VUE 自定义组件之间的相互通信
一.自定义组件 1.全局自定义组件 我们在var vm = new Vue({});的上面并列写上Vue.component('自定义组件名',{组件对象});来完成全局自定义组件的声明.示例代码如下 ...
- [转] vue自定义组件(通过Vue.use()来使用)即install的使用
在vue项目中,我们可以自定义组件,像element-ui一样使用Vue.use()方法来使用,具体实现方法: 1.首先新建一个Cmponent.vue文件 // Cmponent.vue<te ...
- vue 自定义组件销毁
今天在开发电商vue前端项目时,用户每次登出再换其它用户登录时,页面显示的用户名和左则导航都还是上个用户的,刚开始以为是localStorage中没有清除全局数据,然后在用户点击退出系统时手动清除lo ...
- Vue自定义组件插入值
我们自定义组件的时候有时候需要往组件里面插一些内容: //定义一个组件test,插值内容用slog来代替 export default { name: 'test', template:` <d ...
- vue自定义组件并使用
以下是使用自己写的一个简单的文件上传框为例 1.自定义组件结构(一个js文件,一个vue文件),最好单独放一个文件 2.upload.vue 内容 其中,action是父组件传递给子组件的参数,使用p ...
- vue自定义组件中的v-model简单解释
在使用iview框架的时候,经常会看到组件用v-model双向绑定数据,与传统步骤父组件通过props传值子组件,子组件发送$emit来修改值相比,这种方式避免操作子组件的同时再操作父组件,显得子组件 ...
- 8、VUE自定义组件
1.为什么要使用自定义组件? 自定义组件是用来封装复杂的内容,提高可重用性,比如封装复杂的表格组件.日历组件.图片轮播组件等. 2.自定义组件 2.1. 全局组件 全局组件是每个Vue对象都能使用的组 ...
随机推荐
- FinOps首次超越安全成为企业头等大事丨云计算趋势报告
随着云计算在过去十年中的广泛应用,云计算用户所面临的一个持续不变的趋势是:安全一直是用户面临的首要挑战.然而,这种情况正在发生转变. 知名IT软件企业 Flexera 对云计算决策者进行年度调研已经持 ...
- Github说明--如何在Github里面上传自己的代码
1.注册一个账号 这是必须的啦!不清楚注册步骤的,可以去看看我之前的博客,里面的步骤也是挺详细的呢! 2.进入到用户主界面 我们会看到这样的一个+标识: 选择其中的New Repository选项,点 ...
- CSAPP-Data Lab
gcc -O1 -Wall -m32 -lm -o btest bits.c btest.c decl.c tests.c In file included from btest.c:16:0: /u ...
- urllib.parse的使用
urllib简介 urllib是pyhton自带的标准库用于网络请求库,无需安装,直接引用 通常用于爬虫开发,API(应用程序编程接口)数据获取和测试 urllib库的4大模块 urllib.requ ...
- R语言网络数据爬虫之三个问题
现在大家对爬虫的兴趣不断高涨,R和PYTHON是两个非常有力的爬虫工具.Python倾向于做大型爬虫,与R相比,语法相对复杂,因此Python爬虫的学习曲线会相对陡峭.对于那些时间宝贵,又想从网上获取 ...
- docker方式实现redis数据持久化离线安装
保存镜像 root@hello:~# docker pull redis:latest latest: Pulling from library/redis a2abf6c4d29d: Already ...
- 从桌面和应用内 Activity的启动流程
1.APP还没有被打开过从桌面启动 <1>首先桌面进程会像AMS服务发送startActivity的请求,AMS从system_service中去拿----一次IPC通信 <2> ...
- 【从零开始】Docker Desktop:听说你小子要玩我
前言 缘由 捡起遗忘的Docker知识 由于本狗近期项目紧任务重,高强度的搬砖导致摸鱼时间下降.在上线项目时,看到运维大神一系列骚操作,docker+k8s的知识如过眼云烟,忘得干净的很.所以想重新恶 ...
- 【Zookeeper】(三)部署与使用、服务器节点动态上下线案例分析
目录 Zookeeper部署与使用 1 分布式安装部署 配置服务器编号 增加zoo.cfg集群配置参数 2 启动集群服务器 3 启动集群客户端和命令 4 ️API的使用 引入依赖 创建客户端 创建节点 ...
- 2023高效的mysql 随机语句 200万数据为例 用了 0.0030秒
是的,如果数据表中有200万条记录,使用 ORDER BY RAND() 这种方式来随机选择记录会非常慢,因为 MySQL 需要对整个表进行排序,然后再返回指定数量的记录.这个过程需要消耗大量的时间和 ...