- 原文地址:Implementing a Mockup: CSS Layout Step by Step
- 原文做者:Dave Ceddia
- 译文出自:掘金翻译计划
- 本文永久连接:github.com/xitu/gold-m…
- 译者:Baddyo
- 校对者:cyz980908,Moonliujk
对不少人来讲,建立布局是前端开发领域中最难啃的骨头之一。php
你确定经历过耗费数个小时,换着花样地尝试全部可能起做用的 CSS 属性、一遍遍地从 Stack Overflow 上复制粘贴代码,寄但愿于误打误撞地赌中那个能实现预期效果的魔幻组合。css
若是你的惯用策略就是循序渐进地组合布局 —— 先把 A 元素放在这儿,好了,A 元素就位了,我再看怎么把 B 放在那儿 …… 那你没有挫败感才怪呢。CSS 的玩法可与 SKetch 或者 Photoshop 的玩法不同。html
在本文中,我将向你展现如何以统筹全局的思惟实现 CSS 布局,根治布局难产的顽疾。前端
咱们将用一个小案例贯穿全文,我会把全部的 CSS 代码都解释给你听,所以即便你不知道或者忘记了 position
和 display
的用法,即便你分不清 align-items
和 justify-content
的区别,你仍会有所斩获。react
并且咱们会用纯 HTML 和 CSS 代码来演示,所以你不须要 React、Vue、Angular、CSS-in-JS 甚至是 JavaScript 方面的知识储备。android
听起来很棒吧?那就开始吧。ios
在本文中,咱们要比照 Twitter 的推文组件本身仿写一个:git
不管是一个像这样的草图,仍是一个细节精美的原型图,“有章可循” 老是个好主意。github
要避免一边在脑海里设计,一边在浏览器中七拼八凑地攒布局,这样的开发过程才会更顺畅。你固然能够达到那种手脑合一的境界!但鉴于你还在乖乖地读这篇文章,我能够假设你尚未那么神通广大。:)后端
在动手敲代码以前,咱们先把布局的各个单元区分开来:
在用 CSS 铺排布局时,用行和列的形式去构思大有裨益。所以,要么你把元素从上到下排列,要么从左到右排列。这种行和列的思路完美对应了 CSS 中两种布局技术:Flexbox 和 Grid。
固然了,咱们的示例布局并非中规中矩的行列。它有一张图片镶嵌在左侧,其余元素排列在右侧。
画一些方框把这些元素框起来,看看行和列是否初具规模。咱们把方向一致的单元归到同一个方框中。
在页面中的 HTML 元素基本上均可视为矩形。固然,有些元素有圆角,有些元素是圆形,或者是复杂的 SVG 形状等。一般你看不到页面上有一堆矩形。但你能够用矩形边框的模式去分析它们。这样的想象能帮你理解布局。
之因此提到矩形,是由于你要把一系列元素对齐 —— 如第一行的用户名、@handle(译者注:handle 属于专有名词,指 Twitter 中的用户 ID,因此在本文中保留不译。详见 www.urbandictionary.com/define.php?…)和时间以及最后一行的图标 —— 把它们用方框包起来便于规划。
按目前的规划,把布局用 HTML 代码实现出来大概以下所示:
<article>
<img src="http://www.gravatar.com/avatar" alt="Name" />
<div>
<span>@handle</span>
<span>Name</span>
<span>3h ago</span>
</div>
<p>
Some insightful message.
</p>
<ul>
<li><button>Reply</button></li>
<li><button>Retweet</button></li>
<li><button>Like</button></li>
<li><button>...</button></li>
</ul>
</article>
复制代码
展现出的效果是这样的(能够点击这里调试代码):
这离咱们想要的效果还远呢。可是!全部所需的内容都齐全了。有些元素还以从左到右的顺序排列。
咱们能够认为,即便不用进一步设置样式,目前的布局效果也能达到网页想表达的要点,这也是一个优秀的 HTML 应该达到检查标准。
你可能会好奇,为什么我选的是那些元素 —— article
、p
等等。为什么不都用 div
呢?
为什么要这样写:
<article>
<img ... />
<div>
<span/>
<span/>
<span/>
</div>
<p> ... </p>
<ul>
<li>
<button> ... </button>
</li>
</ul>
</article>
复制代码
而不这样写?
<div>
<img ... />
<div>
<div/>
<div/>
<div/>
</div>
<div> ... </div>
<div>
<button> ... </button>
</div>
</div>
复制代码
其实,每一个 HTML 元素的名称都有其特定含义,在不一样场景中恰如其分地使用语义上与它们所表示的内容匹配的元素,是很好的语义化实践。
这种写法,首先,有助于开发者理解代码;其次,对使用屏幕阅读器等辅助设备的用户比较友好。同时这样用标签也有利于 SEO —— 搜索引擎会试着理解这个页面的含义,以便于显示相关广告来盈利、帮助搜索者找到满意结果。
article
标签表明文章类内容,而你能够认为推文这种东西有点相似于一篇文章。
p
标签表明段落,而推文的内容文本有点相似于一个段落。
ul
标签表明无序列表(与有序列表或数字序号列表相对应),在本示例中,你能够用它来存放列表信息。
咱们没法用只言片语就说清楚 HTML 元素的语义,以及何种状况用何种标签。但大多数状况下,一个语义化元素即便其语义再不贴切,也比用 div
强,div
标签只表明 “一块区域”。
是什么决定了元素的样式?为何有的元素独占一行,而有的元素能共处一行?
这要归因于元素的默认样式,这其中就有咱们要探讨的第一个 CSS 知识点:行内元素和块级元素。
行内元素们肩并肩挤在一行里(就像句子中的词同样,必要时会折行)。根据再浏览器中的默认样式划分,span
、button
以及 img
都是行内元素。
而块级元素,老是踽踽独行。以控制台输出的方式去理解,你能够认为块级元素先后各有一个换行符 \n
。就好像console.log("\ndiv\n")
。article
、div
、li
、ul
以及 p
标签都是块级元素。
注意,在上面的例子中,为何即便 img
标签是行内元素,头像图片依然独占一行?由于它下方的 div
是块级元素。
而后要注意,为何 @handle、用户名和时间都在同一行?缘由是它们都在 span
标签中,而 span
是行内元素。
这三个 span
和 文字 “insightful message” 处于不一样行,由于(a)它们被包在一个 div
中,div
后面天然要另起一行;(b)p
标签一样是块级元素,它天然重新行开始排列。(之全部没有出现两个空行,是由于 HTML 合并了相邻的空行,与相邻空格同理。)
若是你再看得仔细点,你会发现 “insightful message” 的上下方空间,要比头像图片以及 handle、用户名、时间的上下方空间要大。此空间的大小也由默认样式控制:p
标签的顶部和底部都有 margin。
你也会注意到按钮列表的圆点,以及列表的缩进行为。这些也都是默认样式。咱们立刻就要修改这些默认样式了。
咱们想把头像图片放在左侧,其他元素放在右侧。你可能会根据刚刚探讨的行内和块级知识来推断,认为只要把右侧的元素都包裹到一个如 span
标签般的行内元素中,就完事大吉了。
但这是行不通的。行内元素并不能阻止其内部的块级元素另起一行。
为了把这些元素收拾得服服帖帖,咱们须要用一些更强大的技术,好比 Flexbox 或者 Grid 布局。此次咱们选用 Flexbox 来解决。
CSS 的 Flex 布局可以把元素以行或者列的形式排布。这是一种单向的布局系统。为了实现交叉的行和列(正如推文组件的设计那样),咱们须要添加一些容器元素来扭转方向。
你能够在容器上设置 display: flex;
来启用 Flex 布局。容器自己是块级元素(得以独占一行),其内部元素会成为 “Flex 子项” —— 即它们再也不是行内或块级元素了;它们都受 Flex 容器控制。
在本例中,咱们会设置一些嵌套的 Flex 容器,让该成行的成行,该成列的成列。
咱们把外层容器(绿色方框)设置为列,蓝色方框设置为行,而红色方框中的元素排布在列中。
因为一些缘由,我决定用 Flexbox 布局而不用 Grid 布局。我以为 Flexbox 布局更易于学习,也更适用于轻量级的布局。当布局中主要是行或者主要是列时,Flexbox 布局的表现更出色。
另外一个重点就是,即便 Grid 布局比 Flexbox 布局年轻,前者也撼动不了后者的地位。它们各自适用于不一样的场景,对于两者,咱们都要学习,技不压身。有些状况你甚至会同时使用两者 —— 例如 Grid 布局排布总体页面,而 Flexbox 布局调控页面中的一个表单。
没错没错,在 Web 开发的世界,广泛的更替法则是后浪推前浪,但 CSS 并不如此。Flexbox 和 Grid 可以和谐共存。
用 CSS 解决问题,条条大路通罗马!
好了,既然咱们已经打定主意,那就开动吧。我把左侧元素包进一个 div
,并给元素们设置类名,便于应用 CSS 选择器。
<article class="tweet">
<img class="avatar" src="http://www.gravatar.com/avatar" alt="Name" />
<div class="content">
<div class="author-meta">
<span class="handle">@handle</span>
<span class="name">Name</span>
<span class="time">3h ago</span>
</div>
<p>
Some insightful message.
</p>
<ul class="actions">
<li><button>Reply</button></li>
<li><button>Retweet</button></li>
<li><button>Like</button></li>
<li><button>...</button></li>
</ul>
</div>
</article>
复制代码
(代码在这里)
看着好像没有变化。
这是由于 div
做为块级元素(若是没有空行就引入一个)是看不见的。当你须要一个包裹其余元素的容器,除了 div
以外没有更贴合语义的选择了。
下面我们的第一段 CSS 代码,咱们会把它放在 HTML 文档中 head
标签的 style
里:
.tweet {
display: flex;
}
复制代码
干得漂亮!咱们用类选择器锁定了全部类名为 tweet
的元素。固然目前只有一个这样的元素,但若是有十个,那它们将都会是 Flex 容器了。
CSS 中以 .
开头的选择器表明类选择器。为何是 .
?我可不知道。你只要记住这条规则就好了。
如今文字内容都到头像右侧去了。问题是头像图片都扭曲变形了。
由于 Flex 容器会默认:
咱们能够用 align-items
属性来控制垂直方向的对齐方式。
.tweet {
display: flex;
align-items: flex-start;
}
复制代码
align-items
的默认值是 stretch
,而将其设为 flex-start
后,会让子项沿着容器顶部对齐,而且让子项保持各自的高度。
另外,Flex 容器的默认排列方向是 flex-direction: row;
。是的,这个方向是 “行”,即便咱们可能感受那更像是两列。要把它想成是子项们排成一行,这样理解就舒服多了。
有点像这张花瓶的图片,或者说两张脸的图片。横当作岭侧成峰。
Flex 布局的子项仅取其所需宽度,但咱们须要 content
区域尽可能宽敞一些。
所以,咱们要给 content
这个 div 设置 flex: 1;
属性。(该 div 有类名,那咱们就又能够用类选择器啦!)
.content {
flex: 1;
}
复制代码
咱们也要给头像设置 margin
,好在头像和文字之间加点空隙:
.avatar {
margin-right: 10px;
}
复制代码
看起来顺眼一些了吧!
那…… 为何用 margin
而不用 padding
?为何要设置在头像右侧,而不是文字内容左侧呢?
这是一条约定俗成的规则:在元素右侧和下方设置 margin,不去碰左侧和上方的 margin。
至少是在英文界面的布局中,文档流的方向是从左到右、从上到下的,所以,每一个元素都 “依赖” 其左侧和上方的元素。
在 CSS 中,每一个元素的定位都受到其左侧和上方的元素的影响。(至少在你碰见 position: absolute
那帮家伙以前是这样的。)
从技术实现的角度来讲,怎样设置 avatar
和 content
之间的空隙都同样。该是多宽就是多宽,没有 border
的干扰(padding
在 border
的内侧;而 margin
在外侧)。
但当事关可维护性、对元素的全局观时,这就有区别了。
我曾尝试把元素理解为一个个独立个体,就像每一个 JavaScript 函数只实现单一功能同样:若是它们都仅仅扮演单一的角色,那么写起代码来就很容易,报错时调试也很容易。
若是咱们把 margin 设置到 content
的左侧,后来有一天咱们去掉了 avatar
,但是之前的缝隙还留在那。咱们还得排查致使额外空间的缘由(是来自 tweet
容器吗? 仍是来自 content
呢?)并把它处理掉。
或者,若是 content
设置了左侧的 margin,而咱们想要把 content
替换成别的元素,咱们还要记着再把以前那个空隙补上。
好了好了,为了 10 像素的事,不必费这么多口舌,干脆就把 margin 设在头像的右侧和下方。让咱们继续埋头敲代码吧。
无序列表 ul
和其中的列表项 li
在左侧窝藏了很大空间,还有一些圆点。这都不是咱们想要的效果。
咱们能够把无序列表左侧的空隙都清除掉。咱们还要把它变成一个 Flex 容器,这样里面的按钮就能排成一行了(用 flex-direction: row
)。
列表项有个属性是 list-style-type
,默认值为 disc
,使得每一个列表项以圆点开头,咱们用 list-style: none;
(list-style
是一个缩写属性,整合了几个其余属性,其中就包括 list-style-type
)将该效果关闭。
.actions {
display: flex;
padding: 0;
}
.actions li {
list-style: none;
}
复制代码
.actions
又是一个类选择器。原汁原味。
而 .actions li
选择器,意即 “actions
类元素中全部的 li
元素”。它是类选择器和元素选择器的结合。
复合选择器中用以分隔的空格表明着选择范围的缩小。事实上,CSS 是以倒序读取选择器的。其过程是 “先找到页面中全部的 li
,而后在这些 li
中找到类名是 actions
的那些”。但不管你用正序仍是倒序的方式去理解,结果都是同样的。(在 StackOverflow 查看更多详解)
要横排按钮有好几种方式。
一种就是设置 Flex 子项的对齐方式。你应该对设置对齐方式很熟悉,每一个富文本编辑器顶部都有这种功能的按钮:
它们把文本进行左对齐、居中对齐、右对齐以及 “两端对齐”,也就是铺满整行。
在 Flexbox 布局中,你能够用 justify-content
属性来实现对齐。设置了 flex-direction: row
(默认值,也是本文中一直在用的设置)后,能够经过 justify-content
把子项进行或左或右地对齐。justify-content
的默认值为 flex-start
(所以全部元素都向左看齐)。若是咱们给 .actions
元素设置 justify-content: space-between
,它们就会均匀地铺满整行,就像这样:
可咱们想要的不是这样的效果。若是这几个按钮能够不占满整行会更好。因此得换一种方式。
此次,咱们给每一个列表项设置一个右侧的 margin,把它们分隔开来。还要给整个推文组件设置一个边框,以便咱们可以直观地衡量效果。用 1px solid #ccc
设置一个 1 像素宽的灰色实线边框。
.tweet {
display: flex;
align-items: flex-start;
border: 1px solid #ccc;
}
.actions li {
list-style: none;
margin-right: 30px;
}
复制代码
如今效果以下:
按钮的排列看起来优雅多了,但灰色边框告诉咱们,全部元素都过于靠左了。仍是用 padding
分配点空间吧。
.tweet {
display: flex;
align-items: flex-start;
border: 1px solid #ccc;
padding: 10px;
}
复制代码
如今推文组件有内边距了,但有些地方仍是很空。若是咱们用浏览器调试工具将元素高亮显示,就会发现 p
和 ul
元素有默认的上下 margin(在 Chrome 的调试工具中,margin 以橙色显示,而 padding 以绿色显示):
还有一处有意思的细节;行与行之间的上下 margin 是等距的 —— 并无叠加出双倍间距!由于 CSS 在竖直方向上有 margin 坍塌现象。当上下两个 margin 短兵相接时,数值大的 margin 会 “吃掉” 小的。详情参见 CSS 技巧:margin 坍塌。
对于本例的布局,我会手动调整 .author-meta
、p
和 ul
的右侧 margin。若是要真刀真枪地开发网站,建议你考虑用 CSS reset 做为开发基础,有利于跨浏览器兼容。
p, ul {
margin: 0;
}
.author-meta, p {
margin-bottom: 1em;
}
复制代码
用 ,
将选择器隔开,能够一次性把样式应用到多个选择器上。所以 p , ul
的含义就是 “全部的 p
元素,以及全部的 ul
元素”。亦即两者的合集。
在这里咱们使用了新的尺寸单位,1em
中的 em
。一个单位的 em
等于 body
标签上的以像素为单位的字号大小。body
标签的默认字号为 16px
(16 像素高),因此本例中的 1em
至关于 16px
。em
随字号改变而改变,所以能够用 1em
来表达 “我想让文字下方的 margin 和文字的高度同样,不论文字高度是多少”。
如今的效果以下:
如今让咱们把图片缩小一些,并将其设置为圆形。咱们将其宽高设置为 48 像素,正和 Twitter 的头像宽高同样。
.avatar {
margin-right: 10px;
width: 48px;
border-radius: 50%;
}
复制代码
咱们用 border-radius
属性来设置圆角,有好几种方式来定义该属性的值。若是你想要小圆角效果,能够用带 px
、em
或其余单位名称的数字赋值。例如 border-radius: 5px
的效果:
若是将 border-radius
设为宽和高的一半(在本例中即为 24 像素),其效果就是一个圆形。但更方便的写法是 border-radius:50%
,这样咱们就没必要知道具体尺寸,CSS 会计算出确切结果。甚至,若是之后宽高值变了,也无需从新修改属性值了!
眼下还有一些须要润色之处。
咱们要把字体设为 Helvetica(Twitter 用的那一款)、把字号缩小一些、把用户名加粗,还有,翻转 “@handle 用户名 的顺序(在 HTML 代码中),使之与 Twitter 如出一辙。:D
.tweet {
display: flex;
align-items: flex-start;
border: 1px solid #ccc;
padding: 10px;
/* 更改字体和字号。 在 .tweet 选择器上设置的 CSS 效果,其全部子元素都会继承。 (除了按钮。按钮不太合群) */
font-family: Helvetica, Arial, sans-serif;
font-size: 14px;
}
.name {
font-weight: 600;
}
.handle,
.time {
color: #657786;
}
复制代码
font-weight: 600;
的效果等同于 font-weight: bold;
。字体有不少不一样程度的字重,范围是从 100 到 900(最淡到最浓)。normal
(默认值)等价于 400。
另外,CSS 中的注释写法与 JavaScript 或其余语言不用,不容许以 //
开头。某些浏览器支持 //
风格的 CSS 注释,但并不是全部浏览器都如此。用 C 语言风格的 /* */
包围注释内容便可高枕无忧。
还有一个小窍门:能够用 伪元素在 “handle” 与 “时间” 之间添加一个凸点。这个凸点符号单纯为了装饰,不具备具体语义,因此用 CSS 实现不会污染 HTML 语义结构。
.handle::after {
content: " \00b7";
}
复制代码
::after
建立了一个伪元素,它位于 .handle
元素内部的最后方(“落后” 于元素的内容)。你还能够用 ::before
建立伪元素。能够给 content
属性赋值任何文字内容,包括 Unicode 字符。你能够恣意发挥,像给任何其余元素设置样式同样。伪元素用来实现标记(badge)、消息提醒或其余小花样最合适不过了。
还有一项工做要作,那就是用图标替换按钮。咱们要在 head
标签里添加 Font Awesome 图标字体:
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous" />
复制代码
而后用下列代码替换原来的 ul
,新列表中的每一个按钮里有图标和隐藏文字:
<ul class="actions">
<li>
<button>
<i class="fas fa-reply" aria-hidden="true" ></i>
<span class="sr-only">Reply</span>
</button>
</li>
<li>
<button>
<i class="fas fa-retweet" aria-hidden="true" ></i>
<span class="sr-only">Retweet</span>
</button>
</li>
<li>
<button>
<i class="fas fa-heart" aria-hidden="true" ></i>
<span class="sr-only">Like</span>
</button>
</li>
<li>
<button>
<span aria-hidden="true">...</span>
<span class="sr-only">More Actions</span>
</button>
</li>
</ul>
复制代码
Font Awesome 是一款图标字体,它配合斜体标签 i
能够展现图标。正由于它是字体,那些能够用于文字的 CSS 属性(例如 color
和 font-size
)都适用于图标字体。
咱们在这儿作了些微调,来提高按钮的可访问性:
aria-hidden="true"
使屏幕阅读器忽略此图标。sr-only
类是 Font Awesome 内置的类。它让元素在你眼前隐身,但屏幕阅读器能读取到它。这里有一门由 Marcy Sutton 讲授的关于图标按钮可访问性的免费 Egghead 课程。
如今咱们将要给按钮添加一些样式 —— 移除边框、上色以及加大字号。还要设置 cursor: pointer
,把鼠标光标变成 “手” 型,就像超连接的效果那样。最后,用 .actions button:hover
选择处于 hover 状态的按钮,把它们变成蓝色。
.actions button {
border: none;
color: #657786;
font-size: 16px;
cursor: pointer;
}
.actions button:hover {
color: #1da1f2;
}
复制代码
下面就是推文组件光芒四射的最终效果:
若是你想本身调试代码,到沙箱里来。
最能提升 CSS 水平的就是实践。
仿写你喜欢的网站。设计者和艺术家称其为 “临摹”。我写过一篇用临摹的方法学 React,其中的原则也适用于 CSS。
选一些有意思的、你以为难度大的样式效果。用 HTML 和 CSS 临摹该效果。若是卡壳了,用浏览器的调试工具看看原网站的效果是如何实现的。“栽秧苗、腿跟上、抬头看看直不直。” :)
若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。