当前位置: 首页 > Web前端 > CSS

巧用CSS伪类选择器实现九宫格的九种风格

时间:2023-03-30 14:39:27 CSS

三剑客传奇前端世界有这么三把剑——HTML、CSS、JavaScript。HTMLHTML——无非是一些标签,带有一些属性,它不是一种编程语言,所以很多人可能看不起它,甚至是小学生,但实际上HTML只是被隐藏起来了。如果它搞“语义化”,那么很多人,包括很多从业多年的前端er,都会“死”在它的刀下。更何况现在是9012还有很多人还有div/span。虽然HTML的语义也值得研究和讨论。但这不是本文的重点。作为一门真正的编程语言,JavaScript虽然有一些被诟病的“特点”,但它也有可能成为今天被广泛使用的语言,而JS也确实在不断完善和完善。今天的JS今非昔比。处处“踢堂”,打后端,踢移动端,在桌面端显赫。不管结果如何,这份勇气实在是难能可贵。以至于《江湖》中流传着这样一句话:凡是能用JavaScript编写的应用,终将用JavaScript编写。JS在江湖的地位可见一斑。虽然JS很流行,但它仍然不是今天的重点。CSS虽然JS很强大也很受欢迎,但是很可能以男粉居多,女生应该更喜欢CSS。作为最会“打扮”的三剑客之一,CSS比亚洲四大法术厉害多了。HTML本来是“很丑”的,但是在CSS的加持下,可以变得“千姿百态”。虽然CSS有很多美化工具来美化HTML的脸,但是这些工具并不像现实中女生用的那些——粉扑、眉笔、睫毛刷……都是单作用的,有时候一个效果需要很多属性应用在同时,所以CSS是非正交的。因为是非正交的,所以经常会出现很多难以理解的现象。用不好,妆容就毁了。CSSVSJS相信很多前端对自己的JS能力都非常自信,可以熟练甚至熟练的使用,但是恐怕没有人敢说自己精通CSS。可见CSS的难度其实不亚于JS。其实两者没有可比性,CSS毕竟不是编程语言,但是由于其非正交的特性,它有很多难以理解的现象,也有很多意想不到的神奇特性。作者对两者也略知一二。我写这篇文章是因为我确实遇到过这样的需求,但我认为其中使用的技术可能对某些人有所帮助,所以我在这里自嘲。如表达有误,或有不足之处,欢迎批评指正。需求那么需求到底是什么?俗话说“有图有真相”,那么我们就来看看最终的设计稿长什么样吧。这是一个场景,可能会出现一些社交应用或者一些基于内容的应用。无非是微信朋友圈之类的消息。业务场景很常见,但既然是图文展示,势必会涉及到布局。对于问题,我们可以重点关注里面的图片。九宫格图片是一种很常见的图片布局,但是“不怕,不怕,就怕设计有思路”,从上图可以看出,九宫格可以说是对应了九种不同的风格.读者可以先尝试思考如何实现这样的设计稿。可能很多开发者收到这样的设计稿时,都会选择用JS修改对应位置图片的类。再加上三大框架的流行,DOM的修改更加方便,无论是React的JSX还是Vue的模板语法都非常方便。或许是太认真了,作者一直认为layout/style的实现应该由CSS来完成,JS不应该参与其中,就像那句话:God'stoGod,Caesar'stoCaesarisliketoday's中所述常说的MV*理论,HTML、CSS、JavaScript各有职责范围,“美如花”的工作应该由CSS单独承担。所以,笔者在收到这样一份设计稿的时候,就在思考如何让三剑客专注于自己的职责。重申一下,这个要求的重点是九宫格,其他请无视。实现HTML首先,HTML负责页面的骨架。从理论上讲,DOM越简单越好。HTML只需要将这些图片一张一张的排列即可。因此,HTML结构应该是这样的:

这应该是最理想的DOM结构,没有多余的元素。接下来,CSS是重头戏。如何在上述结构下完美还原只有CSS的设计稿?我们先给元素类写css。
我们给img一个class--grid-img,以及后续的所有样式布局将只使用这一类来定义。整个九宫阁的修真,不会有二品。而且整个执行过程不会有JS的参与。看到这里,可能有读者会产生疑惑。如果不需要二次类,没有JS的参与,如何实现不同数量的图片在不同位置的图片大小和圆角的设置?接下来,就是见证奇迹的时刻。在揭秘魔术之前,让我们先来看看魔术的结果:纯CSS实现九宫格DEMO揭秘在写代码之前,我们要清楚自己想要达到什么样的效果,想要达到什么样的目的实现。因此,我们需要理解需求,将他们的文字需求转化为代码语言。就像我们在学校学习解数学题时,将题目信息中的关键点提取出来,转化为数学公式进行解答。那我们来看看需求都写了什么。这里的截图只是展示了不同张数的图片之间的区别,并没有展示出图片尺寸最重要的信息,所以这里我用文字整理如下【2倍稿尺寸】:一张图片:宽高:320*320;圆角:10101010;两张图片:宽高:332*332;第一个圆角:100010;第二个圆角:010100;间距:6张三张图片图片宽高:220*220;第一个圆角:100010;第二个圆角:0;第三个圆角:010100;间距:5;四张图片的宽高:220*220;第一个圆角:10000;第二个圆角:01000;第三个圆角:00010;第四个圆角:00100;间距:5;五张图片1~3宽高:220*220;4~5宽高:332*332;第一个圆角:10000;第二个圆角:0;第三个圆角:01000;第四个圆角:00010;第五个圆角:00100;1~3图像间距:5;4~5图像间距:6;六张图片宽高:220*220;第一个圆角:10000;第二个圆角:0;第三个圆角:01000;第四个圆角:00010;第五个圆角:0;第六个圆角:00100;间距:5;七张图片的宽高:220*220;第一个圆角:10000;第二个圆角:0;第三个圆角:01000;第四个圆角:0;第五个圆角:0;第六个圆角:00100;第七个圆角:00010;间距:5;八张图片1~6宽高:220*220;7~8宽高:332*332;第一个圆角:10000;第二个圆角:0;第三个圆角:01000;第四个圆角:0;第五个圆角:0;第六个圆角:0;第七个圆角:00010;第八个圆角:00100;1~6张图片间距:5;7~8画面间距:6;九张图片的宽高:220*220;第一个圆角:10000;第二个圆角:0;第三个圆角:01000;第四个圆角:0;第五个圆角:0;第六个圆角:0;第七个圆角:00010;第八个圆角:0;第九个圆角:00100;间距:5;以上就是九种情况对应的样式。相信很多人都很困惑,怎么只用CSS就可以实现这么多不同的样式,更不用说只用一个类了。答案就不一一列举了。这种安排看起来很复杂。事实上,这是因为这里列出了所有不同的样式。如果我们只关注其中一种风格差异怎么办?比如我们先看宽高:一张图片:宽高:320*320;两张图片:宽高:332*332;三张图片:宽高:220*220;四张图片:宽高:220*220;五张图片1~3宽高:220*220;4~5宽高:332*332;六张图片宽高:220*220;七张图片宽高:220*220;八张图片1~6宽高:220*220;7~8号宽高:332*332;九张图片宽高:220*220;是不是瞬间觉得简单多了?如果用一个类来实现这个需求呢?读者可以试着思考一下。其实当你像这样看到不同的值对应不同的情况时,不知道你脑子里会不会有一个想法——这里面应该有某种规律吧。这就好比当这样一组数字出现“1、1、2、3、5、8、13、21、34”时,难免其中隐藏着一定的规律。但乍一看上面的情况,似乎并没有什么明显的规律。我们不妨继续进一步整理宽高的情况,列出相同值的情况:220*220——3,4,5(1~3),6,7,8(1~6),9320*320——1332*332——2,5(4~5),8(7~8)一下子,情况就减少到了三种。你现在找到什么窍门了吗?通过这样一步一步的排序,我们最终发现规律是:当图片大于2时,宽高为220,5和8除外,即最后一张图片为两张图片时,最后两张图片的宽高都是332,只有一张图片的宽高是320,总结完规则,我们就要考虑怎么转成css了,这才是本篇的重点article,也是实现这个需求的难点。难点就在于——如何知道图片大于2?你怎么知道只有1、5、8张图片?只有解决了这两个问题,才有办法在相应的情况下设置图片的大小。说到选择一个组中的元素个数,大家应该都能想到使用两个伪类选择器:nth-??child/:nth-of-type。这两个选择器有一点区别,但这不是本文的重点。不懂的同学可以自行查阅文档。:nth-child我们都知道:nth-child可以选择某个元素,甚至可以用an+b这样的表达式来选择满足该表达式的第n个元素,所以我们自然而然地想到大于2的问题。:.grid-img:nth-child(n+3){宽度:220px;height:220px;}嘿,等等,好像有点不对劲。这里只选取第二个之后的元素,要求是当图片大于2时,所有元素都是220*220。所以好像nth-child解决不了。不,它使用的是第nth-child,但它需要更灵活。我们不妨先跳过这个,从更简单的第二个开始:只有一张图片时,宽高都是320,这样的需求,CSS是怎么实现的呢?经过以上错误尝试,相信大家不会认为是第n胎了吧?也许有人会想:first-last?但是你再想想,你发现first-child就是nth-child(1)。笔者就不再开玩笑了,答案是它也是一个伪类选择器:only-child。这个选择器可能很多读者都不会用到,甚至可能没有听说过。它的功能就像它的名字一样,非常符合我们的需求——只有一张图。所以CSS可以这样写:.grid-img:only-child{width:320px;height:320px;}如果读者去看MDN文档,会发现:only-child其实可以写成:first-child:last-child,然后你可能会发现-伪类选择器还是可以写成陆续?伪类选择器是很常用的,但是这样用的并不多,所以可能有些人不知道可以这么用。伪类选择器写成两个集合的交集,即:first-child:last-child意思是——第一个子元素和最后一个元素——翻译过来就是只有一个元素的情况。至此,我们的第二种情况就实现了。好吧,经过这样的发现和尝试,我们再回过头来看第一篇文章,就会清楚很多。当图片大于2时,宽高都是220,5和8除外,即当最后一张图片有两张图片时,最后两张图片的宽和高都是332。我们可以看到大部分图片都是220的,只有少数例外。我们完全可以把图片默认设置为220,然后针对少数特殊情况重新设置。没有必要单独列出每个案例。例外的是在有5张和8张的时候最后两张是332。知道了选择器的取交功能后,我们就可以用这个trick来解决这个问题了://3n+1表示每行的第一个,两个一系列的伪类表示取交//所以这里的意思是该项目不仅是行首而且是倒数第二个,下游行也是如此:nth-child(3n+1):nth-last-child(2),:nth-child(3n+2):last-child{width:332px;height:332px;}最后我们实现不同情况宽高的代码是[scss]:.grid-img{//大多数情况下是220。初始化大多数值。宽度:220px;高度:220px;//只有一个的时候,宽高都是320,每边都有圆角。&:only-child{width:320px;高度:320px;边界半径:10px;}//3n+1表示每一行的第一个,两个伪类的串联表示取交集//所以这里的意思是这个项目不仅是行的开头,也是倒数第二个在同时,下行也是如此&:nth-child(3n+1):nth-last-child(2),&:nth-child(3n+2):last-child{width:332px;高度:332px;}}至此,魔术的奥秘就揭开了。它似乎并不是什么神奇的技术,而是一个大多数人可能不熟悉的特性。包括其他的圆角和间距也是用这个技巧实现的,笔者不再赘述。完整代码.grid-img{display:inline-block;//在大多数情况下为220。初始化大多数值。宽度:220px;高度:220px;//只有一个的时候,宽高都是320,每边都有圆角。&:only-child{width:320px;高度:320px;边界半径:10px;}//3n+1表示每一行的第一个,两个伪类的串联表示取交集//所以这里的意思是这个项目不仅是行的开头,也是倒数第二个在同时,下行也是如此&:nth-child(3n+1):nth-last-child(2),&:nth-child(3n+2):last-child{width:332px;高度:332px;}//每行第二边有5px的边距,大部分元素都是220,加上边距,最终容器宽度为220*3+5*2=670&:nth-child(3n+2){边距:05px;//例外是当最后一行是两个元素时,宽度为332,所以margin=670-332*2=6&:last-child{margin-left:6px;}}//从第二行开始有上边距&:nth-child(n+4){margin-top:5px;}//第一个元素有一个固定的左上角&:first-child{border-top-left-radius:10px;}//最后一个元素必须没有右边距,并且有一个右下圆角&:last-child{margin-right:0;border-bottom-right-radius:10px;}//最后一行第一个元素有一个左下圆角//这里也可以枚举,第1、4、7个元素//但是这里需要注意的是当只有四个元素时,布局是不同的,需要分开来处理这种情况,具体见下面:&:nth-child(3n+1){&:last-child,&:nth-last-child(2),&:nth-last-child(3){border-bottom-left-radius:10px;}}//只有最右上角的元素有右上角,其实就是第一行的最后一个元素//因为上面只处理了一个,这里只需要列出剩下的两种情况&:nth-child(2):last-child,&:nth-child(3){border-top-right-radius:10px;}//当只有4个元素时,布局为2x2,与其他情况不同,这里需要特殊处理//为了断行,让第二个元素的右边距大一些,以便挤压thespaceandlet3,4runtothenextline//然后这里是第二个元素右边的圆角&:nth-child(2):nth-last-child(3){margin-right:220px;border-top-right-radius:10px;}//当只有4个元素时,第三个元素设置了边距,因为它符合之前的一些常见情况和圆角,这里需要重新设置这些样式&:nth-child(3):nth-last-child(2){顶部边距:5px;右边距:5px;边界半径:00010px;}//这也是处理4个布局时最后一个元素的样式&:nth-child(4):last-child{border-radius:0010px0;}}