WP DEVELOP

EP065. “渲染结果区域与 Loading 动画”

首页 WordPress 开发课程 LIVE SEARCH AJAX · EP065
约 19 分钟· #EP065#LIVE SEARCH AJAX
🔒 登录后可标记已读

上一讲那个「等 2000 毫秒后 console.log 测试」,这里换成真正往页面里写 HTML:先在 footer.php 里给 overlay 加一个空的结果容器 div,再把 typingLogic 拆出一个新的 getResults 方法,用假文字("Imagine real search results here...")模拟「显示搜索结果」的效果。同时加了「打字瞬间先显示 loading 转圈动画,等结果出来再替换掉」的体验细节,还处理了几个边界情况:方向键/Ctrl/Shift 这类不改变搜索内容的按键不该触发任何反应;输入框清空时要立刻清空结果区、不要显示 loading;以及「其他输入框正在输入时,按 S 键不该触发搜索 overlay」。真正连接 WordPress REST API、显示真实数据要到 Section 14 才开始,这一讲的「搜索结果」还是写死的占位文字。

涉及文件

  • wp-content/themes/fictional-university-theme/footer.php (修改,overlay 内新增结果容器)
  • wp-content/themes/fictional-university-theme/src/modules/Search.js (修改)

代码实现

footer.php 的 overlay 结构里,在原本的 search-overlay__top 那个 div 后面新增一个结果容器:

<!-- wp-content/themes/fictional-university-theme/footer.php -->
<div class="search-overlay">
  <div class="search-overlay__top">
    <div class="container">
      <i class="fa fa-search search-overlay__icon" aria-hidden="true"></i>
      <input type="text" class="search-term" placeholder="What are you looking for?" id="search-term">
      <i class="fa fa-window-close search-overlay__close" aria-hidden="true"></i>
    </div>
  </div>

  <!-- 新增:搜索结果显示区域 -->
  <div class="container">
    <div id="search-overlay__results"></div>
  </div>
</div>

Search.js 整份贴出(这一讲改动分散在 constructor、events、typingLogic 好几处,直接看整份更清楚):

// wp-content/themes/fictional-university-theme/src/modules/Search.js

import $ from "jquery"

class Search {
  constructor() {
    this.resultsDiv = $("#search-overlay__results")   // 新增:选取结果容器
    this.openButton = $(".js-search-trigger")
    this.closeButton = $(".search-overlay__close")
    this.searchOverlay = $(".search-overlay")
    this.searchField = $("#search-term")
    this.events()
    this.isOverlayOpen = false
    this.isSpinnerVisible = false   // 新增:记录 loading 动画目前是否正在显示
    this.previousValue              // 新增:记录上一次的输入框内容
    this.typingTimer
  }

  events() {
    this.openButton.on("click", this.openOverlay.bind(this))
    this.closeButton.on("click", this.closeOverlay.bind(this))
    $(document).on("keydown", this.keyPressDispatcher.bind(this))
    this.searchField.on("keyup", this.typingLogic.bind(this))   // 改动:keydown 改成 keyup
  }

  typingLogic() {
    // 新增:只有当输入框内容真的发生变化时才继续往下走
    if (this.searchField.val() != this.previousValue) {
      clearTimeout(this.typingTimer)

      if (this.searchField.val()) {
        // 输入框有内容:需要的话先显示 loading 动画,再重新起一个 2000ms 的倒数
        if (!this.isSpinnerVisible) {
          this.resultsDiv.html('<div class="spinner-loader"></div>')
          this.isSpinnerVisible = true
        }
        this.typingTimer = setTimeout(this.getResults.bind(this), 2000)
      } else {
        // 输入框被清空:立刻清空结果区,不显示 loading,也不用等计时器
        this.resultsDiv.html("")
        this.isSpinnerVisible = false
      }
    }

    this.previousValue = this.searchField.val()
  }

  // 新增:占位的「获取结果」方法,目前还是假数据
  getResults() {
    this.resultsDiv.html("Imagine real search results here...")
    this.isSpinnerVisible = false
  }

  keyPressDispatcher(e) {
    // 改动:多加一个条件——如果当前有输入框/文本域正被聚焦,S 键不触发 overlay
    if (e.keyCode == 83 && !this.isOverlayOpen && !$("input, textarea").is(":focus")) {
      this.openOverlay()
    }

    if (e.keyCode == 27 && this.isOverlayOpen) {
      this.closeOverlay()
    }
  }

  openOverlay() {
    this.searchOverlay.addClass("search-overlay--active")
    $("body").addClass("body-no-scroll")
    console.log("our open method just ran!")
    this.isOverlayOpen = true
  }

  closeOverlay() {
    this.searchOverlay.removeClass("search-overlay--active")
    $("body").removeClass("body-no-scroll")
    console.log("our close method just ran!")
    this.isOverlayOpen = false
  }
}

export default Search

要点:

  • getResults 是从 typingLogic 里的 setTimeout 回调拆出来的一个独立方法(之前是匿名函数,现在改成 this.getResults.bind(this)),这样代码更清晰,也方便以后在 getResults 里面写真正的 AJAX/REST API 请求(下个 Section 的内容)。
  • loading 动画只在「当前没有显示」时才插入:用 this.isSpinnerVisible 这个状态属性把逻辑包起来,避免每敲一个字母就把 loading 的 HTML 重新插入一次导致动画看起来「卡顿重置」。getResults 执行完(结果出来了)会把 isSpinnerVisible 设回 false,清空结果区的分支同理。
  • keydown 改成 keyup:原本监听输入框打字用的是 keydown,但后面要判断「这次按键有没有真的改变输入内容」时,keydown 触发得太快,浏览器还没来得及把新字符写进输入框的值里,导致 this.searchField.val() 读到的还是旧值。改成 keyup(松开按键之后才触发)就能保证读到的是按键后的最新值。
  • 判断「内容有没有真的变化」:用 this.previousValue 记录上一次的输入框内容,每次 typingLogic 一开始先比对 this.searchField.val() != this.previousValue,只有真的变了才往下执行;方向键、Ctrl、Shift、Windows/Command 键这些不会改变文字内容的按键,就会因为这个判断而被直接跳过,不会误触发 loading 动画或倒数计时。方法最后一定要把 this.previousValue 更新成当前值,供下一次按键比对用。
  • 输入框被清空时的特殊处理if (this.searchField.val()) {...} else {...}——有内容时走「显示 loading + 起 2000ms 倒数」这条路;被清空(空字符串,逻辑上是 falsy)时直接清空结果区、把 isSpinnerVisible 设回 false,不需要再等计时器,因为压根不需要查询空字符串。
  • keyPressDispatcher 里新增的 !$("input, textarea").is(":focus") 条件:网站上其他地方(比如联系表单)可能也有输入框,如果用户在那些输入框里打字,恰好打到字母 S,不应该被误判成「按快捷键要打开搜索」。所以在原本「S 键 + overlay 未打开」的条件上,再加一条「当前没有任何 input/textarea 处于聚焦状态」,三个条件同时满足才真正打开 overlay。

[截图:前台搜索 overlay 输入框打字瞬间出现 loading 转圈动画,2 秒后被替换成"Imagine real search results here..."占位文字的画面]

Hook / Function 速查

名称类型用途
.html(content)jQuery 方法设置/替换某个元素内部的 HTML 内容
.val()jQuery 方法读取表单元素(如 <input>)当前的值
$("input, textarea").is(":focus")jQuery判断页面上是否有输入框/文本域当前处于聚焦(正在被打字)状态
keyup vs keydown(用于读取输入值时)键盘事件类型keydown 触发时浏览器可能还没更新完输入框的值,keyup 更适合用来读取「按键后」的最新内容

常见坑

  • keydown 去读 this.searchField.val() 会读到按键之前的旧值,导致「内容是否变化」的判断出错,要改成 keyup
  • 如果不加 isSpinnerVisible 这个状态判断,每次按键都会重新插入一次 loading 动画的 HTML,视觉上会一直「重置闪烁」,看起来很卡
  • 不处理「输入框被清空」这个分支的话,删空搜索词后 loading 动画和倒数计时还会继续跑,白白发一次没有意义的请求(这一讲还没接真实请求,但逻辑已经先按这个思路搭好)。
  • 忘记给 keyPressDispatcher 加上「其他输入框是否聚焦」的判断,会导致用户在页面别处输入框打字时,按到字母 S 就意外弹出搜索 overlay

延伸 / 后续讲座会用到

getResults 方法目前还是写死的占位文字("Imagine real search results here..."),下一个 Section(Section 14)会把它改成真正发送请求给 WordPress REST API、拿真实的 program/professor/campus/event 数据回来渲染。resultsDivisSpinnerVisiblepreviousValue 这几个属性后面会继续被用到。

Sources

Udemy:

  • Become a WordPress Developer: Unlocking Power With Code — Section 13, EP065