JS实现仿百度搜索框(实时返回搜索建议项)

实现原理

向输入框动态输入时关键词,将当前关键词作为问号参数后面的值,因为要跨域使用百度的接口,所以通过 JSONP 跨域创建 Ajax 请求。回调函数处理返回值。

尝试研究了一下百度的接口,发现原生的 XHR 接口参数有点复杂(百度应该是考虑了很多情况)。

找了一个 2345 导航,在输入框随便输入一个字母 s,打开 Network,发现它也是向百度的一个地址发送了请求,其中问号后面的‘&wd=s’发送的就是此关键词,’&cb='应该就是回调处理函数,并且它的 Type 也是 script,2345 导航应该也是通过 JSONP 向百度获取数据的。
JS实现仿百度搜索框(实时返回搜索建议项)

var script = document.createElement("script");
script.src =
  "https://www.baidu.com/su?&wd=" +
  encodeURI(this.value.trim()) +
  "&p=3&cb=handleSuggestion";
document.body.appendChild(script);

点开那条请求,果然在里面看到了返回的数据。返回的结果是以一个对象的形式返回的。q 对应着检索关键词,s 对应着返回的结果(数组形式)
JS实现仿百度搜索框(实时返回搜索建议项)

后续只需要动态创建 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 标签或者点击"搜索一下"跳转百度进行搜索时,利用事件冒泡原理,进行事件委托。这里没有考虑兼容性问题:

e = e || window.event;
target = e.target || e.srcElement;

6.除了点击事件,键盘事件–回车键以及上下键都是进行事件委托进行注册的。

最终能够实现键盘上下键鼠标选择,点击“搜索一下”或回车键实现跳转搜索。

代码:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <!-- 兼容性视图 -->
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <meta content="更方便快捷搜索,从而达到事半功倍的效果" name="description">
  <title>search you want</title>
  <style>
    html {
      height: 100%;
    }

    body {
      background: #f0f3ef;
      height: 100%;
    }

    .container {
      height: 100%;
      display: flex;
      justify-content: center;
      align-items: center;
      flex-direction: column;
    }

    .bgDiv {
      box-sizing: border-box;
      width: 595px;
      height: 55px;
      position: relative;
    /* position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%); */
    }

    .search-input-text {
      border: 1px solid #b6b6b6;
      width: 495px;
      background: #fff;
      height: 33px;
      line-height: 33px;
      font-size: 18px;
      padding: 3px 0 0 7px;
    }

    .search-input-button {
      width: 90px;
      height: 38px;
      color: #fff;
      font-size: 16px;
      letter-spacing: 3px;
      background: #3385ff;
      border: .5px solid #2d78f4;
      margin-left: -5px;
      vertical-align: top;
      opacity: .9;
    }

    .search-input-button:hover {
      opacity: 1;
      box-shadow: 0 1px 1px #333;
      cursor: pointer;
    }

    .suggest {
      width: 502px;
      position: absolute;
      top: 38px;
      border: 1px solid #999;
      background: #fff;
      display: none;
    }

    .suggest ul {
      list-style: none;
      margin: 0;
      padding: 0;
    }

    .suggest ul li {
      padding: 3px;
      font-size: 17px;
      line-height: 25px;
      cursor: pointer;
    }

    .suggest ul li:hover {
      background-color: #e5e5e5
    }
  </style>
</head>

<body>
  <div class="container">
    <div class="bgDiv">
      <input type="text" class="search-input-text" value="" autofocus placeholder="关键词">
      <input type="button" value="搜索一下" class="search-input-button" id="btn">
      <div class="suggest">
        <ul id="search-result">
        </ul>
      </div>
    </div>
  </div>

  <script>
    var suggestContainer = document.getElementsByClassName("suggest")[0];
    var searchInput = document.getElementsByClassName("search-input-text")[0];
    var bgDiv = document.getElementsByClassName("bgDiv")[0];
    var searchResult = document.getElementById("search-result");

    // 清除建议框内容
    function clearContent() {
      var size = searchResult.childNodes.length;
      for (var i = size - 1; i >= 0; i--) {
        searchResult.removeChild(searchResult.childNodes[i]);
      }
    };

    var timer = null;
    // 注册输入框键盘抬起事件
    searchInput.onkeyup = function (e) {
      suggestContainer.style.display = "block";
      // 如果输入框内容为空 清除内容且无需跨域请求
      if (this.value.length === 0) {
        clearContent();
        return;
      }
      if (this.timer) {
        clearTimeout(this.timer);
      }
      if (e.keyCode !== 40 && e.keyCode !== 38) {
        // 函数节流优化
        this.timer = setTimeout(() => {
          // 创建script标签JSONP跨域
          var script = document.createElement("script");
          script.src = "https://www.baidu.com/su?&wd=" + encodeURI(this.value.trim()) +
            "&p=3&cb=handleSuggestion";
          document.body.appendChild(script);
        }, 130)
      }

    };

    // 回调函数处理返回值
    function handleSuggestion(res) {
      // 清空之前的数据!!
      clearContent();
      var result = res.s;
      // 截取前五个搜索建议项
      if (result.length > 4) {
        result = result.slice(0, 5)
      }
      for (let i = 0; i < result.length; i++) {
        // 动态创建li标签
        var liObj = document.createElement("li");
        liObj.innerHTML = result[i];
        searchResult.appendChild(liObj);
      }
      // 自执行匿名函数--删除用于跨域的script标签
      (function () {
        var s = document.querySelectorAll('script');
        for (var i = 1, len = s.length; i < len; i++) {
          document.body.removeChild(s[i]);
        }
      })()
    }


    function jumpPage() {
      window.open(`https://www.baidu.com/s?word=${encodeURI(searchInput.value)}`);
    }

    // 事件委托 点击li标签或者点击搜索按钮跳转到百度搜索页面
    bgDiv.addEventListener("click", function (e) {
      if (e.target.nodeName.toLowerCase() === 'li') {
        var keywords = e.target.innerText;
        searchInput.value = keywords;
        jumpPage();
      } else if (e.target.id === 'btn') {
        jumpPage();
      }
    }, false);

    var i = 0;
    var flag = 1;

    // 事件委托 监听键盘事件
    bgDiv.addEventListener("keydown", function (e) {
      var size = searchResult.childNodes.length;
      if (e.keyCode === 13) {
        jumpPage();
      };
      // 键盘向下事件
      if (e.keyCode === 40) {
        if (flag === 0) {
          i = i + 2;
        }
        flag = 1;
        e.preventDefault();
        if (i >= size) {
          i = 0;
        }
        if (i < size) {
          searchInput.value = searchResult.childNodes[i++].innerText;
        }
      };
      // 键盘向上事件
      if (e.keyCode === 38) {
        if (flag === 1) {
          i = i - 2;
        }
        flag = 0;
        e.preventDefault();
        if (i < 0) {
          i = size - 1;
        }
        if (i > -1) {
          searchInput.value = searchResult.childNodes[i--].innerText;
        }
      };
    }, false);

    // 点击页面任何其他地方 搜索结果框消失
    document.onclick = () => clearContent()
  </script>
</body>

</html>