前言

据不完全统计(其实就统计了自己身边的朋友和同事),在刨除抖音或快手这一类短视频 APP 后,每天在手机上花费时间最长的就是刷微博和逛朋友圈。

在刷微博和逛朋友圈的时候经常会看到这种东西:

它有一个高大上的名字:九宫格。
顾名思义,九宫格通常为如图这种三行三列的布局。

微信客户端就用到了这种布局方式:

大家最熟悉的朋友圈也采用了九宫格:

还有微博:

它在移动端的运用十分的广泛,而且不仅仅是在移动端的运用,它甚至还运用到了一些面试题中,因为九宫格可以很好的考察面试者的 CSS 功底。

边距九宫格

九宫格通常分为两种,一种是边距九宫格,另一种是边框九宫格。

边距九宫格就是朋友圈那种每张图都带有一定边距的那种:

这种其实反而更简单一些,因为不涉及到边框问题,像这种几行几列的布局用网格布局(grid)简直再合适不过了。

但考虑到大家普遍对网格不太熟悉,所以咱们用同样适合几行几列的表格布局来实现,为什么不用万能的弹性盒子(flex)来做呢?因为下面那道面试题就是用flex实现的,不想用两个一样的布局来实现,为了美观一点,这里使用了一个中文渐变色的库:chinese-gradient,来看代码:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><!-- 在这里用link标签引入中文渐变色 --><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/chinese-gradient"><style>/* 清除默认样式 */* { padding: 0; margin: 0; }/* 全屏显示 */html, body, ul { height: 100% }/* 父元素 */ul {/* 给个合适的宽度 */width: 100%;/* 清除默认样式 */list-style: none;/* 令其用table方式去显示 */display: table;/* 设置间距 */border-spacing: 3px}/* 子元素 */li {/* 令其用table-row方式去显示 */display: table-row}/* 孙子元素 */div {/* 令其用table-cell方式去显示 */display: table-cell;/* 蓝色渐变 */background: var(--湖蓝)}</style>
</head>
<body><ul><li><div></div><div></div><div></div></li><li><div></div><div></div><div></div></li><li><div></div><div></div><div></div></li></ul>
</body>
</html>

运行结果:

可以看到在 DOM 结构上我们并没有用到 <table>、<tr>、<td> 这类传统表格元素,因为在这种情况下只是用到了表格的那种几行几列而已。但实际上九宫格并不是表格,所以为了符合 W3C 的语义化标准,我们采用了其他的 DOM 元素。

在有些适合使用表格布局但又不是表格的情况下,可以利用 display 属性来模仿表格的行为:

  • display: table;相当于把元素的行为变成<table></table>
  • display: inline-table;相当于把元素的行为变成行内元素版的<table></table>
  • display: table-header-group;相当于把元素的行为变成<thead></thead>
  • display: table-row-group;相当于把元素的行为变成<tbody></tbody>
  • display: table-footer-group;相当于把元素的行为变成<tfoot></tfoot>
  • display: table-row;相当于把元素的行为变成<tr></tr>
  • display: table-column-group;相当于把元素的行为变成<colgroup></colgroup>
  • display: table-column;相当于把元素的行为变成<col></col>
  • display: table-cell;相当于把元素的行为变成<td></td><th></th>
  • display: table-caption;相当于把元素的行为变成<caption></caption>

边框九宫格

可能大家看了前面的内容觉得:就这?这么简单还想让人原形毕露?

那咱们来看这么一道题:

要求如下:

  • 边框九宫格的每个格子中的数字都要居中
  • 鼠标经过时边框和数字都要变红
  • 点击九宫格会弹出对应的数字

看起来还是没什么大不了对不对?是不是觉得就是把九宫格加个边框就行了?如果你是这么想的话,那么你写出来的九宫格将会变成这样:

是不是跟想象中的好像不太一样?为什么会这样呢?

因为给每个盒子加入了边框以后,在有边距的情况下看起来都挺正常的,但要将他们合并在一起的话相邻的两个边框就会贴合在一起,肉眼看起来就是一个两倍粗的边框:

那么怎么解决这个问题呢?

解法1

不是相邻的两个边框合并在一起会变粗吗?那么最简单粗暴的办法就是让两个相邻的盒子的其中一个的相邻边不显示边框不就完了!就像这样:

这么做完全可以实现,绝对没毛病。但这种属于笨方法,如果给换成四宫格、六宫格、十二宫格,那么又要重新去想一下该怎么实现,而且写出来的代码也比较冗余,几乎每个盒子都要给它定义一个不同的样式。

如果去参加面试的时候这么实现出来,面试官也不会给你满分,甚至可能连个及格分都不会给。但毕竟算是实现出来了,总比那些没实现出来的强点,不会给零分的。

解法2

上面那种实现方式要给每一个盒子都写一套不同的样式,而且还不适合别的像六宫格、十二宫格这类,代码冗余、可复用性差。

那么怎么才能每个盒子只用到一个样式,并且同样还适用于别的宫格呢?来看看这个思路:

但是仔细一看经不起推敲啊:整个九宫格最右边和最下边的边框都没有了!其实只要咱们在父元素上再加上右侧和下侧的边框即可:

而且并不一定非得是这个方向的,别的方向也可以实现啊,比如酱婶儿的:

酱婶儿的:

还有酱婶儿的:

这种方式不管你是4、6、9还是12宫格,只需在子元素上加一个样式即可,然后再在父元素上加一个互补的边框样式。

解法3

上面那种解法其实已经可以了,但还不是最完美的,那么它都有哪些问题呢?

  • 首先,虽然换成别的宫格也可以复用,但都只适合"满"的情况。比如像朋友圈,最大就是九宫格对吧?但用户可以不是每次都发满九张照片,有可能发7张、有可能发五张,这样的话就会露馅(所以朋友圈采用的是边距九宫格而不是边框九宫格)。

  • 其次,它并不适合这道面试题,因为这道面试题的要求是在鼠标移入时边框变红,而上面那种解法会导致每个盒子的边框都不完整,所以当鼠标移入时效果会变成这样:

那么怎么样才能完美的解出这道题呢?首先每个盒子的边框不能再给它缺斤少两了,但那又会回到最初的那个问题上去:

有的面试题就是这样,在你苦思冥想的时候怎么也想不出来,但是稍微给点思路立马就能明白!

其实就是每个盒子都给它一个负边距,边距的距离恰巧就是边框的粗细,这样后面一个盒子就会"叠加"在前面那个盒子的边框上,我们来写一个粗点的半透明边框演示一下:

中间那些颜色变深了的就是叠在一起的边框,由于是半透明,所以叠在一起时颜色会变深。

不过一些比较细心的朋友可能会纳闷:既然所有盒子都用负边距向左上角移动了,岂不是九宫格不会处在原来的位置上了,没错是这样的!所以我们需要让最左边那一排和最上面那一排不要有负边距,这时候就要考察候选人的CSS水平了,看看他/她能不能够灵活运用伪类选择器:每一行的第一个,应该怎么写?

  • :nth-child(1), :nth-child(4), :nth-child(7)

这样也能实现,不过更好的方式是写成这样:

  • :nth-child(3n+1)

最上面那一排负边距可以不用管,因为如果页面上的九宫格往左边移动了,哪怕只有一两像素,也会导致和页面上的版面无法对齐,而往上移动个一两像素的话谁也看不出来。

但如果要写的话大多数人想的可能是这样:

  • :first-child, :nth-child(2), :nth-child(3)

而更好的方式是这样:

  • :nth-child(-n+3)

每个宫格内的数字要居中,这里推荐用grid,因为九宫格可以用flex去实现,但里面的内容还继续用它去实现的话就体现不出你技术的全面性了,而且在居中这一方面grid可以做到比flex代码更少,即使你对grid不感兴趣,那么只需记住这一固定用法即可:

父元素 {display: grid;/* 令其子元素居中 */place-items: center;
}

点击这里查看更多实现居中布局的方式

里面的内容解决了,外面的九宫格咱们来用万能的flex去实现,flex默认是一维布局,但如果仅支持一维的话就不会称之为万能的flex了,思路是这样的,假如每一个宫格宽高为100 x 100,九宫格加起来是300 x 300,每三个就让它换行,这样就可以考察到候选人对flex的灵活运用的程度了:

父元素 {width: 300px;/* 设置为flex布局 */display: flex;/* 设置换行 */flex-flow: wrap;
}子元素 {width: 100px;height: 100px;border: 1px solid black;
}

看起来没毛病对不对?实际上确是每行只有两个宫格就会换行,因为加了边框以后子元素的宽高就变成了102 x 102了,三个的话就已经超过了300,所以还没到三个就开始换行了,这时候就考察到候选人的盒模型了:

子元素 {width: 100px;height: 100px;border: 1px solid black;/* 设置盒模型 */box-sizing: border-box;
}

这样即使加了边框,宽高也还是100,刚好能满3个就换行,想象一下如果你是面试官,直接问盒模型是不是显得很low,但是就这一个小小的九宫格立马就能区分出这个候选人的水平如何。

再接下来就是鼠标移入时边框和里面的内容一起变红,这有啥难的,不就是:

:hover {/* 红色字体 */color: red;/* 红色边框 */border: 1px solid red;
}

还是那句话,这样确实能实现,但如果在咱们写js的过程中像red这种多处地方使用的值是不是一般都会给它设置成变量啊?那么这里要写CSS变量?也可以,但有一个更好的变量叫做currentColor,这个属性可以把它理解成一个内置变量,就像js里的innerWidth(window.innerWidth)一样,不用定义自然就是一个变量。

和CSS变量不同的是它取的是自身或父元素上的color值,而且它的兼容性还更好,可以一直兼容到IE9

如果你觉得纳闷:这单词这么长,还不如直接写个red多方便啊,那么请别忘了color是可以继承的!如果在一个外层元素中定义了一个颜色,里面的子元素都可以继承,用JS来控制的话只需要获取外层DOM元素然后修改它的color样式即可。

currentColor作为一个变量,可以用在 border、box-shadow、background、linear-gradient() 等一大堆的 CSS 属性上…甚至连svg中的 fill 和 stroke 都可以使用这个变量,它能做的事情很多,这里为了不跑题就先不展开讲,有兴趣的可以去搜一下。

:hover {/* 红色字体 */color: red;/* 红色边框 */border: 1px solid;
}

修改后的代码如上,为什么没有currentColor?那是因为如果你不写的话,默认就是currentColor,这个关键字代表的就是你当前的color值。

大多数的候选人可能都不会写成这样,如果你作为面试官的话最好是适当的提示一下,看他能不能说出currentColor这个变量或者CSS变量

然后就是点击每个宫格弹出对应的数字,这个考察的是事件冒泡和事件代理:

父元素.addEventListener('click', e => alert(e.target.innerText))

你可以观察一下候选人是把事件绑定在父元素上还是一个个的绑定在子元素上,这个问题按理说基本上都不会错。但如果发现候选人一个个把事件绑定在子元素上了,那就可以到此为止了,也不用浪费时间再去问别的问题了,可以十分装B的来一句:行,你的情况我已基本了解了,回去等通知吧!

接下来我们再来写一下完整一点的代码,以便引出下一个问题:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style>/* 清除默认样式 */* { padding: 0; margin: 0; }/* 全屏显示 */html, body { height: 100% }body {/* 网格布局 */display: grid;/* 子元素居中 */place-items: center;}/* 父元素 */ul {width: 300px;/* 清除默认样式 */list-style: none;/* 设置为flex布局 */display: flex;/* 设置换行 */flex-flow: wrap;}/* 子元素 */li {/* 显示为网格布局 */display: grid;/* 子元素水平垂直居中 */place-items: center;/* 宽高都是100像素 */width: 100px;height: 100px;/* 设置盒模型 */box-sizing: border-box;/* 设置1像素的边框 */border: 1px solid black;/* 负边距 */margin: -1px 0 0 -1px;}/* 第1、4、7个子元素 */li:nth-child(3n+1) {/* 取消左负边距 */margin-left: 0}/* 前三个子元素 */li:nth-child(-n+3) {/* 取消上负边距 */margin-top: 0}/* 当鼠标经过时 */li:hover {/* 红色字体 */color: red;/* 红色边框 */border: 1px solid;}</style>
</head>
<body><ul><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li></ul><script>// 选择ul元素const ul = document.getElementsByTagName('ul')[0]// 监听ul元素的点击事件ul.addEventListener('click', e => alert(e.target.innerText))</script>
</body>
</html>

运行结果:

想知道为什么会这样吗?因为当前这个边框被后面的宫格压住了嘛!那么只需要当鼠标经过时不让后面的压住就好了(调高层级)。

说到调高层级,大家首先想到的可能就是z-index了,这个属性用的最多的地方可能就是绝对定位和固定定位了。但其实很少有人知道,z-index不是只能用在position: xxx的,万能的弹性盒子(display:flex)也是支持z-index的:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style>/* 清除默认样式 */* { padding: 0; margin: 0; }/* 全屏显示 */html, body { height: 100% }body {/* 网格布局 */display: grid;/* 子元素居中 */place-items: center;}/* 父元素 */ul {width: 300px;/* 清除默认样式 */list-style: none;/* 设置为flex布局 */display: flex;/* 设置换行 */flex-flow: wrap;}/* 子元素 */li {/* 显示为网格布局 */display: grid;/* 子元素水平垂直居中 */place-items: center;/* 宽高都是100像素 */width: 100px;height: 100px;/* 设置盒模型 */box-sizing: border-box;/* 设置1像素的边框 */border: 1px solid black;/* 负边距 */margin: -1px 0 0 -1px;}/* 第1、4、7个子元素 */li:nth-child(3n+1) {/* 取消左负边距 */margin-left: 0}/* 前三个子元素 */li:nth-child(-n+3) {/* 取消上负边距 */margin-top: 0}/* 当鼠标经过时 */li:hover {/* 红色字体 */color: red;/* 红色边框 */border: 1px solid;/* 调高层级 */z-index: 1;}</style>
</head>
<body><ul><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li></ul><script>// 选择ul元素const ul = document.getElementsByTagName('ul')[0]// 监听ul元素的点击事件ul.addEventListener('click', e => alert(e.target.innerText))</script>
</body>
</html>

运行结果:

结语

没想到这么一个看似不起眼的九宫格一下子就能考察这么多内容吧!如果面试的时候直接问:

  • 你对 flex 了解的怎么样
  • 当元素的外边距为负值时会有什么样的行为
  • 请实现一下水平垂直居中
  • 了解过 grid 吗
  • 谈一下你对盒模型的理解
  • 说一下事件绑定和事件冒泡
  • CSS3的伪类选择器用的怎么样
  • 当页面元素重叠时如何控制哪个在上哪个在下
  • 在CSS中如何运用变量

直接这么问的话既浪费口舌,又显得很low,而且还不能筛选出真正能够灵活运用技术的候选人。

因为这些问题都不难,一般来说都能答出来,但具体能不能灵活运用就不一定了,而这一道九宫格,就像一面照妖镜一样,瞬间让人原形毕露!

如果你是候选人的话,那么一定要好好练习一下这道题。

如果是面试官的话,那么也推荐你用这道题来考察候选者的技术水平,如果能非常完美的做出来,那么基本上就不用再问其他的CSS题目了,日常开发所用到的样式基本难不倒他/她了,可以直接上JS面试题了。

但如果没做出来也不一定就代表这个人水平不行,可以试着提示一下候选者,然后再问一下其他的CSS题来确定一下此人的水平。

该文章首发于前端学不动公众号

千万别小瞧九宫格 一道题就能让候选人原形毕露!相关推荐

  1. 为什么千万不要小瞧月薪几千的女生

    在平台上看到这样一则话题 为什么千万不要小瞧月薪几千的女生 说两句. 因为你压根就不知道她资产是千万级别的还是上亿级别的. 认识一位妹子,是广州本地的,家里有3栋房子,注意单位是3栋,不是3套. 她家 ...

  2. 怎样取消苹果手机x的静音设置_安卓不仿苹果静音键?千万别小瞧“静音键”, 功能竟如此强悍!...

    苹果手机一直是国产手机的学习和模仿的对象,例如3D touch.智能语音助手.凸起的摄像头等都被安卓手机所沿用. 不过细心的网友有没有发现这样的问题?大部分安卓手机都是电源键加上两个音量键,但就是没有 ...

  3. 苹果录屏精灵_安卓不仿苹果静音键?千万别小瞧“静音键”, 功能竟如此强悍!...

    苹果手机一直是国产手机的学习和模仿的对象,例如3D touch.智能语音助手.凸起的摄像头等都被安卓手机所沿用. 不过细心的网友有没有发现这样的问题?大部分安卓手机都是电源键加上两个音量键,但就是没有 ...

  4. 千万不要小瞧那些不好好写代码的程序员

    点击上方"大鱼机器人",选择"置顶/星标公众号" 福利干货,第一时间送达! 本文转自公众号 |鸡仔说 看完了故事,大家发现没有,其实 Krieger 的运气特别 ...

  5. 你可千万别小瞧了自媒体这个行业,没有文化,适合自媒体创业吗?

    没有文化,适合自媒体创业吗?这需要从三方面拆开来分析:文化.自媒体.创业. 1.文化 如果你只是初中或高中毕业,但是经历的特别多.语言组织能力强.懂的东西多等等. 随意给出一个点,可以很快的整理出一个 ...

  6. 千万别小瞧背调公司!手握美团offer,结果背调红灯,哭了

    热文导读|   点击标题阅读 互联网寒冬下,程序员如何突围提升自己? 重磅!阿里内部偷师Android的开发规范文档 什么?鹅厂又出渣男了,劈腿出轨多个震惊朋友圈 相信很多人都会包装简历,尤其是工作经 ...

  7. h5 bootstrap 小程序模板_一道面试题小程序与H5的区别

    抛砖 此文是一道面试题,又不仅仅是一道面试题 面试题,在各个技术社区里都是一个永不落伍的话题,好像大多数人临面试前都会狂刷面试题,恨不得把所有面试题都看一遍,要说有用没,当然有用,因为大部分面试题确实 ...

  8. 电脑计算机和算盘童话作文,消失的算盘作文

    消失的算盘作文 消失的算盘作文1 在童年时代中,隔壁店铺的老奶奶时常拿着算盘,用一双粗糙的手在一个大算盘上来回跳跃,发出"啪啪"声响,这个声音一直停留在我脑海中.时光流逝,大算盘不 ...

  9. 巴卡斯杯 中国大学生程序设计竞赛 - 女生专场(重现)解题思路

    此文章可以使用目录功能哟↑(点击上方[+]) 经过这么一次女生赛,告诉我们千万不要小瞧女生,不然会死得很惨,orz... 链接→"巴卡斯杯" 中国大学生程序设计竞赛 - 女生专场( ...

最新文章

  1. 2019年社交媒体趋势报告
  2. 第十七届全国大学智能车竞赛赛区划分
  3. (LeetCode 141/142)Linked List Cycle
  4. c语言数字储存于变量,用C语言写中文数字字符串转数值变量
  5. 【踩坑记录】Tensorflow在Windows下使用
  6. 查看SQL执行计划的方法及优劣
  7. python绘图背景透明_如何在 Matplotlib 中更改绘图背景
  8. Tarjan算法——强连通分量
  9. oracle vm 4.3.12,Oracle VM VirtualBox 4.3.12_ZZZZ这个程序肿么卸载啊,卸载不了
  10. 鸟哥的Linux私房菜服务器架设篇 第三版
  11. 腾讯联手联通推出车联网“网卡”,打“内容”+“流量”的组合拳
  12. 小米案例分析PPT模板
  13. 直插电阻的分类和区别
  14. 阿童木机器人完成数千万元B1轮融资,雅瑞资本领投
  15. const char *p与char * const p区别
  16. 董老师又双叒叕送书啦,20本《Python数据分析、挖掘与可视化》
  17. 仙人球模型matlab,3dmax软件如何制作带刺的仙人球模型?
  18. 从苏宁电器到卡巴斯基第25篇:难忘的三年硕士时光 I
  19. 2015年百度之星程序设计大赛 - 资格赛:1002列变位法解密
  20. php 连接已重置,[百思不得其解] Nginx 连接已重置 ERR_CONNECTION_RESET

热门文章

  1. 基于ROS下的安卓手机图像和IMU跑ORB-SLAM3
  2. 腾讯云HiFlow场景连接器
  3. Java编程:颜色色带图片的生成
  4. 【图论训练】天气晴朗的魔法【最小生成树】
  5. 怎么寻找微信撤回的图片
  6. matlab获取图片上的字,基于MATLAB图片中文字提取及识别.pdf
  7. CentOS8-1905 本地dnf源挂载
  8. 在中国要想发财的22条秘诀!
  9. linux 音频文件切割_Linux 对音频万能处理的命令——SOX
  10. spring bean的init、destory的几种方法及生命周期