[譯] 垂直排版:重提 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前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索