本教程教你用 100% 纯 CSS 在 Bricks 里实现内容切换器(content switcher)——在多个内容区块间切换显示,典型场景如价格表(pricing tables)切换、功能特性切换,不需要任何 JS。
如果你想要 Bricks 内置的集成方案,可以直接购买 BricksExtras 插件;但如果你愿意动手写自定义 CSS,这篇教程正合适。
DOM 结构
先在 Bricks 里搭出下面的 DOM 结构,给每个元素按如下规则分配 class:
- 第一个元素是代码块(放 HTML)
revealed-items可以是任意元素(就是被切换显示的内容块)
纯 CSS 方法
HTML
把下面这段 HTML 粘贴到代码块里:
<fieldset class="radio-switch">
<!-- First Item -->
<input type="radio" name="switch" id="item1">
<label for="item1">Label 1</label>
<!-- Second Item -->
<input type="radio" name="switch" id="item2" checked>
<label for="item2">Label 2</label>
<!-- Third Item -->
<input type="radio" name="switch" id="item3">
<label for="item3">Label 3</label>
<div class="highlight"></div>
</fieldset>
标签文字(Label)可以按需修改,但不要改动属性,否则动画会失效。示例用了 3 个标签/输入项,CSS 原生支持到 4 个——想增减就按需增删条目。
第二个标签默认是选中态,因为它的 <input> 末尾带了 checked 属性;想默认选中哪个,就把 checked 移到哪个条目上。
CSS
把下面的 CSS 粘贴到页面:
/* CSS Variables */
fieldset.radio-switch {
--switch-item-number: 3;
--switch-padding: 5px;
--switch-bg: #fff;
--switch-border-width: 1px;
--switch-border-color: #ccc;
--switch-border-radius: 50px;
--switch-label-padding: 1.5rem 0.5rem;
--switch-inactive-text-color: #666;
--switch-active-text-color: #fff;
--switch-active-bg-color: #350792;
}
/* Fieldset*/
fieldset.radio-switch {
display: flex;
position: relative;
padding: 0;
outline: 0;
border: var(--switch-border-width) solid var(--switch-border-color);
background-color: var(--switch-bg);
border-radius: var(--switch-border-radius);
overflow: hidden;
width: 100%;
}
/* Hide Imputs*/
fieldset.radio-switch input {
visibility: hidden;
opacity: 0;
display: none !important;
}
/* Labels */
fieldset.radio-switch label {
z-index: 1;
margin: 0;
padding: var(--switch-label-padding);
border-radius: var(--switch-border-radius);
cursor: pointer;
color: var(--switch-inactive-text-color);
font-size: 14px;
line-height: 1.4em;
font-weight: 600;
}
/* Active Labels*/
fieldset.radio-switch input:checked+label {
color: var(--switch-active-text-color);
cursor: default;
}
/* Highlight */
fieldset.radio-switch label,
fieldset.radio-switch .highlight {
width: calc(100% / var(--switch-item-number));
display: flex;
align-items: center;
justify-content: center;
text-align: center;
transition: 300ms transform ease-in-out,
300ms color ease-in-out;
}
fieldset.radio-switch .highlight {
position: absolute;
background-color: var(--switch-active-bg-color);
top: calc(var(--switch-padding) / 2);
left: calc(var(--switch-padding) / 2);
bottom: 0;
right: 0;
border-radius: var(--switch-border-radius);
height: calc(100% - var(--switch-padding));
width: calc((100% / var(--switch-item-number)) - calc(var(--switch-padding) / var(--switch-item-number)));
}
/* Moving Highliht*/
fieldset.radio-switch input:nth-of-type(2):checked~.highlight {
transform: translateX(100%);
}
fieldset.radio-switch input:nth-of-type(3):checked~.highlight {
transform: translateX(200%);
}
fieldset.radio-switch input:nth-of-type(4):checked~.highlight {
transform: translateX(300%);
}
/* Revealed Items */
body.bricks-is-frontend .revealed-items {
display: none !important;
}
/* Active Reveald Item */
body.bricks-is-frontend .switcher-wrapper:has(input:nth-of-type(1):checked)~.revealed-items.item1,
body.bricks-is-frontend .switcher-wrapper:has(input:nth-of-type(2):checked)~.revealed-items.item2,
body.bricks-is-frontend .switcher-wrapper:has(input:nth-of-type(3):checked)~.revealed-items.item3,
body.bricks-is-frontend .switcher-wrapper:has(input:nth-of-type(4):checked)~.revealed-items.item4 {
display: flex !important;
animation: 300ms activeItems ease-in-out;
}
/* Custom Entrance Animation */
@keyframes activeItems {
0% {
transform: translateY(40px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
注意:第一段是 CSS 变量,方便你按需调整样式。其中 --switch-item-number 是必填变量,代表切换器里的标签数量——示例用了 3 个标签所以填 3;增删 HTML 条目时记得同步修改这个数字。
移动端竖排(可选)
想在手机上把切换器竖着排列,追加这段 CSS:
@media screen and (max-width: 767px) {
fieldset.radio-switch {
--switch-border-radius: 15px;
flex-direction: column;
}
/* Highlight */
fieldset.radio-switch .highlight,
fieldset.radio-switch label {
width: calc(100% - var(--switch-padding));
height: calc((100% / var(--switch-item-number)) - calc(var(--switch-padding) / var(--switch-item-number)));
}
/* Moving Highliht*/
fieldset.radio-switch input:nth-of-type(2):checked~.highlight {
transform: translateY(100%);
}
fieldset.radio-switch input:nth-of-type(3):checked~.highlight {
transform: translateY(200%);
}
fieldset.radio-switch input:nth-of-type(4):checked~.highlight {
transform: translateY(300%);
}
}
Firefox 兼容
早期 Firefox 不支持 :has() 伪类,需要做降级处理:先把默认显示的 revealed item 加上 .active 类,然后修改 CSS(把 :has() 那几行注释掉,改用 .active 类触发显示),最后加一段 JS 在点击时切换 .active 类:
/* Active Reveald Item */
/*
body.bricks-is-frontend .switcher-wrapper:has(input:nth-of-type(1):checked)~.revealed-items.item1,
body.bricks-is-frontend .switcher-wrapper:has(input:nth-of-type(2):checked)~.revealed-items.item2,
body.bricks-is-frontend .switcher-wrapper:has(input:nth-of-type(3):checked)~.revealed-items.item3,
body.bricks-is-frontend .switcher-wrapper:has(input:nth-of-type(4):checked)~.revealed-items.item4 {
display: flex !important;
animation: 300ms activeItems ease-in-out;
}*/
body.bricks-is-frontend .revealed-items.active{
display: flex !important;
animation: 300ms activeItems ease-in-out;
}
// Load the script when the DOM is loaded
window.addEventListener('DOMContentLoaded', () => {
// Query all the switchers on the page
const radioGroups = document.querySelectorAll('.switcher-wrapper');
// stop the script if there is no switcher
if (radioGroups.length < 1) return;
// Function to get all the siblings of an element
const getSiblings = (elem) => {
return Array.prototype.filter.call(elem.parentNode.children, (sibling) => {
return sibling !== elem;
});
};
// Loop in each switcher groups
radioGroups.forEach((radioGroup) => {
// Query the inputs
const radios = radioGroup.querySelectorAll('input');
// Query the revealed items
const revealedItems = getSiblings(radioGroup);
// Loop into each radio groups
radios.forEach((radio, index) => {
// Listener on click
radio.addEventListener('click', () => {
// Get the revealed item
const revealedItem = revealedItems[index];
// Remove the active class on all revealed items
revealedItems.forEach(el => el.classList.remove('active'));
// Add the active class to the active revealed item
revealedItem.classList.add('active');
})
})
})
})
💡 译者注:Firefox 从 121 版(2023 年 12 月)起已原生支持
:has(),现在的 Firefox 不再需要这段降级代码——保留它主要兼容旧版浏览器。
结论
设置正确的话,前端应该能看到:点击标签 → 高亮滑块平滑滑动 → 对应内容块淡入显示。
需要提醒:这个方案开箱即用不满足无障碍要求(accessibility)。如果你的项目强制要求无障碍,请换用其他方案,或自行补充额外工作。