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. 界面

界面上比较简单, 输入框、当前/总数、上一个、下一个、关闭按钮。

  1. <div class="search-box" :style="boxStyle">
  2. <input
  3. v-model="input"
  4. placeholder="请输入检索内容"
  5. class="search-input"
  6. type="text"
  7. @input="search"
  8. >
  9. <!--当前/总数、上一个、下一个、关闭-->
  10. <span class="input-append">
  11. &nbsp;&nbsp;{{ current }}/{{ total }}&nbsp;&nbsp;
  12. </span>
  13. <span class="input-append" @click="searchPrevious">
  14. <div class="svg-container">
  15. <svg width="100px" height="100px">
  16. <path d="M 100 0 L 0 50 L 100 100" stroke="black" fill="transparent" stroke-linecap="round"/>
  17. </svg>
  18. </div>
  19. </span>
  20. <span class="input-append" @click="searchNext">
  21. <div class="svg-container">
  22. <svg width="100px" height="100px" transform="rotate(180)">
  23. <path d="M 100 0 L 0 50 L 100 100" stroke="black" fill="transparent" stroke-linecap="round"/>
  24. </svg>
  25. </div>
  26. </span>
  27. <span class="input-append" @click="searchClose">
  28. <div class="svg-container">
  29. <svg width="100%" height="100%">
  30. <line x1="0" y1="0" x2="100%" y2="100%" stroke="black" stroke-width="1" />
  31. <line x1="100%" y1="0" x2="0" y2="100%" stroke="black" stroke-width="1" />
  32. </svg>
  33. </div>
  34. </span>
  35. </div>

2. 检索与跳转

这部分是search-box的核心功能,一共有以下几个需要解决的问题:

  1. 获取待搜索的容器

    • 为提高组件的通用性,可以通过传入选择器列表来获取容器,如['.container', '#containerId'],使用document.querySelector()获取容器。
  2. 获取所有文本
    • 不能单独对某个dom节点获取文本, 因为某个待搜索词可能被分割在多个节点中, 例如<span>hello</span><span>world</span>,所以需要获取整个容器内的所有文本拼接起来, 然后再进行检索。
    • 使用innetText获取文本会受到样式影响, 具体见文章最后的其它问题。所以需要遍历所有节点将文本拼接起来。
    • 遍历文本节点时, 可以用node.nodeType === Node.TEXT_NODE判断是否为文本节点。
    1. if (node.nodeType === Node.TEXT_NODE) { // text node
    2. callback(node)
    3. } else if (node.nodeType === Node.ELEMENT_NODE) { // element node
    4. for (let i = 0; i < node.childNodes.length; i++) {
    5. traverseTextDom(node.childNodes[i], callback)
    6. }
    7. }
  3. 检索结果的保存
    • 由于查找完之后需要实现跳转, 所以为方便处理, 将检索到的结果所在的dom节点保存起来, 以便后续跳转时使用。每个结果对应一个domList。
  4. 高亮检索词
    • 使用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标签设置的样式未必有效, 可以采用动态添加样式的方式。
    1. function createCssStyle (css) {
    2. const style = myDocument.createElement('style')
    3. style.type = 'text/css'
    4. try {
    5. style.appendChild(myDocument.createTextNode(css))
    6. } catch (ex) {
    7. style.styleSheet.cssText = css
    8. }
    9. myDocument.getElementsByTagName('head')[0].appendChild(style)
    10. }
    • 将span标签插入到原先文本节点的位置, 若使用innerHtml直接进行替换, 处理起来略有些麻烦。可以考虑使用insertBefore和removeChild方法。
    1. const tempNode = myDocument.createElement('span')
    2. tempNode.innerHTML = textHtml
    3. const children = tempNode.children
    4. if (children) {
    5. for (let i = 0; i < children.length; i++) {
    6. domList.push(children[i])
    7. }
    8. }
    9. // 将节点插入到parent的指定位置
    10. // insertBofore会将节点从原来的位置移除,导致引错误,所以不能用forEach
    11. while (tempNode.firstChild) {
    12. parent.insertBefore(tempNode.firstChild, textNode)
    13. }
    14. parent.removeChild(textNode)
  5. 跳转

    由于结果对应的dom节点已保存,所以跳转起来比较容易。跳转时修改当前高亮的dom节点的类名, 然后将其滚动到可视区域。
    1. setCurrent (index) {
    2. const lastSelector = this.searchResult[this.currentIndex] ? this.searchResult[this.currentIndex].selector : null
    3. const currentSelector = this.searchResult[index] ? this.searchResult[index].selector : null
    4. if (this.currentIndex >= 0 && this.currentIndex < this.searchResult.length) {
    5. this.searchResult[this.currentIndex].domList.forEach((dom) => {
    6. dom.classList.remove(this.currentClass)
    7. })
    8. this.searchResult[this.currentIndex].domList[0].scrollIntoView({ behavior: 'smooth', block: 'center' })
    9. }
    10. this.currentIndex = index
    11. if (this.currentIndex >= 0 && this.currentIndex < this.searchResult.length) {
    12. this.searchResult[this.currentIndex].domList.forEach((dom) => {
    13. dom.classList.add(this.currentClass)
    14. })
    15. }
    16. }
  6. 移除高亮效果
    • 由于高亮效果是通过给text节点添加span标签实现, 所以需要将span标签移除, 并替换为原先的文本节点。
    • 使用insertBeforeremoveChild方法。
    • 替换完节点后需要调用normalize()方法, 将相邻的文本节点合并为一个文本节点。
    1. function convertHighlightDomToTextNode (domList) {
    2. if (!domList || !domList.length) { return }
    3. domList.forEach(dom => {
    4. if (dom && dom.parentNode) {
    5. const parent = dom.parentNode
    6. const textNode = myDocument.createTextNode(dom.textContent)
    7. parent.insertBefore(textNode, dom)
    8. parent.removeChild(dom)
    9. parent.normalize() // 合并相邻的文本节点
    10. }
    11. })
    12. }

3. 添加对iframe的支持

有时候页面中可能会包含iframe标签, 如果需要检索iframe中的内容, 直接使用当前的document是无法获取到iframe中的内容的, 需要拿到iframe的document对象。

  1. const myIframe = document.getElementById(this.iframeId)
  2. if (myIframe) {
  3. myDocument = myIframe.contentDocument || myIframe.contentWindow.document
  4. } else {
  5. myDocument = document
  6. }
  7. if (myIframe && this.lastIframeSrc !== myIframesrc) {
  8. const css = `.${this.highlightClass} { background-color: ${this.highlightColor}; } .${this.currentClass} { background-color: ${this.currentColor}; }`
  9. createCssStyle(css)
  10. this.lastIframeSrc = myIframe.src
  11. }

同一个iframe, 如果src发生变化, 则需要重新给其生成样式, 否则样式会失效。

其他问题

  1. 使用svg画按钮图标时,双击svg按钮会自动触发全选

    • 解决方法: 在svg标签所在容器上添加user-select: none;样式
  2. 使用node.nodeType === Node.TEXT_NODE判断文本节点时,会遇到一些空节点,导致检索错误
    • 解决方法: 在判断文本节点时,加上node.textContent.trim() !== ''的判断, 获取所有元素的文本时。
    • 后续修改: 可以不单独处理这些空的文本节点, 只要保证所有使用到获取文本的地方都统一使用或不使用trim()即可。尽量都不使用trim(), 如果随意使用trim(),可能会导致部分空白字符被误删。

vue自定义组件——search-box的更多相关文章

  1. vue自定义组件(vue.use(),install)+全局组件+局部组件

    相信大家都用过element-ui.mintui.iview等诸如此类的组件库,具体用法请参考:https://www.cnblogs.com/wangtong111/p/11522520.html ...

  2. Vue自定义组件实现v-model指令

    Tips: 本文所描述的Vue均默认是Vue2版本 在我们初次接触Vue的时候,一定会了解到一个语法糖,那就是v-model指令,它带给我们的第一印象就是它可以实现双向绑定 那么,什么是双向绑定?通俗 ...

  3. 如何运用Vue自定义组件以及组件的传值

    Vue自定义组件 引入组件 首先在项目内的components新建.vue文件. 创建完成之后搭建完整的框架.其实就是新建组件,在此之前,需要在VScode中引入一个插件(vue 2 snippets ...

  4. VUE 自定义组件之间的相互通信

    一.自定义组件 1.全局自定义组件 我们在var vm = new Vue({});的上面并列写上Vue.component('自定义组件名',{组件对象});来完成全局自定义组件的声明.示例代码如下 ...

  5. [转] vue自定义组件(通过Vue.use()来使用)即install的使用

    在vue项目中,我们可以自定义组件,像element-ui一样使用Vue.use()方法来使用,具体实现方法: 1.首先新建一个Cmponent.vue文件 // Cmponent.vue<te ...

  6. vue 自定义组件销毁

    今天在开发电商vue前端项目时,用户每次登出再换其它用户登录时,页面显示的用户名和左则导航都还是上个用户的,刚开始以为是localStorage中没有清除全局数据,然后在用户点击退出系统时手动清除lo ...

  7. Vue自定义组件插入值

    我们自定义组件的时候有时候需要往组件里面插一些内容: //定义一个组件test,插值内容用slog来代替 export default { name: 'test', template:` <d ...

  8. vue自定义组件并使用

    以下是使用自己写的一个简单的文件上传框为例 1.自定义组件结构(一个js文件,一个vue文件),最好单独放一个文件 2.upload.vue 内容 其中,action是父组件传递给子组件的参数,使用p ...

  9. vue自定义组件中的v-model简单解释

    在使用iview框架的时候,经常会看到组件用v-model双向绑定数据,与传统步骤父组件通过props传值子组件,子组件发送$emit来修改值相比,这种方式避免操作子组件的同时再操作父组件,显得子组件 ...

  10. 8、VUE自定义组件

    1.为什么要使用自定义组件? 自定义组件是用来封装复杂的内容,提高可重用性,比如封装复杂的表格组件.日历组件.图片轮播组件等. 2.自定义组件 2.1. 全局组件 全局组件是每个Vue对象都能使用的组 ...

随机推荐

  1. What is UDS Service 0x10 - Diagnostic Session Control ?

    Why need the UDS Service 0x10? ECU在正常工作时会处于某一个会话模式下,上电后会自动进入默认会话模式,所以ECU启动后我们不需要输入0x10 01来进入该会话模式.EC ...

  2. c++方便的输出vector和map_重载的应用 【python一样写c++、二】

    写程序,尤其是调试的时候,会想着直接输出一个map或者vector来调错. 但本来的cout<<没有这种功能.我们就会想了,要是c++能和python一样,直接输出一个列表(vector) ...

  3. 对于Java课上问题的探究和解答

    问题一:子类和父类的继承关系(extends) 需要强调的是,子类自动声明继承父类中的public和protected的成员 其中,public成员,外界可以自由访问: private成员,外界无法进 ...

  4. CSS in JS (JSS)

    JSS 是什么 简单来说,一句话概括CSS in JS (JSS),就是"行内样式"(inline style)和"行内脚本"(inline script). ...

  5. VW

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  6. 火山引擎 A/B 测试产品——DataTester 私有化架构分享

    作为一款面向 ToB 市场的产品--火山引擎A/B测试(DataTester)为了满足客户对数据安全.合规问题等需求,探索私有化部署是产品无法绕开的一条路. 在面向 ToB 客户私有化的实际落地中,火 ...

  7. Unity3D中的Attribute详解(六)

    本文将重点对Unity剩下常用的Attribute进行讲解,其他不常用的Attribute各位可以自行去官方文档查阅. 首先是UnityEngine命名空间下的. ColorUsage,这个主要作用于 ...

  8. IDA 逆 WDF 驱动时的函数识别插件

    快一年没更新了,累,工作累,各种累,想换个工作,突然发现找不到合适的工作了,哎,自己往火坑里跳,怪不得别人. import idautils import idaapi import idc prin ...

  9. tidyr包几个函数的用法

    在R语言中,tidyr主要提供了一个类似Excel中数据透视表 (pivottable)的功能; gather和spread函数将数据在长格式和宽格式之间相互转化,应用在比如稀疏矩阵和稠密矩阵之间的转 ...

  10. 利用NGINX搭建部署直播流媒体服务器

    直播如今是一个老生常谈的问题,怎么用于直播,大多数人只晓得,大佬某平台直播软件,点击开始即可直播.那么如何来搭建一个简易的直播平台呢?仅仅是有直播功能,没有涉及转码以及播放软件. 安装nginx以及r ...