[译] 垂直排版:重提 writing-mode

垂直排版:重提 writing-mode

大约一年前, 我写了在一次 Web 中文垂直排版的尝试中的一些发现。这是一个简单的 demo,它容许你经过复选框来切换书写模式。javascript

我在不久后遇到了 Yoav Weiss,并聊了一下响应式图片社区小组,由于我提到若是能够经过媒体查询获得 picture 元素的 writing-mode,我就没必要在切换排版的时候经过一些比较 hack 的方式对图像进行转换。他建议我把它写成一个响应式图像用例css

但当我从新打开这个一年没打开的 demo 的时候,个人表情在最初的五分钟由 😱 变成了 😩(我还能说什么呢,我就是这么表情丰富 🤷)。因此为了宣泄,我将一步步写下谁(也就是各类浏览器)破坏了什么以及目前可能的解决办法。html

帖子很长,可使用连接来跳转。前端

大脑转储结构

最初的发现

我只在看我能当即访问的浏览器,由于个人人生还有不少别的事要作 🙆。java

Chrome (64.0.3278.0 dev)

vertical-rl on Chrome

好的,这看起来很是棒。我说全部东西都被破坏了其实有点夸张。全部的文字和图片都占满,在垂直书写模式下没有重大的渲染问题。作的好,Chrome。android

horizontal-tb on Chrome

切换排版模式将东西都踢去了右边。我记得在垂直排版下将东西水平居中是一件让人特别痛苦的事情,因此在第一次不太顺利的尝试中我确定用了某些 hack 手段。ios

这在 2017 年初是绝对可行的,由于我为个人 Webconf.Asia 幻灯片作了这个截屏。我很肯定当时用的是 Chrome。几个月时间一个 demo 的变化让人惊讶。个人老大提到过一个词叫「代码腐烂」,也许这就是吧。css3

Firefox (59.0a1 Nightly)

vertical-rl on Firefox

天哪,这,我都无语了。Firefox Nightly 是个人默认浏览器,因此个人最初反应是一切都被破坏了。一切确实都被破坏了,看看这无限滚动的水平滚动条,到底发生了什么?!git

horizontal-tb on Firefox

让咱们切换……等等,个人复选框呢?唉,这可能要等一会。无论怎么说,至少我将复选框绑在了 label 上,因此我仍然能够经过点击 label 来切换排版。因此,这绝对不是居中,但也没有太崩。两个浏览器的表现形式天差地别。github

Safari Technology Preview 44

vertical-rl on Safari TP

嘿,嘿,嘿!这看起来使人惊讶的好。甚至连高度都是正确的。Safari,我可能误判你了。Safari 的渲染引擎究竟是什么?好吧,WebKit。

horizontal-tb on Safari TP

噢噢噢,这有点居中。不看代码,我也能肯定我尝试过一些很奇怪的转译来改变整个内容块,所以在每一个浏览器中行为不一致。但这是个使人欣慰的惊喜。

Edge 16.17046

这是 Windows 10 内置快速通道版本,因此我想个人 Edge 浏览器应该比大多数人的版本更高。不要紧,我也能够用个人手机(没错,我用的是 Windows phone,不服来战)。

vertical-rl on Edge 16

不管如何,这看起来也不算太坏。只是那个复选框有点错位。更重要的是滚轮正常工做!其余全部的浏览器都不容许我用滚轮水平滚动。虽然我不知道这是 Windows 的功劳仍是 Edge。

horizontal-tb on Edge 16

也是隐约的居中。我真的须要立刻检查下个人转换代码。如今我可能对个人复选框究竟怎么了也产生了疑问。啊,使用滚轮没法垂直滚动,这就有意思了。另外,注意滚动条在左边 🤔。

Edge 15.15254

Edge 15 上的 vertical-rl

Edge 15 上的 horizontal-tb

跟 Edge 16 几乎如出一辙。我有理由相信 Windows phone 上的 Edge 浏览器用的是与桌面版本一样的渲染引擎 EdgeHTML,若是有错还望指正。

iOS 11 WebKit

iOS 11 WebKit 上的 vertical-rl

iOS 11 WebKit 上的 horizontal-tb

尽管个人 iPad 上装了一大堆浏览器,但我知道它们的渲染引擎都是 WebKit,由于苹果从未容许过第三方的浏览器引擎。正如在桌面版展现的那样,这是表现比较好的浏览器。

代码时间

好了,既然咱们已经肯定了破坏的基准,如今是时候把防尘罩拆下来,看看底下到底有什么怪异的代码。公平地说,没有太多,考虑到这是一个很是简单的演示,因此还不错。

同时我还要强烈安利(无数次)Browsersync,那是我最重要的开发工具,尤为是须要在不一样设备的不一样浏览器上调试的时候。若是我没有 Browsersync,我将不会为此作这么多工做。

一些背景

切换器的实现能够用两种形式,一是经过 Javascript 切换类,二是 hack 复选框。我一般倾向于只使用 CSS 的解决方案,因此决定 hack 复选框。这个 demo 足够简单,因此不会有太多键盘控制方面的干扰。个人意思是,你能够像其它任何的复选框同样用 tab 切换到它而后切换。 我真的须要研究可访问性的问题以肯定我是否会在屏幕阅读器上搞砸它,但那是另外一回事了。今天优先处理布局问题。

若是你没有尝试过 hack 复选框,它涉及到 :checked 伪选择器的使用和兄弟或子选择器,你能够经过这种方式用 CSS hack 复选框的状态。

须要注意的是,切换 :checked 状态的 input(一般是复选框元素),必须处于与你想切换状态的目标元素相同或更高的层级。

<body>
  <input type="checkbox" name="mode" class="c-switcher__checkbox" id="switcher" checked>
  <label for="switcher" class="c-switcher__label">竪排</label>

  <main>
    <!-- 内容样式 -->
  </main>

  <script src="scripts.js"></script>
</body>
复制代码

问题就在复杂度上。在同一个页面上混合使用不一样的嵌套的书写模式确实会搞垮浏览器。我不是浏览器工程师,但我有足够的常识知道渲染东西不是微不足道的。可是我是一个执着的人,因此必受其苦。

通常的复选框 hack 策略

原始的 demo上,我在 body 元素上设置默认的书写模式为 vertical-rl,而后使用复选框来切换 main 元素里的书写模式。可是看起来彷佛每一个人(浏览器渲染引擎)都向上面的截图目录同样,以不一样的方式处理嵌套的书写模式。

调试 101: 重置为基准

记住,这是一个大脑转储条目,若是你以为无聊,我对此表示抱歉。我作的第一件事就是删除全部样式,从新开始。再次重申,这个 demo 有效是由于它十分简单。上下文才是一切,朋友们。

html {
  box-sizing: border-box;
  height: 100%;
}

*,
*::before,
*::after {
  box-sizing: inherit;
}

body {
  margin: 0;
  padding: 0;
  font-family: "Microsoft JhengHei", "微軟正黑體", "Heiti TC", "黑體-繁", sans-serif;
  text-align: justify;
}
复制代码

这几乎成了我全部项目的事实起点。将全部元素设置成 border-box,并且一般我还会加上 margin: 0padding: 0 做为样式重置的基础。可是就这个 demo 而言,我将让浏览器保留它的空白只重置 body 元素。

这个 demo 几乎全是中文,因此我只添加了中文字体,把系统自带的 sans-serif 做为后备。不过大多数状况来讲,优先选择基于拉丁语的字体是个广泛的共识。但在这里,中文字体支持基本的拉丁字符,而反过来状况就不同了。

当浏览器遇到中文字符时,它不会在基于拉丁语的字体中寻找,因此它会选用下一种备选字体,直到找到合适的。若是你先将中文字体列出来,浏览器将使用中文字体中的拉丁语字符,有时候这些字形没被打磨,看起来也不太好,尤为是在 Windows 上。

接下来是一些不太影响布局的美化(line-height 算吗?🤔)

img {
  max-height: 100%;
  max-width: 100%;
}

p {
  line-height: 2;
}

figure {
  margin: 0;
}

figcaption {
  font-family: "MingLiU", "微軟新細明體", "Apple LiSung", serif;
  line-height: 1.5;
}
复制代码

这一个合理、体面的基准。如今咱们能够调查 writing-mode 的行为了。

vertical-rl 的含义

每个元素的 writing-mode 的默认值都是 horizontal-tb,并且它是一个继承属性。若是你设置了一个元素的 writing-mode,这个值将传递到它全部的子元素。

若是咱们将 main 元素的 writing-mode 设置为 vertical-rl ,在每一个浏览器上,全部的文字和图像都被正确渲染了。Firefox 有 15px 轻微的垂直溢出,我怀疑是由于滚动条,不过我不能肯定。其它的浏览器一点水平溢出都没有。

vertical-rl on the main element

main 元素是垂直书写模式的同时,document 自己是水平书写模式,就会产生问题,意味着内容从左边开始,并且咱们最终会看到第一次加载的文章的末尾。

因此,让咱们把东西提高一个层级,在 body 上设置 writing-mode: vertical-rl。Chrome,Safari 和 Edge 如咱们所想从右到左渲染内容。可是 Firefox 仍然显示文章的末尾,尽管这确实修复了滚动条溢出的问题,它看起来和 Bug 1102175有关。

vertical-rl on the body element

最后,若是咱们将 html 设置 writing-mode: vertical-rl,Firefox 终于正常并从右到左显示了,并且没有搞笑的溢出。And lastly, if we apply writing-mode: vertical-rl to the html element, Firefox finally comes around and reads from right-to-left. Also, no funny overflowing, just vertical right-to-left goodness.

vertical-rl on the html element

IE11 支持书写模式属性,只不过使用较早的规范中定义的旧语法 -ms-writing-mode: tb-rl。这工做正常,但我因为如今使用的 main 标签 IE11 并不支持,切换器失效了。甚至将 main 标签设置成 display: block 都没法修复。我能够为了更好的兼容性将 main 替换成 div。让我考虑一下。

布局切换

因为 Firefox 有已知的垂直书写的弹性盒模型的问题,因此我将把调试任务分红两个部分,一是纯粹的布局。找出使切换器正常工做的不一样方法,并且没有任何奇怪的溢出。

第二个部分将与图像居中有关,这让我陷入混乱。除了居中,我还想调整图像的方向,它是让我首先重温 RICG 用例汇总的缘由。#不起眼的注脚

解决方案 #1: Javascript

让咱们先来尝试回避的解决方案,既然问题出在混用书写模式,也许咱们能够中止混用。基于咱们上面的观察,用一个 Javascript 事件监听器去切换 html 元素的 CSS 类能够隐性修复许多奇怪的渲染问题。好了,代码时间到。

我想切换的两个类的类名简单地叫作 verticalhorizontal。既然我已经有了复选框,也许也能够用做类的切换器。

document.addEventListener('DOMContentLoaded', function() {
  const switcher = document.getElementById('switcher')

  switcher.onchange = changeEventHandler
}, false)

function changeEventHandler(event) {
  const isChecked = document.getElementById('switcher').checked
  const container = document.documentElement

  if (isChecked) {
    container.className = 'vertical'
  } else {
    container.className = 'horizontal'
  }
}
复制代码

将内容块居中完成得很好。由于再也没有嵌套的书写模式或者弹性盒模型。直接的自动 margin 在全部浏览器中都完美实现了居中,甚至 Firefox。

.vertical {
  writing-mode: vertical-rl;

  main {
    max-height: 35em;
    margin-top: auto;
    margin-bottom: auto;
  }
}

.horizontal {
  writing-mode: horizontal-tb;

  main {
    max-width: 40em;
    margin-left: auto;
    margin-right: auto;
  }
}
复制代码

Auto margins for vertical centring

有趣的是,在垂直书写模式,咱们能够用 margin-top: automargin-bottom: auto 来垂直居中。但相信我,水平居中将比你想象的更使人痛苦。在下一个 hack 复选框的部分你将看到。

意外的 TIL: Microsoft Edge 遵照 ECMAScript5「严格模式下不容许分配只读属性」的规范,可是 Chrome 和 Firefox 在严格怪异模式下仍然容许,极可能是为了代码兼容。我最初尝试使用 classList 来切换类名,但它是一个只读属性,而 className 则不是。相关阅读在下面的连接

解决方案 2: 复选框 hack

这个方案的原理相似使用 Javascript,区别在于咱们不使用 CSS 类来改变状态,而是使用 :checked 伪元素。如咱们前面所讨论的,复选框元素必须和 main 元素在同一层级才会生效。

.c-switcher__checkbox:checked ~ main {
  max-height: 35em;
  margin-top: auto;
  margin-bottom: auto;
}

.c-switcher__checkbox:not(:checked) ~ main {
  writing-mode: horizontal-tb;
  max-width: 40em; 
  margin-left: auto; // 无效
  margin-right: auto; // 无效
}
复制代码

布局代码与 .vertical.horizontal 同样,但,结果却不同。垂直居中是好的,看起来好像是咱们在用 Javascript。可是水平居中歪向了右边。自动 margin 在这一部分彷佛彻底没有发挥做用。 但仔细一想,这实际上是「正确」的行为,由于咱们一样不能用这种方式在水平书写模式下实现垂直居中。为何呢?让咱们来看一下规范。

全部的 CSS 属性都有值,一旦你的浏览器解析了一个文档并构建了 DOM 树,每一个元素的每一个属性都须要赋值。Lin Clark 写了一个精彩的代码漫画来解释 CSS 引擎如何工做,你不能错过它!话说回来,值,规范里说:

一个属性的最终值是四步计算的结果:首先经过规范肯定值(「指定值」),而后解析为一个用于继承的值(「计算值」),而后若是有必要,转换成绝对值(「使用值」),最后依据具体场景限制再作转换(「实际值」)。

与此同时,依据规范,高度和 margin 的计算由各种盒模型的许多规则决定的。若是上下的值同时为 auto,它们的使用值将被解析成 0

Margins resolving to zero

当咱们将书写模式设置成垂直,「height」彷佛在计算的时候会变成水平坐标。我说彷佛是由于我并不百分百肯定它真的是这样计算的。它让我以为 Javascript 解决方案很神奇。

开个玩笑,实际上由于咱们在 Javascript 解决方案中没有混用书写模式,因此将各自的值解析为 0 并不影响咱们想要的居中效果。可能你须要重读这一句话几回 🤷。

想要在切换到垂直书写模式的时候将 main 元素水平居中,咱们须要使用好的变换技巧。

.c-switcher__checkbox:not(:checked) ~ main {
  position: absolute;
  top: 0;
  right: 50%;
  transform: translateX(50%);
}
复制代码

这在 Chrome,Firefox 和 Safari 上可行。不幸的是,Edge 上有点毛病,东西都歪向页面中间的某个地方以及左边。是时候记录下这个 Edge 的 bug。另外,滚动条出如今了左侧而不是右侧。

Seems to be buggy on Edge

处理图像对齐

好了,继续。当在垂直书写模式时,我但愿有两张图片的 figure 元素堆叠显示,而在水平书写模式中,若是空间容许,则并排显示。理想状况下,figure 元素(图像和标题)将在各自的书写模式下居中。

经典的属性

既然咱们正在一个干净的页面工做,让咱们试试最基础的居中技术:text-align。默认状况下,图像和文本是内联元素。给 figure 元素设置 text-align: center,天呐,成功了 😱!

水平和垂直书写模式下的图像都已经成功地居中了。我如今很是怀疑一年前我作这个的时候的智商。显然,为了个人目的和意图,弹性盒模型是没必要要的。我首先尝试了新的技术,但它让我付出了代价。

真是醉了 🥃。

在水平书写模式中,不须要添加太多东西。只是一个简单的 margin-bottom: 1em,给 figure 之间留空间。因为空间关系,我确实须要将竖直的图像旋转,在这里我使用 transform 的 rotate 来完成。

.vertical {
  figure {
    margin-bottom: 1em;
  }

  figcaption {
    max-width: 30em;
    margin: 0 auto;
    display: inline-block;
    text-align: justify;
  }

  .img-rotate {
    transform: rotate(-90deg);
  }
}
复制代码

问题是,当你旋转了一个元素,浏览器仍然会记住它原来的宽高(我想),因此在个人 demo 中,当视窗变得很是窄的时候,它将触发水平溢出。可能有办法修复这个问题,但我没有找到。欢迎指教。

这就是我将为 RICG 编写的用例。想法是,若是能够经过媒体查询获得书写模式,我就可使用 srcset 定义一个垂直的图像和一个水平的图像,分别为对应的书写模式提供图片。

在垂直书写模式中,咱们一般但愿文字整齐,或者至少在短行上对齐半孤立的字符。而后文字间的空隙,margin 应该设置为 left 而不是 bottom。

.vertical {
  figure {
    margin-left: 1em;
  }

  figcaption {
    max-height: 30em;
    margin: auto 0.5em;
    display: inline-block;
    text-align: justify;
  }
}
复制代码

如今咱们几乎能够称之为圆满的一天。最终结果已经实现了目标。我想补充说的是,除了我以前提到的 Edge 缺陷以外,不管 Javascript 方案仍是复选框 hack 方案都是彻底相同的。

使用弹性盒模型居中

我怀疑我选择弹性盒模型实现居中的理由,尽管老实说我想不起来到底为何我以为这是一个好主意。显然,我不须要弹性盒模型的任何特色。那我应该也作个大脑转储?

但看了一眼个人源码,我才发现我给包裹图像的应该堆叠的 div 设置了 display: flex,这让图像成为了弹性容器的子元素,致使 Firefox 的垂直书写模式渲染混乱。

Flexbox issue with vertical writing-mode on Firefox

使用这种方法,东西看上去都很美好,并且我测试过的 Chrome,Edge 以及 Safari 的全部版本(前面提到的列表)均可行,所以图像在垂直和水平两种模式下都居中对齐。但 Firefox 不行,真的,切换到垂直书写模式时,图片在个人页面上不可见,虽然在水平模式下很好。

Flexbox issue with vertical writing-mode on Firefox

我已经用 display: flexdiv 包裹了应该堆叠显示的图像,但不知为什么在 Firefox 的垂直模式下搞砸了。我怀疑这个行为和这些 bug 有关:Bug 1189131Bug 1223180, Bug 1332555Bug 1318825Bug 1382867

与此同时,我对 Firefox 下,在垂直书写模式中做为弹性容器子元素的图像的效果产生了好奇。好像浏览器直接对你说不 ♀️ 🙅 💩。

Flexbox issue with vertical writing-mode on Firefox

抛开垂直书写模式,我和 Jen Simmons 交流过不一样浏览器的 flexbox 实现,她发如今全部的浏览器中,缩小图像的处理都是不一样的。这个问题仍在 CSS 工做组中讨论,敬请期待更新。

这个缩小的问题与固有尺寸的概念有关,尤为是含有固有长宽比例的图像。CSS 工做组对此有过至关长的讨论,由于这不是一个小问题。

Firefox 上一个有趣的观察是,弹性容器的宽被视窗的宽度限制,但目前没有在别的浏览器上发现这个问题。当容器内全部的图片的宽度之和超过了视窗宽度,在 Firefox 上,图像会缩小以适应宽度,但在别的全部的浏览器上,它们只会溢出而后你会获得一个水平滚动条 🤔。

为了暂时避免这个问题,我要确保个人图像都不是弹性容器的子元素。全部的图像,不管是单仍是双,都被包裹在额外的 div中。figure 元素设置了 display: flex 属性,让 figcaption 和包裹图像的 div 成为弹性容器的子元素而不是图像自己。

.vertical {
  writing-mode: vertical-rl;

  main {
    max-height: 35em;
    margin-top: auto;
    margin-bottom: auto;
  }

  figure {
    flex-direction: column;
    align-items: center;
    margin-left: 1em;
  }

  figcaption {
    max-height: 30em;
    margin-left: 0.5em;
  }

  .img-single {
    max-height: 20em;
  }
}

.horizontal {
  writing-mode: horizontal-tb;

  main {
    max-width: 40em;
    margin-left: auto;
    margin-right: auto;
  }

  figure {
    flex-wrap: wrap;
    justify-content: center;
    margin-bottom: 1em;
  }

  figcaption {
    max-width: 30em;
    margin-bottom: 0.5em;
  }

  .img-wrapper img {
    vertical-align: middle;
  }

  .img-single {
    max-width: 20em;
  }

  .img-rotate {
    transform: rotate(-90deg);
  }
}

复制代码

复选框 hack 的实现彻底同样。我从中学习到的是,浏览器对于元素的区域计算须要下很大功夫,尤为是具备固有尺寸比例的。

Grid 怎么样?

咱们已经在布局所需上走了很远,因此我考虑尝试使用 Grid 来实现图像对齐。咱们能够尝试让每一个 figure 都成为一个 grid 容器,或许能够用上 grid-areafit-content 这些有趣的属性让东西对齐。

不幸的是,十分钟的尝试以后,我脑壳炸了。Firefox 的 grid 调试器并不能匹配我页面上的元素,但也有多是由于页面上太多东西了。

Grid inspector tool issue in vertical writing-mode

我须要为使用 grid 的垂直书写模式建立一个简化的测试用例,那将是一个简单得多的 demo,我还会单独写一篇文章(可能还有相关的错误报告)。

成功的解决方案?

当前完成的个人独立 demo 使用的是不用弹性盒模型的复选框 hack 解决方案。我将保留复选框 hack 的版本以追踪 Edge 的 bug。但弹性盒模型解决方案,若是你不介意多余的包裹,也是能够的。用于 Javascript 实现的标记也看起来更好,由于你将切换器包裹在一个 div 中而后写样式。

在最后,有不少方法能够实现一样的结果。从别的地方拷贝代码也能够,可是出现莫名其妙的问题就麻烦了。你没必要从头开始编写全部东西,但要确保里面没有没法破译的「魔法」。

说说而已 😎。

延伸阅读

问题和错误列表


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索