转:JS实现仿百度搜索框(实时返回搜索建议项)
实现原理
向输入框动态输入时关键词,将当前关键词作为问号参数后面的值,因为要跨域使用百度的接口,所以通过 JSONP 跨域创建 Ajax 请求。回调函数处理返回值。
尝试研究了一下百度的接口,发现原生的 XHR 接口参数有点复杂(百度应该是考虑了很多情况)。
找了一个 2345 导航,在输入框随便输入一个字母 s,打开 Network,发现它也是向百度的一个地址发送了请求,其中问号后面的‘&wd=s’发送的就是此关键词,’&cb='应该就是回调处理函数,并且它的 Type 也是 script,2345 导航应该也是通过 JSONP 向百度获取数据的
1 var script = document.createElement("script"); 2 script.src = 3 "https://www.baidu.com/su?&wd=" + 4 encodeURI(this.value.trim()) + 5 "&p=3&cb=handleSuggestion"; 6 document.body.appendChild(script);
点开那条请求,果然在里面看到了返回的数据。返回的结果是以一个对象的形式返回的。q 对应着检索关键词,s 对应着返回的结果(数组形式)
后续只需要动态创建 li 标签,设置里面的内容,以及注意其他细节问题。
1.使用 flex 布局实现搜索框的水平垂直居中。
坑 设置完 flex 属性之后发现并没有水平垂直居中,当时设置了父盒子 height:100%,发现如果将 height 设置成具体值就可以实现居中。怀疑是设置了%高度无效,查了一下,高度百分比是相对于父盒子的,也就是 body。默认 html 和 body 是没有设置 height 的。另外,在布局中对于没有设置宽高的块状盒子,宽度默认是 100%的,高度是由里面的内容自然撑开的。
2.先获取常用的 DOM 节点,避免后续频繁查询操作 DOM。
3.为了避免在输入过程中频繁发送请求(如果打字速度快),对请求函数做了函数节流,调了一下间隔 130ms 差不多正好,时间再长就会有卡顿的感觉。使用了 ES6 中的箭头函数避免了 setTimeout 中 this 指向的问题。
4.在回调函数中:
-
每一次执行时首先要清除建议框里的内容,不然上一次的结果还会存在建议框里!截取了结果中的前五个(如果把所有结果都展示出来感觉有点丑…百度官方是展示前四个搜索建议)
-
结果处理完毕后,执行自执行匿名函数,删除创建的 script 标签;
5.由于 li 是动态创建的,点击 li 标签或者点击"搜索一下"跳转百度进行搜索时,利用事件冒泡原理,进行事件委托。这里没有考虑兼容性问题:
1 e = e || window.event; 2 target = e.target || e.srcElement;
6.除了点击事件,键盘事件–回车键以及上下键都是进行事件委托进行注册的。
最终能够实现键盘上下键鼠标选择,点击“搜索一下”或回车键实现跳转搜索。
代码:
1 <!DOCTYPE html> 2 <html lang="en"> 3 4 <head> 5 <meta charset="UTF-8"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <!-- 兼容性视图 --> 8 <meta http-equiv="X-UA-Compatible" content="ie=edge"> 9 <meta content="更方便快捷搜索,从而达到事半功倍的效果" name="description"> 10 <title>search you want</title> 11 <style> 12 html { 13 height: 100%; 14 } 15 16 body { 17 background: #f0f3ef; 18 height: 100%; 19 } 20 21 .container { 22 height: 100%; 23 display: flex; 24 justify-content: center; 25 align-items: center; 26 flex-direction: column; 27 } 28 29 .bgDiv { 30 box-sizing: border-box; 31 width: 595px; 32 height: 55px; 33 position: relative; 34 /* position: absolute; 35 left: 50%; 36 top: 50%; 37 transform: translate(-50%, -50%); */ 38 } 39 40 .search-input-text { 41 border: 1px solid #b6b6b6; 42 width: 495px; 43 background: #fff; 44 height: 33px; 45 line-height: 33px; 46 font-size: 18px; 47 padding: 3px 0 0 7px; 48 } 49 50 .search-input-button { 51 width: 90px; 52 height: 38px; 53 color: #fff; 54 font-size: 16px; 55 letter-spacing: 3px; 56 background: #3385ff; 57 border: .5px solid #2d78f4; 58 margin-left: -5px; 59 vertical-align: top; 60 opacity: .9; 61 } 62 63 .search-input-button:hover { 64 opacity: 1; 65 box-shadow: 0 1px 1px #333; 66 cursor: pointer; 67 } 68 69 .suggest { 70 width: 502px; 71 position: absolute; 72 top: 38px; 73 border: 1px solid #999; 74 background: #fff; 75 display: none; 76 } 77 78 .suggest ul { 79 list-style: none; 80 margin: 0; 81 padding: 0; 82 } 83 84 .suggest ul li { 85 padding: 3px; 86 font-size: 17px; 87 line-height: 25px; 88 cursor: pointer; 89 } 90 91 .suggest ul li:hover { 92 background-color: #e5e5e5 93 } 94 </style> 95 </head> 96 97 <body> 98 <div class="container"> 99 <div class="bgDiv"> 100 <input type="text" class="search-input-text" value="" autofocus placeholder="关键词"> 101 <input type="button" value="搜索一下" class="search-input-button" id="btn"> 102 <div class="suggest"> 103 <ul id="search-result"> 104 </ul> 105 </div> 106 </div> 107 </div> 108 109 <script> 110 var suggestContainer = document.getElementsByClassName("suggest")[0]; 111 var searchInput = document.getElementsByClassName("search-input-text")[0]; 112 var bgDiv = document.getElementsByClassName("bgDiv")[0]; 113 var searchResult = document.getElementById("search-result"); 114 115 // 清除建议框内容 116 function clearContent() { 117 var size = searchResult.childNodes.length; 118 for (var i = size - 1; i >= 0; i--) { 119 searchResult.removeChild(searchResult.childNodes[i]); 120 } 121 }; 122 123 var timer = null; 124 // 注册输入框键盘抬起事件 125 searchInput.onkeyup = function (e) { 126 suggestContainer.style.display = "block"; 127 // 如果输入框内容为空 清除内容且无需跨域请求 128 if (this.value.length === 0) { 129 clearContent(); 130 return; 131 } 132 if (this.timer) { 133 clearTimeout(this.timer); 134 } 135 if (e.keyCode !== 40 && e.keyCode !== 38) { 136 // 函数节流优化 137 this.timer = setTimeout(() => { 138 // 创建script标签JSONP跨域 139 var script = document.createElement("script"); 140 script.src = "https://www.baidu.com/su?&wd=" + encodeURI(this.value.trim()) + 141 "&p=3&cb=handleSuggestion"; 142 document.body.appendChild(script); 143 }, 130) 144 } 145 146 }; 147 148 // 回调函数处理返回值 149 function handleSuggestion(res) { 150 // 清空之前的数据!! 151 clearContent(); 152 var result = res.s; 153 // 截取前五个搜索建议项 154 if (result.length > 4) { 155 result = result.slice(0, 5) 156 } 157 for (let i = 0; i < result.length; i++) { 158 // 动态创建li标签 159 var liObj = document.createElement("li"); 160 liObj.innerHTML = result[i]; 161 searchResult.appendChild(liObj); 162 } 163 // 自执行匿名函数--删除用于跨域的script标签 164 (function () { 165 var s = document.querySelectorAll('script'); 166 for (var i = 1, len = s.length; i < len; i++) { 167 document.body.removeChild(s[i]); 168 } 169 })() 170 } 171 172 173 function jumpPage() { 174 window.open(`https://www.baidu.com/s?word=${encodeURI(searchInput.value)}`); 175 } 176 177 // 事件委托 点击li标签或者点击搜索按钮跳转到百度搜索页面 178 bgDiv.addEventListener("click", function (e) { 179 if (e.target.nodeName.toLowerCase() === 'li') { 180 var keywords = e.target.innerText; 181 searchInput.value = keywords; 182 jumpPage(); 183 } else if (e.target.id === 'btn') { 184 jumpPage(); 185 } 186 }, false); 187 188 var i = 0; 189 var flag = 1; 190 191 // 事件委托 监听键盘事件 192 bgDiv.addEventListener("keydown", function (e) { 193 var size = searchResult.childNodes.length; 194 if (e.keyCode === 13) { 195 jumpPage(); 196 }; 197 // 键盘向下事件 198 if (e.keyCode === 40) { 199 if (flag === 0) { 200 i = i + 2; 201 } 202 flag = 1; 203 e.preventDefault(); 204 if (i >= size) { 205 i = 0; 206 } 207 if (i < size) { 208 searchInput.value = searchResult.childNodes[i++].innerText; 209 } 210 }; 211 // 键盘向上事件 212 if (e.keyCode === 38) { 213 if (flag === 1) { 214 i = i - 2; 215 } 216 flag = 0; 217 e.preventDefault(); 218 if (i < 0) { 219 i = size - 1; 220 } 221 if (i > -1) { 222 searchInput.value = searchResult.childNodes[i--].innerText; 223 } 224 }; 225 }, false); 226 227 // 点击页面任何其他地方 搜索结果框消失 228 document.onclick = () => clearContent() 229 </script> 230 </body> 231 232 </html>