EP228. “data-wp-class 切换 class 与 data-wp-bind--hidden 控制显隐”
🔒 登录后可标记已读把「点击答案后判断对错」的逻辑,接上真正的视觉反馈:答对/答错后分别显示「恭喜/抱歉」提示消息(用 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.js:guessAttempt 补齐完整判分逻辑:
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字段名字符串时拼写不一致(比如showSorrryvsshowSorry)——不会报错,只会导致对应功能安静地失效,排查时要逐字比对两侧字符串 - 忘记给「答错后显示的提示消息」加自动隐藏的计时器——状态值一旦变成
true就不会再变化,第二次触发同样的错误不会重新触发显示动画(这门课在插件章节的原生 JS 实现里已经踩过一次同样的坑) - 忘记给「答对后」加上
solved锁定逻辑——用户可以在答对之后继续点击别的答案,反复触发判分逻辑,体验不合理 data-wp-bind--hidden的条件表达式正负号搞反——比如把「正确答案该显示勾」写成不取反、「错误答案该显示叉」写成取反,会导致图标显示逻辑完全颠倒
延伸 / 后续讲座会用到
下一讲要处理剩余的视觉细节(错误答案的半透明效果、答对后取消鼠标悬浮高亮),并介绍 callback 这个新工具,用于处理更复杂的多条件判断逻辑。
Sources
Udemy:
- Become a WordPress Developer: Unlocking Power With Code — Section 30, EP228