开箱即用的文本折叠

1.5k 2 mins
... ...

  写追剧感想的时候顺手捣鼓出来的长文折叠,代码很简单,基本上直接复制就能用。外加我的脑子还是处在不停 storm 的状态中,所以还是更新一些不太需要动脑子组织语言的内容……

  先来看效果:

  Mercy is hybris, charity is sacrilege.
  慈悲即傲慢,救济乃渎神。⸺五十五 孁(我说的)

  虽然只测试了纯文本,不过理论上任意元素都是能正常工作的,只是排版和动画上可能会出现一点小瑕疵,但是 CSS 代码没什么泛用性所以大家就各扫门前雪……【喂

文本部分

  为了保持代码简洁易懂所以文本输入的方式稍微有一点复杂,格式大概如下:

1
2
3
4
5
6
7
<div class="hidden-post">

折叠提示:

折叠正文折叠正文折叠正文折叠正文

</div>

  如果嫌 hidden-post 太长了可以自己改一个好打的,但我建议用输入法做一个快捷输入
  另外代码的设计思路是「div 中的第一个 <p> 子元素作为折叠提示」,所以代码里的每一个换行都不可以省略噢【为什么出现了迷之幼师语气……

JS 部分

  注:本代码需依赖 jQuery 运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$('.hidden-post').each(function() {
// 如果之前改了 class 这里也要做相应修改
let $div = $(this);
let $hint = $div.children('p:first-child');
let $hiddenP = $div.children(':not(p:first-child)');

$hiddenP.hide();

$hint.on('click', function() {
$hiddenP.slideToggle().toggleClass("active");
// 如果不需要透明渐变的话可以去掉 `.toggleClass()`
$(this).toggleClass("active-hint");
// 如果不需要提示按钮可以直接删掉这一行
});
});

  直接复制就可以开箱即用了!如果要添加提示按钮小箭头的话需要再添加一些 CSS。

CSS 部分

  在 css 里额外添加的东西:折叠正文的透明度渐变,折叠提示的指示按钮。

  这是透明度渐变的代码:

1
2
3
4
5
6
7
8
.hidden-post p {
transition: opacity 0.5s;
opacity: 0;
}
.hidden-post p.active,
.hidden-post p:first-child {
opacity: 1;
}

  这是提示按钮的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.hidden-post p:first-child::after {
content: '▶';
// 也可以用「展开」
cursor: pointer;
// 使鼠标经过按钮时显示为超链接样式,但其实点击整行字都能触发
background: #000a;
color: #fff;
transition: all .5s;
}
.hidden-post p:hover::after {
background: #000;
}
.hidden-post p.active-hint::after {
content: '▼'
// 也可以用「折叠」
}

  所以就是这样完成了……诶居然才写了 500 字吗不管了【

小 bug 调整

  昨天更新完之后突然发现展开和收起的动画会有一个小小的卡顿,像这样:

  这是会卡的:

  前面的那个不会卡

  测试后发现是一个非常玄学的 bug……
  首先 blog 的 <p> 样式是上下各 margin 1em,两个连续的 <p> 元素之间理论上会间隔 2em,但浏览器会自动把两个 margin 部分重叠,于是最终显示的间隔还是 1em。
  折叠提示和后续的正常文本之间也存在这种重叠关系,但是当折叠文本播放展开动画的时候,「折叠提示」和「后续文本」之间就会出现一个新的 <p> 元素,而动画刚开始播放的时候这个 <p> 元素的高度为 0,所以看起来就会像两个文本之间突然多了 1em 的间距。

  大概感觉就是这样:

【点击前】

折叠提示

后续文本

【点击后】

折叠提示

后续文本

  解决的方法非常简单,只要把 hidden-post 后续紧跟着的 <p> 元素的 margin-top 设置为 0,浏览器就不会再自动处理重叠了。

1
2
3
.hidden-post + p {
margin-top: 0;
}