WP DEVELOP

EP228. “data-wp-class 切换 class 与 data-wp-bind--hidden 控制显隐”

首页 WordPress 开发课程 INTERACTIVITY API · EP228
约 24 分钟· #EP228#INTERACTIVITY API
🔒 登录后可标记已读

把「点击答案后判断对错」的逻辑,接上真正的视觉反馈:答对/答错后分别显示「恭喜/抱歉」提示消息(用 data-wp-class--{class名} 根据 context 布尔值动态切换某个 CSS class);每个答案旁边的对错图标(绿色勾/红色叉)默认隐藏,只有「已解出」之后才显示对应图标(用 data-wp-bind--hidden 根据 context 布尔值动态控制 hidden 属性)。同时加上「答对之后彻底锁定,后续点击不再生效」的逻辑(复用 EP227 就预留好的 solved 字段),以及答错后 2600 毫秒自动隐藏提示消息的计时器。


涉及文件

  • wp-content/plugins/interactivity-quiz/src/render.php (修改)
  • wp-content/plugins/interactivity-quiz/src/view.js (修改)

代码实现

src/render.php(完整文件,答案列表项内嵌对错图标,底部新增恭喜/抱歉提示)

<?php
/**
 * PHP file to use when rendering the block type on the server to show on the front end.
 * ...
 */

	$answers = array();
	for ($i = 0; $i < count($attributes['answers']); $i++) {
		$answers[$i]['index'] = $i;
		$answers[$i]['text'] = $attributes['answers'][$i];
		$answers[$i]['correct'] = $attributes['correctAnswer'] == $i;
	}
	$ourContext = array('answers' => $answers, 'solved' => false, 'showCongrats' => false, 'showSorry' => false, 'correctAnswer' => $attributes['correctAnswer']);

?>


<div style="background-color: <?php echo $attributes['bgColor'] ?>" class="paying-attention-frontend" data-wp-interactive="create-block" <?php echo wp_interactivity_data_wp_context($ourContext) ?>>
	<p><?php echo $attributes['question'] ?></p>
	<ul>
		<?php
			foreach($ourContext['answers'] as $answer) { ?>
				<li <?php echo wp_interactivity_data_wp_context($answer) ?> data-wp-on--click="actions.guessAttempt"><span data-wp-bind--hidden="!context.solved">
					<span data-wp-bind--hidden="!context.correct"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16">
                  <path d="M10.97 4.97a.75.75 0 0 1 1.07 1.05l-3.99 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425a.267.267 0 0 1 .02-.022z" />
                </svg></span>
					<span data-wp-bind--hidden="context.correct"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16">
                  <path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z" />
                </svg></span>
				</span> <?php echo $answer['text'] ?></li>
			<?php }
		?>
	</ul>

				<div class="correct-message" data-wp-class--correct-message--visible="context.showCongrats">
        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" class="bi bi-emoji-smile" viewBox="0 0 16 16">
          <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
          <path d="M4.285 9.567a.5.5 0 0 1 .683.183A3.498 3.498 0 0 0 8 11.5a3.498 3.498 0 0 0 3.032-1.75.5.5 0 1 1 .866.5A4.498 4.498 0 0 1 8 12.5a4.498 4.498 0 0 1-3.898-2.25.5.5 0 0 1 .183-.683zM7 6.5C7 7.328 6.552 8 6 8s-1-.672-1-1.5S5.448 5 6 5s1 .672 1 1.5zm4 0c0 .828-.448 1.5-1 1.5s-1-.672-1-1.5S9.448 5 10 5s1 .672 1 1.5z" />
        </svg>
        <p>That is correct!</p>
      </div>

	<div class="incorrect-message" data-wp-class--incorrect-message--visible="context.showSorry">
        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" class="bi bi-emoji-frown" viewBox="0 0 16 16">
          <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
          <path d="M4.285 12.433a.5.5 0 0 0 .683-.183A3.498 3.498 0 0 1 8 10.5c1.295 0 2.426.703 3.032 1.75a.5.5 0 0 0 .866-.5A4.498 4.498 0 0 0 8 9.5a4.5 4.5 0 0 0-3.898 2.25.5.5 0 0 0 .183.683zM7 6.5C7 7.328 6.552 8 6 8s-1-.672-1-1.5S5.448 5 6 5s1 .672 1 1.5zm4 0c0 .828-.448 1.5-1 1.5s-1-.672-1-1.5S9.448 5 10 5s1 .672 1 1.5z" />
        </svg>
        <p>Sorry, try again.</p>
      </div>
</div>

src/view.jsguessAttempt 补齐完整判分逻辑

import { store, getContext } from "@wordpress/interactivity"

store("create-block", {
  actions: {
    guessAttempt: () => {
      const context = getContext()
      if (!context.solved) {
        if (context.index === context.correctAnswer) {
          context.showCongrats = true
          context.solved = true
        } else {
          context.showSorry = true
          setTimeout(() => {
            context.showSorry = false
          }, 2600)
        }
      }
    },
    toggle: () => {
      const context = getContext()
      context.isOpen = !context.isOpen
    }
  },
  callbacks: {
    logIsOpen: () => {
      const { isOpen } = getContext()
      // Log the value of `isOpen` each time it changes.
      console.log(`Is open: ${isOpen}`)
    }
  }
})

关键改动点:

  • data-wp-class--{class名}="表达式"——动态切换某个 CSS class 的开关:这是这一讲的核心新工具——属性名格式是 data-wp-class-- 后面直接跟上「想要动态控制的那个 class 名字」(这里是 correct-message--visible/incorrect-message--visible),属性值是一个表达式,值为 true 时这个 class 会被加上,为 false 时会被移除。跟 EP225 手动写 context.click count 这种直接输出文字不同,这次是「按条件加/删一个 class」,效果类似 React 里 className={条件 ? "a b" : "a"} 这种写法,但不需要自己拼字符串
  • 恭喜/抱歉提示消息复用同一套动画机制correct-message/incorrect-message 这两个 <div> 本身一直存在于 DOM 里(不是动态插入/移除),只是通过 --visible 这个修饰符 class 的有无来控制淡入淡出动画(这个视觉过渡效果本身是纯 CSS 实现,这门课在插件章节已经写过),data-wp-class 只负责「要不要加这个 class」,不负责动画本身
  • data-wp-bind--hidden="表达式"——控制 HTML5 原生的 hidden 属性hidden 是标准 HTML5 属性,只要元素上有这个属性(不需要任何值),浏览器就会隐藏它;data-wp-bind-- 后面接的是「要动态绑定的属性名」(这里是 hidden),表达式为真时会真的加上 hidden 属性,为假时移除——这跟 data-wp-class 的语法结构一致(data-wp-指令--参数),但控制的对象不同(一个管 class,一个管任意 HTML 属性)
  • 对错图标的三层嵌套显隐逻辑
    • 最外层 <span data-wp-bind--hidden="!context.solved">:整个图标区域,只有「已解出」(solved 为真)之后才显示,取反是因为「没解出时应该隐藏」
    • 内层第一个 <span data-wp-bind--hidden="!context.correct">:绿色勾图标,只在「这个答案本身是正确答案」(correct 为真,来自这个 <li> 自己声明的 context)时显示
    • 内层第二个 <span data-wp-bind--hidden="context.correct">:红色叉图标,条件正好相反(不取反,correct 为真时反而要隐藏这个叉),只在「这个答案不是正确答案」时显示
    • 两层判断组合起来的效果:解出之前所有图标都隐藏;解出之后,正确答案旁边显示勾,其余答案旁边显示叉
  • guessAttempt 补齐完整判分与状态管理逻辑
    • 最外层 if (!context.solved)——只有「还没解出」时才处理点击,避免答对之后继续点击其他答案会重复触发逻辑(这是 EP227 提到但没实现的「一旦解出就锁定」需求,这一讲正式落地)
    • 内层 if (context.index === context.correctAnswer)——比较「当前点击的这一项的下标」和「继承自外层 <div> 的正确答案下标」,跟 EP227 建立的逐级合并机制一致
    • 答对:context.showCongrats = true 触发恭喜消息淡入,同时 context.solved = true 永久锁定(不会再被重置回 false,因为没有对应的计时器)
    • 答错:context.showSorry = true 触发抱歉消息淡入,配合 setTimeout(() => {context.showSorry = false}, 2600) 在 2.6 秒后自动隐藏——这样再次点错时,状态又能从 false 变回 true,触发一次新的显示,不会像「一直停留在 true」那样卡住不再触发
  • PHP 端一处真实拼写错误踩坑记录:整理 $ourContext 数组时曾经把 showSorry 误拼成 showSorrry(多了一个 r),因为 JS 端用的是正确拼写 showSorry,两边对不上导致这个功能完全不生效——修好后一切正常。这是一个提醒:JS/PHP 两侧手写同一个字段名字符串时,拼写不一致不会报错,只会「安静地不生效」,排查这类问题要格外留意两侧字符串是否完全一致
  • 图标 SVG 直接从旧插件(用普通 JS/React 实现的版本)搬运,去掉 className 相关写法:这些 SVG 图标本身跟 Interactivity API 无关,之前在插件开发章节已经写好、验证过,原样复制粘贴,只是把 React 特有的 className 改回普通 HTML 的 class(或者干脆去掉,因为这里根本不需要这个 class)

[截图:前台点击某个答案后的效果——答对显示"That is correct!"恭喜消息和正确答案旁的绿色勾图标,答错显示"Sorry, try again."提示和红色叉图标]


Hook / Function 速查

名称类型用途
data-wp-class--{class名}="表达式"Interactivity API HTML 属性表达式为真/假时动态添加/移除指定的 CSS class
data-wp-bind--{属性名}="表达式"Interactivity API HTML 属性表达式为真/假时动态添加/移除指定的任意 HTML 属性(这里是 hidden
hidden(HTML5 原生属性)HTML 标准属性元素上只要有这个属性(无需值)就会被浏览器隐藏

常见坑

  • PHP 和 JS 两侧手写同一个 context 字段名字符串时拼写不一致(比如 showSorrry vs showSorry)——不会报错,只会导致对应功能安静地失效,排查时要逐字比对两侧字符串
  • 忘记给「答错后显示的提示消息」加自动隐藏的计时器——状态值一旦变成 true 就不会再变化,第二次触发同样的错误不会重新触发显示动画(这门课在插件章节的原生 JS 实现里已经踩过一次同样的坑)
  • 忘记给「答对后」加上 solved 锁定逻辑——用户可以在答对之后继续点击别的答案,反复触发判分逻辑,体验不合理
  • data-wp-bind--hidden 的条件表达式正负号搞反——比如把「正确答案该显示勾」写成不取反、「错误答案该显示叉」写成取反,会导致图标显示逻辑完全颠倒

延伸 / 后续讲座会用到

下一讲要处理剩余的视觉细节(错误答案的半透明效果、答对后取消鼠标悬浮高亮),并介绍 callback 这个新工具,用于处理更复杂的多条件判断逻辑。


Sources

Udemy:

  • Become a WordPress Developer: Unlocking Power With Code — Section 30, EP228