github地址: https://github.com/lxmghct/my-vue-components
完整代码见github: https://github.com/lxmghct/my-vue-components
在其中的src/components/SearchBox下。
界面上比较简单, 输入框、当前/总数、上一个、下一个、关闭按钮。
<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>
这部分是search-box的核心功能,一共有以下几个需要解决的问题:
['.container', '#containerId']
,使用document.querySelector()
获取容器。<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) } }
hello
,则可以将其替换为<span>h</span><span>e</span><span>l</span><span>l</span><span>o</span>
。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) }
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)
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) }) } }
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() // 合并相邻的文本节点 } }) }
有时候页面中可能会包含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发生变化, 则需要重新给其生成样式, 否则样式会失效。
user-select: none;
样式node.nodeType === Node.TEXT_NODE
判断文本节点时,会遇到一些空节点,导致检索错误
node.textContent.trim() !== ''
的判断, 获取所有元素的文本时。trim()
即可。尽量都不使用trim()
, 如果随意使用trim()
,可能会导致部分空白字符被误删。