文章轉自個人博客:https://knightyun.github.io/2019/03/04/articles-search,轉載請申明
現在很多網站頁面裏都有搜索模塊,包括在線搜索、站內搜索等等,尤其是博客類網站,文章搜索功能就顯得比較重要,現在以個人博客網站爲例,詳細介紹如何給頁面添加搜索功能模塊,至於如何搭建個人博客網站,可以參考這篇文章:https://knightyun.github.io/2018/04/01/github-pages-blog;
功能分析
爲網頁添加搜索模塊的第三方網站有不少,實現效果也都不錯,既然人家都能通過代碼實現此功能,與其過度依賴第三方,何不自己研究一下原理自己實現呢*_*;
網頁內搜索功能無非就是在文本輸入框內輸入一串想要搜索的關鍵字符串,點擊搜索按鈕後查找匹配出站內所有文章的匹配結果進行輸出,再附上一個文章的鏈接;針對這個功能,作者本人第一時間想到的則是前端領域裏的 Ajax,利用 XMLHttpRequest() 實現數據的交互,不清楚的可以自行百度快速補習,然後一般博客類網站都有 RSS 訂閱功能及其數據頁面,不清楚的也可以迅速百度瞭解一下,其實就是站內某個目錄內存在一個叫 feed.xml 或者類似名字的頁面,XML格式的,早期用於實現新聞內容訂閱的,現在用的也比較廣泛,裏面存儲的就是文章相關的一些數據,因此我們就可以獲取這個文件裏面的所有內容然後實現搜索匹配;
如果像我一樣用 Gihub Pages 搭建的個人博客網站的話,無論用的 jekyll 或者 hexo,一般這個 feed.xml 文件就位於 根目錄 下,並且該文件同時記錄着某一篇文章所對應的 標題、頁面鏈接、文章內容 等內容,這三個部分後面將用到,xml 內容大致如下:
當然有其他更好的實現方法可以自行參考或者在文末評論,那麼接下來便通過這個方法來一步步實現搜素功能;
功能實現
先預覽一下頁面最終的實現效果:
或者在這裏:https://knightyun.github.io/ 預覽我的博客的搜索模塊的最終實現;
HTML部分
即頁面樣式,組成很簡單,即一個文本輸入框<input>
和一個搜索圖標,這裏圖標可以自行搜索下載一個,或者像下面一樣使用在線圖標,全部代碼如下:
先在<header></header>
內部添加以下代碼,使用在線圖標:
<link href="https://fonts.googleapis.com/icon?family=Material+Icons"
rel="stylesheet">
然後在網頁內需要添加搜索欄的合適位置添加以下代碼,一般放在頂部導航欄:
<div class="search">
<i class="material-icons search-icon search-start">search</i>
<input type="text" class="search-input" placeholder="Searching..." />
<i class="material-icons search-icon search-clear">clear</i>
<div class="search-results"></div>
</div>
上面的clear是一個清除輸入框內容的圖標,search-results是用於輸出匹配到的結果的板塊;
CSS部分
然後來看一下CSS樣式代碼,僅供參考:
.search {
position: relative;
height: 30px;
text-align: right;
line-height: 30px;
padding-right: 10px;
}
.search .search-icon {
float: right;
height: 100%;
margin: 0 10px;
line-height: 30px;
cursor: pointer;
user-select: none;
}
.search .search-input {
float: right;
width: 30%;
height: 30px;
line-height: 30px;
margin: 0;
border: 2px solid #ddd;
border-radius: 10px;
box-sizing: border-box;
}
.search .search-clear {
display: none;
}
.search .search-results {
display: block;
z-index: 1000;
position: absolute;
top: 30px;
right: 50px;
width: 50%;
max-height: 400px;
overflow: auto;
text-align: left;
border-radius: 5px;
background: #ccc;
box-shadow: 0 .3rem .5rem #333;
}
.search .search-results .result-item {
background: aqua;
color: #000;
margin: 5px;
padding: 3px;
border-radius: 3px;
cursor: pointer;
}
樣式可以自行隨意調整,最終感覺好看就OK;
JavaScript部分
接下來就是重頭戲了,也是實現搜索功能的核心部分,搜索邏輯的實現;
再來大致分析一下邏輯,和實現的思路:
- 利用XMLHttpRequest()獲取站內feed.xml內的所有數據,保存到一個XML DOM對象中;
- 將XML對象中的文章標題、鏈接、內容、索引等通過
getElementsByTagName()
等方法獲取並保存到對應數組變量中; - 用戶在輸入框輸入查找內容,提交後內容保存到一個字符串類型變量中;
- 遍歷保存文章內容的數組,通過
.search()
等方法和輸入值進行匹配; - 匹配成功後得到所有匹配成功的數組元素的索引值,該索引值也是該內容的標題、鏈接數組對應的索引值;
- 將最終蒐集的文章標題、鏈接,以及匹配到的內容片段摘取輸出到頁面;
這裏附上最終的 js 實現代碼與註釋:
// 獲取搜索框、搜索按鈕、清空搜索、結果輸出對應的元素
var searchBtn = document.querySelector('.search-start');
var searchClear = document.querySelector('.search-clear');
var searchInput = document.querySelector('.search-input');
var searchResults = document.querySelector('.search-results');
// 申明保存文章的標題、鏈接、內容的數組變量
var searchValue = '',
arrItems = [],
arrContents = [],
arrLinks = [],
arrTitles = [],
arrResults = [],
indexItem = [];
var tmpDiv = document.createElement('div');
tmpDiv.className = 'result-item';
// ajax 的兼容寫法
var xhr = new XMLHttpRequest() || new ActiveXObject('Microsoft.XMLHTTP');
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
xml = xhr.responseXML;
arrItems = xml.getElementsByTagName('item');
// 遍歷並保存所有文章對應的標題、鏈接、內容到對應的數組中
for (i = 0; i < arrItems.length; i++) {
arrContents[i] = arrItems[i].getElementsByTagName('description')[0].childNodes[0].nodeValue;
arrLinks[i] = arrItems[i].getElementsByTagName('link')[0].childNodes[0].nodeValue.replace(/\s+/g, '');
arrTitles[i] = arrItems[i].getElementsByTagName('title')[0].childNodes[0].nodeValue;
}
}
}
// 開始獲取根目錄下 feed.xml 文件內的數據
xhr.open('get', '/feed.xml', true);
xhr.send();
searchBtn.onclick = searchConfirm;
// 清空按鈕點擊函數
searchClear.onclick = function(){
searchInput.value = '';
searchResults.style.display = 'none';
searchClear.style.display = 'none';
}
// 輸入框內容變化後就開始匹配,可以不用點按鈕
searchInput.onkeydown = function () {
setTimeout(searchConfirm, 0);
}
searchInput.onfocus = function () {
searchResults.style.display = 'block';
}
function searchConfirm() {
if (searchInput.value == '') {
searchResults.style.display = 'none';
searchClear.style.display = 'none';
} else if (searchInput.value.search(/^\s+$/) >= 0) {
// 檢測輸入值全是空白的情況
searchInit();
var itemDiv = tmpDiv.cloneNode(true);
itemDiv.innerText = '請輸入有效內容...';
searchResults.appendChild(itemDiv);
} else {
// 合法輸入值的情況
searchInit();
searchValue = searchInput.value;
searchMatching(arrContents, searchValue);
}
}
// 每次搜索完成後的初始化
function searchInit() {
arrResults = [];
indexItem = [];
searchResults.innerHTML = '';
searchResults.style.display = 'block';
searchClear.style.display = 'block';
}
function searchMatching(arr, input) {
// 在所有文章內容中匹配查詢值
for (i = 0; i < arr.length; i++) {
if (arr[i].search(input) != -1) {
indexItem.push(i);
var indexContent = arr[i].search(input);
var l = input.length;
var step = 10;
// 將匹配到內容的地方進行黃色標記,幷包括周圍一定數量的文本
arrResults.push(arr[i].slice(indexContent - step, indexContent) +
'<mark>' + arr[i].slice(indexContent, indexContent + l) + '</mark>' +
arr[i].slice(indexContent + l, indexContent + l + step));
}
}
// 輸出總共匹配到的數目
var totalDiv = tmpDiv.cloneNode(true);
totalDiv.innerHTML = '總匹配:<b>' + indexItem.length + '</b> 項';
searchResults.appendChild(totalDiv);
// 未匹配到內容的情況
if (indexItem.length == 0) {
var itemDiv = tmpDiv.cloneNode(true);
itemDiv.innerText = '未匹配到內容...';
searchResults.appendChild(itemDiv);
}
// 將所有匹配內容進行組合
for (i = 0; i < arrResults.length; i++) {
var itemDiv = tmpDiv.cloneNode(true);
itemDiv.innerHTML = '<b>《' + arrTitles[indexItem[i]] +
'》</b><hr />' + arrResults[i];
itemDiv.setAttribute('onclick', 'changeHref(arrLinks[indexItem[' + i + ']])');
searchResults.appendChild(itemDiv);
}
}
function changeHref(href) {
// 在當前頁面點開鏈接的情況
location.href = href;
// 在新標籤頁面打開鏈接的代碼,與上面二者只能取一個,自行決定
// window.open(href);
}
可以把上面的代碼保存到search-box.js
這樣的 js 文件中,然後引入到 html 頁面裏;
看一下最終效果:
或者來我的主頁查看:https://knightyun.github.io/