干货 | 如何用纯 CSS 方式实现内容区域的更多展示效果
作者简介
林小志,就职于携程技术中心用户体验与设计中心。运营微信公号“闲谈CSS那些事儿”,ID:talk-css。
在以前,包括现在还是会有很多网站中为了能够展现更多内容,一般都会把某个详细内容的模块隐藏部分,然后通过点击“更多”或者“展开”之类的方式让用户去查看更多的内容。在实现这个效果的时候,最简单的办法就是通过 JS 的方式直接修改容器的高度,从而展现更多的内容。
通过 JS 的方式修改内容模块的容器高度,可能会有两种方式:
height: auto;
通过内容来撑开高度;给定一个固定的高度值,这种方式一般不太会去采用,因为会对内容区域的文本数量做限制;
显然通过 JS 的方式给一个 auto
的高度值是最方便的,不需要去考虑其他因素,模块的高度会根据内容而展开。如果是要复杂一点,效果好一点的话,可能会有人加一些文字颜色变化啊、边框样式改变啊,甚至是背景样式的变化,更甚至会去考虑浏览器解析 CSS 时的重绘以及重排的因素,最终是通过 JS 切换 DOM 节点元素的 className 来达到最终的效果。
但无论是通过哪种方式,反正改变高度是必须的,不然怎么看到更多的部分呢。
虽然通过 JS 实现这种方式十分简单:
document.getElementById("more").style.height = "auto";
但是这种简单的操作对于我这样一个不会写 JS 的人来说,还是太难了,把高度切换成 auto
后,还要考虑再次点击后切换到原本应该有的高度。怎么说呢,JS 的方式真的很简单,如果是用 JQ 的话,那就更简单了,可是,我真的不写 JS 的,所以,突发奇想,要不用 CSS 来试试……
设想
实现展开更多查看更多内容的时候,前面提到了,最基本的前提就是修改元素的高度,让更多的文字显示出来。那么在 CSS 中带有一点交互性的选择符会有哪几个呢?
:target
:checked
:target
是用在锚点中的,而锚点是少不了 <a>
标签元素的。在 <a>
标签元素中,我们还会去用到 :hover
、:active
等,但是这几个伪类选择符用在这里的话,操作起来似乎并不理想,不可能考虑鼠标经过或者点击不放开鼠标的时候来实现我们需要的效果吧?
:checked
是用在单选框和复选框中的,然而作为单选框的 <input>
在使用的时候,一旦选中,就只有切换到另外一个单选框才可以切换,感觉有点复杂了,所以,这个选择符能结合使用的应该也就只有 <input type="checkbox">
这个复选框了。
除了这两个选择符以外,在 CSS 中好像也没有其他什么选择符方式可以用了,表单元素中虽然有很多带有交互性的选择符,但似乎对表单元素是有要求的,想想还是算了。
哦,还有,:focus
这个伪类选择符也不会去考虑了,我应该不会需要当用户把焦点移到“更多”或者“展开”的文字上面时就展开全部的内容的,这样的交互会怪怪的。如果有兴趣的可以玩玩,我没兴趣……
开工
有了设想之后,接下来要做就是根据自己的设想开工啦。设想可能会失败,也可能会成功。失败的可能是 HTML 结构不合理,或者也有可能是这个设想本身就是一个错误。无论成功还是失败,实践是最佳的方式。
:target
锚点的方式
这个设想主要是来源于当初自己尝试用 :target
做 tab 选项卡的时候,可以参考一下《调戏锚点与 :target 选择符》当时的这篇内容。对于目前这个展示更多信息的模块,我采用的 HTML 结构如下:
<div class="box" id="more">
<p>通过锚点的特性来展示更多的内容部分。通过锚点的特性来展示更多的内容部分。通过锚点的特性来展示更多的内容部分。</p></div><div class="detail_ctrl"><a href="#more">更多</a> <a href="#">缩小</a></div>
HTML 结构简单说明
首先在需要控制展示内容的部分需要增加一个
id
,因为:target
是通过id
的方式获取到锚点的位置的;然后把“更多”和“缩小”(文案我这边随意写的)的位置移出内容部分,其实不移出来也可以 OK 的,这里我主要是为了能够更好地通过 CSS 去展示效果;
最后是把“更多”和“缩小”两个部分的链接锚点改变一下;
“更多”指向到我们需要的锚点部分;
“缩小”为一个
#
空的锚点,也就是会跳转到页面顶部,或者可以根据需要换成其他的;
实现思路
有了 HTML 结构后,接下来要做的就是写 CSS 了。思路很简单,就是当用户点击“更多”的时候,其实这个时候我们所看到的锚点的指向将会是 id="more"
的这个 div
标签上,然后再通过 :target
来改变样式即可。
body {font-size: 20px;}.box { height: 2em; overflow: hidden; width: 10em; margin-bottom: .5em; line-height: 1em; border: 1px solid #000;}#more:target { height: auto;}
从这个 CSS 样式中我们可以看到,当我们 .box
这个 div
获取锚点指向后,height
会从原来的 2em
高度变成 auto
自适应高度了,这样内容区域也可以正常显示了。
注:这里使用 #more:target
而不是 .box:target
主要是为了能够更好的辨识。
现在这样基本上已经能够满足我们的需求了,然而链接部分的“更多”和“缩小”还并没有进行切换。那么这个时候我们就需要继续使用 :target
锚点的方式来查找到相邻的兄弟元素中的 <a>
标签元素了。
.detail_ctrl a { display: inline;}.detail_ctrl a + a { display: none;}#more:target + .detail_ctrl a { display: none;}#more:target + .detail_ctrl a + a { display: inline;}
这里的 <a>
标签元素是内联元素,所以我们要设置为 display: inline;
来显示,不显示的元素就直接 display: none;
即可。接着就是当我们的 :target
锚点被触发的时候,切换一下这两个元素的 display
属性值,最后我们要的效果就也就有了,就像这样:
缺点
刚开始完成这个 demo 后可能觉得,哇塞,好像挺有意思的感觉,其实我们仔细想想,可能会发现这些问题,最终在实际项目中是不是要去用这个方式就要三思而行了。
点击锚点的位置后,可能会导致页面的跳动;
在地址栏直接输入带锚点的链接后将会直接展开,例如: http://url/page.html#more;
占用了一个
id
名,可能需要跟 JS 开发人员沟通;
:checked
复选框的方式
就我个人所知道的情况,目前很多人都喜欢用 :checked
的方式来实现很多效果,尤其是 tab 选项卡的形式居多,各种各样的 tab 选项卡,通过 :checked
和 ~
的结合来实现。而现在我也是用这个方式来实现这个更多内容展示的效果。
实现思路
通过 :checked
的方式来操作,无非就是当复选框被选中或者没有选中的时候,然后通过相邻元素选择符的方式查找到需要的元素来改变样式。根据 CSS 选择符的特点,从上面往下查找,所以,HTML 结构如下:
<input type="checkbox" id="more_c"><div class="box">
<p>通过 input 的复选框以及 ~ 选择符的方式来控制元素内容的显示,从而展示更多的内容部分。</p></div><label for="more_c">更多</label>
还是习惯性的把“更多”放在最后,不过这个时候我们的“更多”是包含在 <label>
中了。因为 <lable>
的 for
属性可以帮我们找到相对应的 <input>
元素,只要 id
与 for
的这个值相同即可,无论他们两个在页面的什么位置,只要同一个页面即可。
如果你们不乐意“更多”在底下的话,也可以把“更多”放在前面,反正最终的页面效果是根据大家的实际需求而定的。
效果实现
有了这个 HTML 结构以及整体的思路后,我们就可以开始写 CSS 部分的内容了:
body {font-size: 20px;}.box { height: 2em; overflow: hidden; width: 10em; margin-bottom: .5em; line-height: 1em; border: 1px solid #000;}#more_c:checked + .box { height: auto;}
跟前面一样,当 <input>
复选框被选中后,也就是 :checked
选择符被实现了之后,我们让这个 <input>
复选框相邻的内容元素 .box
高度改变为 auto
就可以了。
然后接着还是要考虑 <label>
中的文字变化了。这里换个思路来操作,利用 :after
或者 :before
的 content
属性来改变文字内容,这样我们在 HTML 结构中就可以少写一个标签元素了。
#more_c:checked ~ label[for="more_c"]:after { content: "闭合"; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: #fff; z-index: 2;}
这里我们使用了 ~
同级相邻元素选择符的方式来查找到 <label>
标签,然后设置了 :after
元素的样式。
content
中添加了一个切换“更多”后的文字内容;position
设置absolute
,并且把四个方向设置为 0 之后,会将该元素针对父级元素的容器大小而撑满;background
和z-index
通过这两个方式让:after
的内容覆盖在父级元素上面;
这样“闭合”的文字就会在“更多”上面了,而且是当 :checked
被触发的时候。当然,关键的还有就是我们不能少了对 <label>
增加一个定位特性,然后 :after
能够准确找到相对应的父级。
label { position: relative; cursor: pointer;}
同时增加一个手型的鼠标效果,让用户明确知道这个“更多”是可以点击的(突然觉得我好贴心啊……)。
完善最后效果
做了以上的操作之后,我们并没有真正的完成,因为 <input>
复选框还显示在页面中呢,我们不能让用户去操作这个复选框,或者说我们不应该让用户看到这个复选框啊。所以呢,必须隐藏起来,而且不让用户可操作。
#more_c { position: absolute; z-index: -1;}
为了不让用户能够操作这个元素,我们随便增加一个定位属性,然后通过 z-index: -1;
方式放到容器的底下去,这样用户就不会那么容易操作到了。如果还不放心,那么就再来一个 top: -999em;
,这样用户看都看不到了,更省心了。
而我为了不让用户看到,在上面的基础上增加了 opacity: 0;
,让这个 <input>
复选框透明了。
缺点
感觉上这个方式似乎很完美了有木有,个人觉得有啊,能有什么缺点呢?
因为代码洁癖,所以不喜欢把表单元素用在这里?
id
名又被用了?……
说实在的,因为个人对于 :checked
和 ~
的结合感觉太过于完美了,实在不知道应该怎么说缺点。
小结一下
反正无论怎么样,在写 CSS 的时候,个人并不觉得有任何方式是唯一的解决方案,根据不同 HTML 结构我们可以做的事情有很多,最终实现的效果也将会不同。然而对于这个纯 CSS 方式实现更多内容的展现效果来说,个人并不推崇,究其原因大概有这么几点:
CSS 主要是做展现排版的,而并不是过分去做交互效果的;
我个人多多少少还是有一点代码洁癖的;
效果写的太特殊了,需要考虑后续维护上、跨团队沟通上会不会存在问题;
最后最关键的是,无论 :target
还是 :checked
,甚至是 ~
同级兄弟选择符,我们都需要去考虑兼容性啊。我想这个才是我要去考虑的最终的因素才对!
如果不去考虑兼容性的问题,我绝对会去用,什么沟通不沟通,不过是一句话的事情;什么代码洁癖,难道不用这个方式我的代码就很干净了;什么过多的交互效果,难道 :target
和 :checked
不是与用户互动的伪类选择符吗?
所以,我绝对不会自欺欺人,究其原因只有一个,兼容性!
最后补充一下,前面说了那么多,最后我个人比较喜欢 :checked
的原因是地址栏是干净的,没有锚点,并且在没有 JS 的基础上,也可以实现一个简单的交互效果。
延伸阅读:
啊,你看到这里啦~
携程技术中心目前开设了架构/移动/大数据/前端/运维5个微信群,方便关注同一领域的小伙伴们交流。我们也会在群里及时同步携程技术中心主办的相关话题线上和线下活动。
如想进群交流,请加携程技术中心小助手个人微信号ctrip_tech,标注相关领域(如大数据),之后小助手会拉你入群哦。
喜欢我们的会点赞,爱我们的会分享~