转: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>

 

 

转载自:http://www.pianshen.com/article/564159407/

posted @ 2019-06-19 09:37  傅丹辰cds  阅读(2348)  评论(0编辑  收藏  举报