前言 🎙️
结果页列表过长我们一般会要求移动端默认用遮罩将内容遮盖,仅展示前 N 条用户最感兴趣的,点击『展开更多』才展示全部内容,因为移动端尺寸有限,PC 端则默认全部展示即可。
如何做到类似功能?本文将基于知乎的『展开阅读原文』来完成我们的需求。
原理 🧠
CSS 如何让 N 条之后的内容隐藏。本文最关键的两个知识点:
- 如何选择
N条之后的内容::nth-child(n + N) - 结合
:has(:nth-child(n + N))来选定含有大于N条内容的父元素
HTML 🧱
假设我们的结构如下
<div class="result">
...
</div>
<div class="result">
...
</div>
<div class="result">
...
</div>
<div class="result">
...
</div>
<div class="result">
...
</div>
第一步先用容器 section 将其包裹起来,然后尾部增加一个 div.references-expander 做为我们的遮罩,将容器设置 position: relative;,因为我们需要将遮罩定位成绝对布局盖在内容上面。遮罩里面就一个按钮:文字加一个向下的 icon,整个遮罩的 HTML 部分从知乎 copy 过来即可。按照需求自定义下文字即可,并且给按钮绑定点击事件 expandReferences。
<section class="references-wrapper" style="position: relative;">
<div class="result">
...
</div>
<div class="result">
...
</div>
<div class="result">
...
</div>
<div class="result">
...
</div>
<div class="result">
...
</div>
<!-- 遮罩 -->
<div class="references-expander">
<button type="button" class="btn btn-link btn-sm" onclick="expandReferences(this)">
Expand to read more
<span style="display:inline-flex;align-items:center">
<svg width="24" height="24" viewBox="0 0 24 24" class="Zi Zi--ArrowDown ContentItem-arrowIcon" fill="currentColor"><path fill-rule="evenodd" d="M17.776 10.517a.875.875 0 0 1-.248 1.212l-5.05 3.335a.875.875 0 0 1-.964 0L6.47 11.73a.875.875 0 1 1 .965-1.46l4.56 3.015 4.568-3.016a.875.875 0 0 1 1.212.248Z" clip-rule="evenodd"></path></svg>
</span>
</button>
</div>
</section>
CSS ✨
CSS 部分默认隐藏遮罩,只有移动端才开启 @media (max-width: 767px)。重点来了,移动端也并不是任何情况都开启,必须大于 N 条,我们用 3 条举例。如何用 CSS 实现?
第一步默认隐藏:
.references-expander {
display: none;
}
接下来要求移动端且大于 3 条,即 ≥ 4才展示。我们用到了 :nth-child(n+4) 来选中 3 条之后的,并且用 :has() 来匹配容器内部有超过 3 条数据,则将遮罩变成显示状态:
@media (max-width: 767px) {
.references-wrapper:has(.result:nth-child(n + 4)) .references-expander {
display: flex; /* 满足两个条件则显示遮罩 */
}
}
那遮罩如何实现,因为我们的遮罩是半透明的。从上到下从透明变成不透明。background-image: linear-gradient(to bottom, transparent, #ffffff 48px); 即可实现,遮罩部分整体样式如下:
.references-expander {
display: none;
width: 100%;
height: 110px;
position: absolute;
bottom: 0;
background-image: linear-gradient(to bottom, transparent, #ffffff 48px);
justify-content: center;
align-items: flex-end;
}
@media (max-width: 767px) {
/* 到 JS 部分会讲到 */
.references-wrapper.expanded.expanded.expanded.expanded .result {
display: block;
}
.references-wrapper:has(.result:nth-child(n + 4)) .result:nth-child(n + 4) {
display: none;
}
.references-wrapper:has(.result:nth-child(n + 4)) .references-expander {
display: flex;
}
}
JS 🔗
点击展开逻辑,其实只需 CSS 即可实现 radio 结合 :checked 伪类,这里为了理解简单,还是用 JS 监听点击事件结合 CSS 实现。最主要逻辑是点击遮罩时给其容器增加 .expanded,即让下面 CSS 生效,将第三条之后的所有数据展示出来:
/* 重复使用类名达到增加 CSS 权重的目的 */
.references-wrapper.expanded.expanded.expanded.expanded .result {
display: block;
}
JS 部分很容易理解,主要解释 closest:找到最近的自己或祖先元素。
/**
* @param {HTMLButtonElement} btn
*/
window.expandReferences = (btn) => {
// 这句也可以直接 btn.closest('.references-expander').remove() 更简洁
btn.closest('.references-expander').style.display = 'none';
btn.closest('.references-wrapper').classList.toggle('expanded');
}
效果
注意请在窄屏幕下才可看到展开功能
总结 🎯
本文只使用了少量的必要的 JS 和一些 CSS 高级语法实现了移动端遮罩效果。涉及到的知识点:
- 媒体查询
:has()重点:nth-child(n + N)重点linear-gradientclosestclassList.toggle- 最后一条:可以重复使用类名达到增加 CSS 权重的目的