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()
,可能會導致部分空白字元被誤刪。