这篇教程教你:当用户点击查询循环上方的图标切换按钮时,动态改变查询循环容器的布局。
引言
想让用户自己从几种布局方案里挑一个来查看你的查询循环网格吗?
这篇教程将演示如何做到这一点——不需要外部插件、不需要 JS 库、也不需要复制内容(复制内容真的会伤 SEO 内容优化,对吧?),只需要在页面里加一小段自定义代码。
DOM 结构
按照下面的结构重建,并按下图添加类名。注意:post 容器的样式/结构完全由你决定——它不影响脚本运行。
**重要:一定要给默认选中的切换按钮加上 .toggle-layout--active 类。**这个类的样式随你喜欢。
在每个布局切换按钮上,添加下面的属性:
data-layout 属性:选一个能描述该布局的唯一名称作为值。示例中我们用了 list、list-two-col、grid。这些名字会被当作类名,加到查询容器上,用来针对特定布局给内部元素写样式。
data-layout-col 属性:填入将应用到查询容器的列数:
JavaScript
把下面的代码插入页面。个人推荐使用条件加载脚本的方法:
// 等 DOM 完全加载后再运行脚本 - 避免阻塞渲染
window.addEventListener('DOMContentLoaded', () => {
// 查找所有带布局切换的容器
const containers = document.querySelectorAll('.loop-ctn-w-layout-toggles');
// 检查页面上是否有带布局切换的容器
if (containers.length < 1) return console.log('There is no container with layout toggles on this page. Double check you correctly added the .loop-ctn-w-layout-toggles class on the main container.');
// 遍历所有容器
containers.forEach(container => {
// 查找切换按钮容器
const toggleContainer = container.querySelector('.toggle-container');
// 检查是否有切换按钮容器
if (!toggleContainer) return console.log('There is no toggle container. Double check you correctly added the .toggle-container class on the toggle container.');
// 查找查询容器
const queryContainer = container.querySelector('.query-container');
// 检查是否有查询容器
if (!queryContainer) return console.log('There is no query container. Double check you correctly added the .query-container class on the query container.');
// 查找所有切换按钮
const toggles = toggleContainer.querySelectorAll('.toggle-layout');
// 查找当前激活的切换按钮
const activeToggle = toggleContainer.querySelector('.toggle-layout--active');
// 加载时设置默认布局
queryContainer.classList.add(activeToggle.dataset.layout);
// 检查是否有切换按钮
if (toggles.length < 1) return console.log('There are no layout toggles. Double check you correctly added the .toggle-layout class on each toggle.');
// 创建空数组,存放所有不同的布局类
let layouts = [];
// 把所有布局类填入数组
toggles.forEach(toggle => {
// 检查 data-layout 属性是否正确设置
if (!toggle.dataset.layout) return console.log('The attribute data-layout has not been correctly set on the toggles.');
layouts.push(toggle.dataset.layout);
});
// 遍历每个切换按钮
toggles.forEach(toggle => {
// 给切换按钮添加点击事件监听
toggle.addEventListener('click', (e) => {
// 阻止默认行为
e.preventDefault();
// 读取 data-layout 属性
const layout = toggle.dataset.layout;
// 如果已经是当前布局,避免重复执行动画
if (queryContainer.classList.contains(layout)) return;
// 从属性读取列数
const col = toggle.dataset.layoutCol;
// 检查 data-layout-col 属性是否正确设置
if (!toggle.dataset.layoutCol) return console.log('The attribute data-layout-col has not been correctly set on the toggles.');
// 移除现有的激活类
toggles.forEach(el => el.classList.remove('toggle-layout--active'));
// 给点击的切换按钮添加激活类
toggle.classList.add('toggle-layout--active');
// 从查询容器移除已有布局类
queryContainer.classList.remove(...layouts);
setTimeout(() => {
// 给查询容器添加自定义布局类,方便进一步定制内部元素样式
queryContainer.classList.add(layout);
// 修改网格列数
queryContainer.style.setProperty('--grid-column-count', col);
}, 100);
})
})
})
})
CSS
把下面的 CSS 插入页面:
.loop-ctn-w-layout-toggles .query-container {
/* 下面的变量按需设置 */
--grid-layout-gap: 3rem;
--grid-column-count: 4;
--grid-item--min-width: 280px;
/* 变量结束 - 不要修改下面的数值 */
display: grid;
grid-gap: var(--grid-layout-gap);
}
@media screen and (min-width: 768px) {
.loop-ctn-w-layout-toggles .query-container {
--gap-count: calc(var(--grid-column-count) - 1);
--total-gap-width: calc(var(--gap-count) * var(--grid-layout-gap));
--grid-item--max-width: calc((100% - var(--total-gap-width)) / var(--grid-column-count));
grid-template-columns: repeat(auto-fill, minmax(max(var(--grid-item--min-width), var(--grid-item--max-width)), 1fr));
}
/* 可选动画 */
body.bricks-is-frontend .loop-ctn-w-layout-toggles .query-container .post-container {
animation: none;
opacity: 0;
}
body.bricks-is-frontend .loop-ctn-w-layout-toggles .query-container.list .post-container,
body.bricks-is-frontend .loop-ctn-w-layout-toggles .query-container.list-two-col .post-container,
body.bricks-is-frontend .loop-ctn-w-layout-toggles .query-container.grid .post-container {
animation: 300ms slide-up ease-in-out forwards;
}
/* 可选:根据查询容器布局定制内部元素样式 */
.loop-ctn-w-layout-toggles .query-container.list .post-container,
.loop-ctn-w-layout-toggles .query-container.list-two-col .post-container {
flex-direction: row;
}
.loop-ctn-w-layout-toggles .query-container.list .post-container img {
width: 30%;
max-height: 100%;
}
.loop-ctn-w-layout-toggles .query-container.list-two-col .post-container img {
width: 45%;
max-height: 100%;
}
}
@keyframes slide-up {
0% {
opacity: 0;
transform: translateY(40px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
重要提示:
-
这套代码基于这篇文章里的第 2 种网格方法。你可以修改前 3 个 CSS 变量来设置默认布局样式。
-
CSS 里包含一段可选的动画代码——记得把它换成你自己的布局名:
-
这个示例中我们决定给 list 和 list-two-col 布局改
flex-direction。为此需要加几行自定义 CSS:先把布局类挂到查询容器上(如下图红色方框所示),然后定位你要改的 CSS 选择器——本例是 post 容器和特色图:
最终效果
一切设置正确的话,前端应该能看到下面的效果:
结论
这个方案只靠一段 JS + 一段 CSS 就实现了查询循环的布局切换:切换按钮用 data-layout(布局名)和 data-layout-col(列数)两个属性驱动,脚本负责给查询容器切换布局类、更新 --grid-column-count 变量,CSS 用 grid 的 auto-fill + minmax 响应式算法(最小列宽 280px、断点 768px)自动排布。不需要任何外部插件或库。
💡 译者注:原文代码块中有一行
body.[bricks](https://brickslabs.com/go/bricks)-is-frontend,是原文抓取时残留的 markdown 链接产物,实际应为body.bricks-is-frontend(与同段其他选择器一致),翻译时已修正。