按照惯例,在开头给出你可能看过或者没看过的其他指导链接,阅读这些指导可能帮助你更轻松地理解本文的内容:
一直以来,我觉得在指导中重述 HTML 和 CSS 基础用处不大,因为这些知识都可以在别处学到,因此这篇文章将会略去任何这样的指导,默认读者熟悉上述领域中的各种术语。下面为有需求的读者提供一些学习资源:
这一大堆链接权当本文的前情提要,接下来我们进入正题。
了解状态切换state toggling
状态切换是 Wikidot 上交互设计的基础。
从最基础的迭代、折叠块、Tabview,到更复杂的 :target 伪迭代;无论是移动端(以及部分版式下的桌面端)的折叠侧栏、顶栏下拉菜单、SCP-DE 实装的夜间模式按钮,还是 SCP-CN-4000𝄇-EX 那种点击登录按钮后全屏内容切换的效果,这些交互设计都在做同一件事:捕捉一个状态的切换。
:target 伪迭代、侧栏展开收起、顶栏悬停下拉菜单都是利用CSS的伪类变化实现的。当用户点击链接,或者把鼠标光标移动到某个元素上方时,:target 或者 :hover 等CSS伪类就会生效。使用CSS选择特定的伪类添加样式,即可实现这些交互。
然而,捕捉伪类变化存在一些问题:常用的伪类通常比较“不稳定”。:target 依赖于页面链接“#”以后的部分,因而一次只能有一个元素成为目标;:hover 依赖于鼠标悬停,鼠标挪开之后效果就会消失,导致它更多地被用于能够保持悬停的移动端。当页面内需要同时存在多种复杂交互效果时,使用伪类状态切换作为触发器似乎比较不现实。
还有别的状态切换可以被捕捉吗?很遗憾,出于安全性考虑,Wikidot 页面内不能直接加载 JavaScript 脚本,因此也无法动态地操作元素、元素的属性等内容。有时只要靠 JavaScript 即可轻松实现的交互,在 Wikidot 页面上比较困难。
不过,JS 的确是一个突破口。具体而言,我们将采用和 Colmod 类似的思路:利用 Wikidot 自身的动态效果。
CSS :has() 伪类
平地一声惊雷,交互救星炸出。在这篇指导的前作发出一年多后,即 2023 年 12 月起,一个 CSS 新特性——:has() 伪类成为基线特性,即在各主流浏览器中均可用。
尽管我已经声明过在此不介绍 HTML 和 CSS 基础,这里还是需要隆重介绍一下 :has() 伪类。
众所周知,CSS 选择器有如下几种用法:A B 可以匹配到元素 A 的子元素 B;A ~ B 可以匹配到元素 A 之后的元素 B;以上两种用法还有更严格的对应 A > B 和 A + B,分别在 B 是 A 的直接子元素或 B 和 A 直接相邻时选择到 B。在伟大的 :has() 降临前,CSS 只能从前面出发选择到后面或者从外面出发选择到里面,试图选择 A 前面的 B 是不可能的。
那么我们来看看 :has() 做了什么。以下是 MDN 上 :has() 的定义:
当作为参数传入的相对选择器至少能匹配到一个元素时,:has() 匹配到前述相对选择器所锚定到的元素。
MDN 上 :has() 的中文描述实在过于拗口,上面这句话是我试着自己重新翻译它的结果,还是很拗口。具体的语法细节可以到 MDN 上查看,我已经在前文给出过链接。回到那一堆 A 和 B 上来,我有一种简单的方法可以说明 :has() 的作用::has() 可以捕捉 A 的变化,然后选择到页面上任何一个元素 B——这个元素 B 除了同在一个页面上外,可以和元素 A 完全无关1。
说到这里,:has() 伪类在 Wikidot 交互设计中的作用已经不言自明了。我们可以直接拿来一个 Wikidot 原生交互元素,然后利用 :has() 伪类把它变成我们目标交互的触发器trigger。思路和 Colmod 类似,不过比起 Colmod 时代,有了 :has() 的我们自由得多。
双状态切换
使用折叠块:开关灯
如果只需要在两种状态之间切换,折叠块即 [[collapsible]] 元素是个不错的触发器。我们利用 :has 捕捉 [[collapsible]] 元素结构中的展开后部分 div.collapsible-block-unfolded 的 style 属性,就可以捕获折叠块的变化。为了具体到特定的折叠块,我们不妨用带 class 属性的 [[div]] 给它包起来,即:
:root:not(:has(div.A div.collapsible-block-unfolded[style*="none"])) B
:root 是包含整个页面内容在内的根元素,我们只需要照常把我们要改变的那个元素 B 的选择器写进去就行了。
一个简单的例子是开关灯。本页使用的平行版式可以通过 CSS 变量调整整页的配色方案,因此我们只需要带 :has() 伪类的一个 :root,把暗色模式的 CSS 变量抄进去。
CSS 部分:
:root:not(:has(div.light-switch div.collapsible-block-unfolded[style*="none"])) { --basalt-primary-color: 20, 20, 20; --basalt-secondary-color: 32, 29, 28; --basalt-tertiary-color: 40, 39, 38; --basalt-main-text-color: 254, 254, 254; --basalt-overtone: 254, 254, 254; --basalt-undertone: 202, 20, 20; --basalt-bright-element-color: 255, 20, 20; --basalt-dark-element-color: 144, 20, 20; --basalt-darker-element-color: 202, 50, 50; --basalt-alternate-color: 132, 132, 20; --basalt-positive-color: 24, 163, 20; --basalt-negative-color: 255, 48, 48; --basalt-sub-text-color: 162, 154, 155; --header-title-color: var(--main-text-color); --header-subtitle-color: var(--main-text-color); --top-bar-link-color: var(--basalt-overtone); --UI-title-color: var(--basalt-darker-element-color); --UI-title-background-color: var(--basalt-secondary-color); }
FTML 部分:
[[div class="light-switch"]]
[[collapsible show="开灯" hide="关灯"]]
[[/collapsible]]
[[/div]]结果:
使用用户状态:您有一条新信息!
Wikidot 的消息提示会在版头的用户信息旁添加一个链接。当你收到新的 Wikidot 私信时,那个一闪一闪的数字就会出现,我们可以根据这个链接存在与否来判断你是否还有未读消息,然后将这种差异反映到正文内。
CSS 部分:
div.new-message { display: none; } :root:has(div#login-status > a[href*="messages"]) div.new-message { display: block; } :root:has(div#login-status > a[href*="messages"]) div.no-new-message { display: none; }
FTML 部分:
[[div class="new-message"]]
您有新信息!
[[/div]]
[[div class="no-new-message"]]
您没有新信息!
[[/div]]结果:
多状态切换
如何利用 :has() 进行多状态切换?这个问题就留作本次的课后习题吧。





