前端开发者学堂 - fedev.cn

图解CSS:CSS逻辑属性

发布于 大漠

自Web诞生以来,我们一直习惯于物理CSS属性,比如我们都知道使用margin-topmargin-rightmargin-bottommargin-bottom来设置元素的外边距,但随着书写模式特性的出现,这些物理特性,比如上、右、下和左的概念已经失去了它们的意义。特别是随着越来越多的Web开发人员要处理国际化业务,那么以前的物理特性已经无法满足业务的需求。换句话说,为了具有不同编写模式的多种语言设计页面时,开发人员必须跨多个元素分别调整这些物理属性,这也成了Web开发者的噩梦。幸运的是,CSS的逻辑属性的出现,可以让开发者根据书写模式来维护布局的完整性。即,根据内容的语义顺序进行动态更新。今天这篇文章,我们将和大家一起来探讨CSS的逻辑属性。

什么是逻辑属性

2017年5月18日,W3C的 CSS工作组(CSS Working Group) 发布了 CSS逻辑属性和值(CSS Logical Properties and Values Level 1) 的首份工作草案(First Public Working Draft)。不同的书写模式(writing mode)中,可以抽取出共性的抽象概念(如开始位置,或行),这些逻辑抽象概念需要在不同书写模式下映射到左或右、上或下等物理的概念上。一些CSS布局可能依赖这些共性的逻辑概念。该 CSS 模块给出了用于通过逻辑方式(而不是基于物理坐标、书写方向和维映射等)控制布局的逻辑属性和取值(logical properties and values)。这个模块来源于CSS21中关于逻辑属性和值的特性。

在过去(CSS逻辑属性还未到来之前),在CSS中来描述元素盒模型、位置等特性都是采用的物理属性(比如元素的尺寸,方向等),比如我们熟悉的元素位置会映射到toprightbottomleft。在盒模型中都以物理属性为中心,不管是marginpaddingborder还是positionfloat等。

除了toprightbottomleft之外的widthheight都是物理属性之一,比如我们描述一个盒子的大小,就是用widthheight为描述的。但是这些属性和书写模式有着很大的关系,因为书写模式可以直接改变其方向。假设你的网站上有一些从右到左(RTL)的内容,那么左边可能是物理上的右边,因此你要是设置了margin-left: 100px,你可能希望将其替换为margin-right: 100px。但是,如果同时使用从左到右(LTR)和从右到左(RTL)混合,则需要根据不同的CSS属性来设置左或右。如果你考虑垂直写作模式,也会遇到类似的问题,内容可能是物理上的顶部(top)或底部(bottom)。

但在逻辑属性中却不一样,在逻辑属性中没有方向性的概念,只有开始(start)和结束(end)、块(block)和内联(inline)的概念。比如说,在从左到右(LTR)中,startleft,但在从右到左(RTL),它是right。也就是说,逻辑属性更易于适应不同的书写模式。

书写模式

虽然说,在Web排版的时候,我们习惯了从左到右(LTR)的排版,但当你的业务要面对多语言的场景时,你会发现除了熟悉的从左到右的排版之外,你还需要处理从右到左(RTL),从上到下等排版。

在Web开发中,HTML的dir属性取值为ltr可以实现从左到右排版,rtl可以实现从右到左排版。在CSS中的direction属性和dir属性类似,可以定义内联内容在屏幕上的流动方式(排版方式),即ltr是从左到右排版,rtl是从右到左排版。除此之外,CSS中的writing-mode属性除了可以义定内联内容在屏幕上的排版方式之外,还可以定义块内容在屏幕上的排版方式。该属性可以取值:

  • horizontal-tb:定义了内容从左到右水平流动,从上到下垂直流动。下一条水平线位于上一条线下方
  • vertical-rl:定义了内容从上到下垂直流动,从右到左水平流动。下一条垂直线位于上一行的左侧
  • vertical-lr:定义了内容从上到下垂直流动,从左到右水平流动。下一条垂直线位于上一行的右侧
  • sideways-rl:定义了内容从上到下垂直流动,所有字形,甚至是垂直脚本中的字形,都设置在右侧
  • sideways-lr:内容从上到下垂直流动,所有字形,甚至是垂直脚本中的字形,都设置在左侧

就拿上面示例上的圆角来说,如果采用以前我们熟悉的物理属性来描述border-radius的值,那么在不同书写模式下,它的值将要根据不同的模式进行调整:

.card__heading {
    border-radius: 6px 6px 0 0;
}

[data-lang="Japanese"] .card__heading{
    border-radius: 0 6px 6px 0;
}

[data-lang="Mongolian"] .card__heading{
    border-radius: 6px 0 0 6px
}

这就是物理属性在多语言布局中带来的局限性,幸运的是,逻辑属性将彻底解决这一切烦恼。

逻辑属性与物理属性及逻辑值与物理值

CSS中布满了物理位置的关键词,比如我们熟悉的toprightbottomleft。在使用positionstatic值对元素盒子定位时,就要使用这些物理位置来描述元素盒子的偏移量。

另一个用到物理关键词的地方是使用text-align控制文本对齐方式,比如取值为right时文本会右对齐,这也是CSS中的物理属性。当我们为项目增加外边距(margin)、内边距(padding)和边框(border)时会使用像margin-leftpadding-left这样的物理属性。

把这些关键词称为物理属性,是因为它们与你看到屏幕紧密相关,左永远是左,不管文本流动的方向如何。

在开发有多种语言的网站时,如果其中包含了从右侧而不是从左侧开始书写的文字,物理属性就会成为一个问题。浏览器很擅长处理文本方向,不需要真的在一种 rtl (从右到左)的语言下开发,我们也可以一窥究竟。下面的例子里有两个段落,一个段落没有设置 text-align 属性,另一个段落的 text-align 设置为 left。在 html 元素上添加 dir="rtl" 声明,就会把书写模式从默认的 ltr (从左到右)的英语切换为 rtl (从右到左)的语言。我们可以看到,第一段仍然是从左到右显示,因为 text-align 的值为 left,但第二段把文字的流动方向切换成了从右到左。

这只是在使用物理属性和值时引起问题的一个非常简单的例子,它们阻止浏览器切换书写模式,因为这些物理属性和值已经假设文字的流动方向一定是从左到右、从上到下的。

逻辑属性和值不会预设文字方向,这也是为什么在网格布局中要实现对齐到容器的开始位置时使用 start 关键字的原因。对我来说,因为我使用英语工作,所以 start 就是左侧,不过它并不总是代表左侧,并不能根据 start 这个词推断出物理位置。

为什么需要逻辑属性

传统CSS根据屏幕的物理尺寸来定义元素的尺寸、位置,这些值都是物理尺寸。因此,我们使用CSS将元素(盒子)描述为具有宽度(width)和高度(height),用toprightbottomleft来描述元素的位置。而CSS逻辑属性和值定义了这些物理值到它们的逻辑或流相关的映射,例如用开始和结速来描述左和右,顶部和底部。

为什么需要这些映射呢?我们来看一个简单地示例:

<h2>City Lights in New York</h2>
<h2>أضواء المدينة في نيويورك</h2>

如果仅仅按物理属来给标题定义一个左边框以及内容距左边框内距,往往会这样来描述:

h2 {
    border-left: 6px double currentColor;
    padding-left: 5vh;
}

对阅读模式来说,上面的样式适合于英文排版,但对于阿拉伯语来说,上面的样式设置就不太适合,因为阿拉伯语阅读方式是从右向左,也就是左边框变成了右边框,左内距变成了右内距。在逻辑属性还未出现之前,往往我们需要针对阿拉伯语单独做样式处理:

h2:nth-child(2) {
    border-right: 6px double currentColor;
    border-left: none;
    padding-right: 5vh;
    padding-left: 0;
}

如果我们用逻辑属性来描述的话,事情就会变得容易地多:

h2 {
    border-inline-start: 6px double currentColor;
    padding-inline-start: 5vh;
}

可能上面的示例你可能会感到陌生,那我们来看另一个示例。

在现代Web的布局中,我想你应该已经使用过了Flexbox布局,或者说已经接触过了Grid的布局。就比如说一个设置了宽度(width)的网格容器(元素上显式设置displaygridinline-grid),在让网格项目对齐时会用到align-selfjustify-self属性,而且它们的值会是startend。这些属性会根据流方式产生不同的效果,比如justify-self: start会让网格项目在内联维度的开始位置,而align-self: start会让网格项目在块维度的开始位置。

如果我们修改writing-mode的值,你可以看到效果如下:

如果将物理属性width换成inline-size,把height换成block-size,再改变书写模式时,比如writing-mode的值为vertical-rl(或vertical-lr)时,inline-size(内联维度)会按垂直方向运行,而block-size(块维度)会按水平方向运行。这个时候justify-self取值start(或end)时,位置也会根据维度相应变化:

具体的效果,你可以尝试调整下拉选择框的值:

逻辑属性中的重要概念

从上面的内容中我们可以了解到,CSS的书写模式对逻辑属性最终呈现给用户的效果有着决定必的作用。除此之外,还要深入的了解几个和逻辑属性有关系的概念,只有了解了这几个概念,才能更好的运用好CSS的逻辑属性。

流在CSS中是一个非常重要的概念,我们在《视觉格式化模型》和《聊聊CSS中的层叠相关概念》都有聊过流。回到Web中来,在Web中所说的流主要指的是文档流和文本流。比如说,在HTML中,我们的元素可以分为块元素(如div),行内元素(如span)和可替换元素(如img)。如果未使用CSS做任何样式上的处理,那么块元素会按从上往下排列:

在CSS中,我们可以通过display属性block元素和inline元素之间转换:

对于文本同样有流的概念,比如说英文,一般是从左到右,阿拉伯文是从右到左,而日文(古代的中文)从上到下,从右到左:

不管是文档流还是文本流,它们都具有相应的物理特性,比如从左到右,从右到左,从上到下,从下到上。即,它们都没有离开toprightbottomleft方向。

随着Flexbox的到来,具体的方向性没有那么重要了,比如在Flexbox中,不再关注方向,而是更关注主轴和侧轴:

特别是进入到Grid的时代,方向性更不重要了。因为在Grid的时代变成了:

你也可以发现了,在Flexbox和Grid中,不再有具体的方向性的概念,有的只是开始(start)和结束(end)。如果你用过了Flexbox布局和Grid布局,我想你也就用过了前面提到的justify-selfalign-self等属性,他们的的值不再有leftright,而是startend

另外,这些都会随着书写模式(writing-mode)会有所变动,比如:

换句话说,在CSS中可以使用流相对值来替代相应的物理值

块(block)和内联(inline)维度

处理流相关属性和值的一相关键概念是**块(block内联(inline)**两个维度。正如上面所看到的,像Flexbox和Grid布局方法在对齐项目时使用了块和内联维度的概念,而不是toprightbottomleft

内联(inline

内联维度是在使用的书写模式中运行的文本行(文本流)所在的维度。即,对应于文本流(阅读方式)的轴线。例如,英文是从左到右的文本流(或阿拉伯文从右到左),因此内联轴是水平的;对于日文,它的阅读方式是自上而下,因此内联轴是垂直的

块(block

块维度是另一个维度,以及块(如段落)相继显示的方向。在英语和阿拉伯语中,这些是垂直的,而在任何垂直书写模式中,这些是水平的。

也就是说,如果用逻辑属性而不是物理属性来思考,就不能使用从左到右,从上到下的方式观察世界,我们需要一个新的参考点,也就这里所说的内联轴和块轴。理解它们是非常重要的,除了能帮助我们更好的理解CSS逻辑属性之外,对于理解CSSS中Flexbox,Grid布局中的术语以及对齐方式也特别的有用。

我们可以换过一种方式来理解:

  • 块轴:主要定义网站文档(元素块)流,CSS的书写模式writing-mode会影响块轴的方向
  • 内联轴:主要定义网站的文本流方向,也就是文本的阅读方式,CSS的direction或HTML的dir会影响内联轴的方向

开始(start)和结束(end

在前面的内容中,我们多次提到了开始和结束。一但你知道了文本的方向,就能很好的理解开始和结束。

开始(start

这对应于文本的方向,并反映了文本的侧边,你将从哪里开始阅读。对于英文,开始对应于左。对于阿位伯文来说,对应于右。

结束(end

这也对应于文本的方向,并反映了文本的侧边,你将在哪里结束阅读。对于英文,结束对应于右。对于阿拉伯文来说,对应于左。

逻辑维度 vs. 物理维度

将前面的内联轴、块轴、开始和结束结合起来可以构建CSS逻辑属性中的流相对值。即 block-startblock-endinline-startinline-end。这几个属性也被称为逻辑维度,其实就是用来指定在对应轴上的开始和结束位置。对应的就是我们熟悉的toprightbottomleft几个物理方向。

换句话说,在CSS逻辑中,使用流相对值来代替相应的物理值。正如前面所述,流相对值(逻辑维度)和CSS的书写模式writing-mode或阅读方式direction有关。

接下来,我们通过几种典型的语言为例,来向大家阐述逻辑维度和物理维度的映射关系。

首先来看英文,英文的阅读方式一般是从左往右(即dirction: ltrwriting-mode:horizontal-tb),这种模式常称为 LTR(Left-To-Right)。它的内联轴是水平的,块轴是垂直的,相应的逻辑维度和物理维度映射关系如下:

逻辑维度 物理维度
inline-start left
inline-end right
block-start top
block-end bottom

接着来看阿拉伯文,它的阅读方式是从右往左(即direction: rtlwriting-mode:horizontal-tb),这种模式常称为 RTL(Right-To-Left)。它的内联轴是水平的,块轴是垂直的,相应的逻辑维度和物理维度映射关系如下:

逻辑维度 物理维度
inline-start right
inline-end left
block-start top
block-end bottom

再来看日文,竖排(有点类似中国古代的汉字书写模式),对应的writing-mode: vertical-rl。它的内联轴是垂直的,块轴是水平的,相应的逻辑维度和物理维度映射关系如下:

逻辑维度 物理维度
inline-start top
inline-end bottom
block-start right
block-end left

最后再来看蒙文,也是竖排,和日文不同的是writing-mode: vertical-lr。它的内联轴是垂直的,块轴是水平的,相应的逻辑维度和物理维度映射关系如下:

逻辑维度 物理维度
inline-start top
inline-end bottom
block-start left
block-end right

逻辑属性带来的变化

了解了上面介绍的这些概念,就能更好的帮助我们理解CSS逻辑属性。

CSS的逻辑属性通常不使用位置命名。正如《Web中向左向右》一文中所提到的,当文本以另一种语言呈现时,位置可能变得毫无意义。例如,使用padding-left来设置文本距容器左侧边缘距离(增加空白空间)比较适合于英文文本,但却不适合阿拉伯文(看上去怪怪的)。这主要是因为阿拉伯文的阅读方式是从右往左(LTR),也就是说,换到阿拉伯文就需要设置padding-right来增加内距,才更符合视觉上的效果。如果我们将padding-left这样的物理属必换成padding-inline-start,我们就无需担心文本是英文还是阿拉伯文了。

正如上面示例所言,我们以前熟悉的CSS盒模型相关属性,比如marginpaddingborderwidthheight;元素定位属性,比如toprightbottomleft;浮动方向、文本对齐方向等都可以映射到对应的CSS逻辑属性上。

更详细的映射表如下图所示:

接下来,我们就来看看CSS逻辑属性的基本使用和给我们带来的变化。

特别声明:CSS的逻辑属性和directionwriting-modetext-orientation以及HTML的dir属性有着直接关系,因为这几个属性取值不同会改变轴的方向(内联轴和块轴);从而会影响startend

流盒模型属性

这里所说的流盒模型属性是相对于物理盒模型属性来说的。比如下图所示:

物理盒模型中的属性都有匹配逻辑属性映射关系。而且也可以按组划分,比如padding-*margin-*border-*等。而且这些映射关系(或者说流盒模型属性)并不是固定不变的,它们的结果和书写模式writing-modedirectiontext-orientation取值有关。

另外,流相关属性的指定值与对应物理属性的指定值不同,但流相关属性和物理属性共享计算值。换句话说,如果同一个元素同时声明了流相关的属性和物理相关的属性,最终计算值将会由CSS级联规则来决定。我们来看一个简单的示例:

.box {
    border-inline-start: 5px solid #f36;
    border-left: 5px solid #f90;
    border-inline-end: 5px solid #90f;
}

尝试着调整writing-modedir的值,其结果如下:

writing-mode:horizontal-tbdirection:ltr.box的左边框颜色为#f90,右边框颜色为#90f。在这种模式下,border-inline-startborder-left共享一个计算值,而且border-left的声明在border-inline-start之后,因此最终运用于.box的是border-left的值(即5px solid #f90):

writing-mode:horizontal-tbdirection:rtl.box的左边框颜色为#90f,右边框的颜色为#f36。在这种模式下,border-leftborder-inline-end共享一个计算值,而且border-inline-end的声明在border-left之后,因此最终运用于.box的是border-inline-end的值(即5px solid #90f):

writing-mode: vertical-lr(或vertical-rl)和direction: ltr,不会像前面两个场景,共享同一个值,这个时候呈现给大家的效果如下:

writing-mode: vertical-lr(或vertical-rl)和direction: rtl,也不会共享同一个值,这个时候呈现给大家的效果如下:

接下来,我们来按照盒模型的属性划分来看看CSS逻辑属性给盒模型带来的变化

逻辑尺寸

从《元素尺寸的设置》中可以得知,物理属性widthheightmin-widthmin-heightmax-widthmax-height可以用来设置元素盒子尺寸大小。它们在CSS逻辑属性中都有对应的映射关系。比如:

  • widthheight映射的是block-sizeinline-size
  • min-widthmin-height映射的是min-block-sizemin-inline-size
  • max-widthmax-height映射的是max-block-sizemax-inline-size

但具体的对应关系取决于writing-mode属性的值。我们来看一个简单的示例,比如:

.box {
    inline-size: 50vh;
    block-size: 30vh;
}

同样拿不同的语言为例,比如英文(writing-mode: horizontal-tbdirection: ltr)、阿拉伯文(writing-mode: horizontal-tbdirection: rtl)、日文(writing-mode: vertical-rl)和蒙文(writing-mode: vertical-lr)。具体的效果如下:

min-widthmin-heightmax-widthmax-height在不同writing-modedirection模式下表现行为和widthheight相似

物理属性 逻辑属性(horizontal-tb 逻辑属性(vertical-lr 逻辑属性(vertical-rl
width inline-size block-size block-size
height block-size inline-size inline-size
min-width min-inline-size min-block-size min-block-size
min-height min-block-size min-inline-size min-inline-size
max-width max-inline-size max-block-size max-block-size
max-height max-block-size max-inline-size max-inline-size

在CSS中,除了上面提到的属性可以显式设置元素盒子大小之外,还可以使用resize属性来调整元素盒子大小。其物理值中的horizontalvertical也有相应的逻辑关键字值。

  • 使用resize:inline允许在内联维度调整元素大小
  • 使用resize:block允许在块维度调整元素大小

逻辑边框

CSS盒模型中的边框(border)属性在逻辑属性中也有相应的映射属性。这些逻辑边框属性和writing-modedirectiontext-orientation有关。

.box {
    border-inline-start: 6px solid #9c27b0;
    border-inline-end: 8px solid #ff9800;
    border-block-start: 10px solid #2196f3;
    border-block-end: 12px solid #8bc34a;
}

同样的,在不同的语言中来向大家演示:

物理边框属性和逻辑边框属性在writing-modedirection下对应的映射关系如下所示:

CSS的物理属性border可以分拆为border-top-widthborder-right-widthborder-bottom-widthborder-top-styleborder-right-styleborder-bottom-styleborder-left-styleborder-top-colorborder-right-colorborder-left-color等。同样的,它们分别有对应的逻辑属性border-block-start-widthborder-block-end-widthborder-inline-start-widthborder-inline-end-widthborder-block-start-styleborder-block-end-styleborder-inline-start-styleborder-inline-end-stylebordere-block-start-colorborder-block-end-colorborder-inline-start-colorborder-inline-end-color

其中border-widthborder-styleborder-color对应的逻辑属性有border-block-widthborder-inline-widthborder-block-styleborder-inline-styleborder-block-colorborder-inline-color

熟悉CSS的同学都知道,CSS的border属性是border-topborder-rightborder-bottomborder-left的简写,CSS逻辑属性是border-block-startblock-block-endblock-inline-startblock-inline-end可以简写为border-blockborder-inline。其中border-blockborder-inline对应的是:

border-block: border-block-start border-block-end
border-inline: border-inline-start border-inline-end

逻辑内距

CSS的逻辑内距和逻辑边框有点类似,和物理padding能找到相应映射的属性。同样的逻辑内距和writing-modedirectiontext-orientation有关。比如:

.box {
    padding-inline-start: 10px;
    padding-inline-end: 20px;
    padding-block-start: 30px;
    padding-block-end: 40px;
}

同样在不同的语言的渲染效果如下:

物理内距和逻辑内距对应的映射关系如下:

逻辑内距也可以简写:

padding-block: padding-block-start padding-block-end
padding-inline: padding-inline-start padding-inline-end

逻辑外距

逻辑外距和逻辑内距类似,它和物理外距对应的映射关系如下:

逻辑圆角

在物理属性中,border-radius可以用来设置元素盒子的圆角,其拆分出来由border-top-left-radiusborder-top-right-radiusborder-bottom-left-radiusborder-bottom-right-radius四个属性。而CSS的逻辑圆角属性由border-start-start-radiusborder-start-end-radiusborder-end-start-radiusborder-end-end-radius组成。同样的,CSS逻辑圆角属性也受writing-modedirectiontext-orientation属性的影响。

比如下面这个示例:

请用Firefox浏览器查看Demo

物理圆角和逻辑圆角映射关系如下:

逻辑偏移

CSS的position取值为非static时,可以通过toprightbottomleft让定位元素偏移,在CSS的逻辑属性中同样也有相应的映射属性:inset-block-startinset-block-endinset-inline-startinset-inline-end。它们还可以简写为inset-blockinset-inlineinset

同样的,逻辑偏移也受writing-modedirectiontext-orientation属性的影响。比如:

.item:nth-child(1) {
    inset-inline-start: 20px;
    inset-block-start: 30px;
}

.item:nth-child(2) {
    inset-inline-end: 40px;
    inset-block-end: 60px;
}

物理属性和逻辑属性对应的关系如下:

小结

如果在构建Web页面或应用时,面对的仅是单一语言,那么使用CSS物理属性并无大碍,而且也不会影响整个Web应用的布局。而且使用CSS逻辑属性来替代物理属性也可以实现同等效果。如果你构建的Web应用是要处理多语言,那么物理属性带来的局限性就非常的明显,而且也会造成阅读上的不便,这个时候CSS逻辑属性就能起到作用就非常的大。

CSS逻辑属性和物理属性都有着相应的映射关系,比如下图所示:

CSS逻辑属性和物理属性之间最大的差异是,CSS逻辑属性受着writing-modedirectiontext-orientation属性的影响,并且用startend来替代原有的物理方向。

对于初学者而言,使用CSS逻辑属性会有一定能困惑,因为它并不是固定的,会随着书写模式,阅读方式等变化,这也会造成一定的使用成本和理解成本。但使用多了,就会熟悉这方面的属性。如果你在这方面有更多的经验和建议,欢迎在下面的评论中与我们一起共享。