國際化 - 通用 LTR/RTL 佈局解決方案
ofollow,noindex">原文地址
在英文或者中文的網站,我們習慣的閱讀方式都是從左往右的,所以你在訪問國內外的網站的時候會發現,不管是文字還是佈局,都是從左往右進行排版,而我們也熟悉和適應了這種閱讀習慣,但是在中東地區,有很多國家,諸如像阿拉伯語、希伯來語,他們的閱讀習慣卻是從右到左的,恰好跟我們是相反的,我也查閱了大量阿拉伯語的網站的設計,感興趣也可以點選下面的網站看看:
通過上面的網站,可以很直觀地看出像阿拉伯語,典型 RTL 佈局網站的特點:
- 文字都是右對齊,並且是從右往左閱讀的
- 排版都是從右到左的,在一個產品列表中,右邊第一個商品是第一個
- 箭頭代表的意義剛好相反,比如在輪播圖中,向左箭頭代表下一幀,而向右箭頭則代表檢視上一張圖片
知道了 RTL 佈局的特點,我們在使用場景上需要考慮:
- 如何以較低的成本,可維護,相容地改造線上已有的場景支援 RTL 佈局網站
- 對於未來新的場景,怎麼樣在編碼的環節可以快速支援 LTR、RTL 佈局特點的網站
所以本文探究的是 在假定語言文案,圖片等資訊正確的情況下,如何使用一套程式碼,不僅可以支援像英文,中文等 LTR 佈局的網站,也可以支援像阿拉伯,希伯來語等 RTL 佈局的網站。
"神奇" 的 direction
在做 RTL 佈局的時候,我們自然而然就會想到 direction 這個 CSS 屬性,它與在 html 標籤上直接新增 dir="rtl"
的作用一樣,可以改變我們網站的佈局特點,CSS 手冊中對 direction 屬性是這樣描述的: 該屬性指定了塊的基本書寫方向,以及針對 Unicode 雙向演算法的嵌入和覆蓋方向。
講的很繞口,看的雲裡霧裡的,通俗點講,它改變了部分元素的書寫特點:
direction:rtl
通過下面幾個簡單例子就可以理解:
<style> span { display: inline-block; } </style> <div style="direction: rtl;">1 2 3 4 5 6</div> <div style="text-align:left;direction:rtl;">1 2 3 4 5 6</div> <div style="text-align:right;">1 2 3 4 5 6</div> <div style="direction: rtl;"><span>This is </span><span>my blog</span></div> <div style="direction: rtl;">這是我的部落格。</div> <div style="text-align:right;">這是我的部落格。</div> <div style="direction: rtl;">.</div> <div style="text-align:right;">.</div>
展示效果:

direction 真的是萬能的嗎?
上面介紹了一些 direction 的基本用法,那是不是就可以認為只要使用 direction: rtl
之後網站就可以做到相容阿拉伯語/希伯來語等排版從右往左的網站了呢?答案是否定的。 direction 的功能並沒有你想象中那麼強大。
在 PC 網頁上,頁面佈局是千變萬化的,比如我們常使用的佈局有:flex,內聯,浮動,絕對定位等佈局方式。
我也對一些常用的佈局方式進行測試:
- flex 佈局: https:// jsfiddle.net/0srfqgnp/1 /
- inline-block 佈局: https:// jsfiddle.net/t7kn9dap/
- float 佈局: https:// jsfiddle.net/y0tdv7hn/
- 絕對定位佈局: https:// jsfiddle.net/yopreL9z/
通過上述的測試可以發現 direction 只能改變 display: flex/inline-block 元素的書寫方向,對於 float/絕對定位佈局就無能為力,更別談複雜的頁面佈局,比如 BFC 佈局、雙飛翼、聖盃佈局等等。
另外 direction 無法改變 margin, padding, border 的水平方向,也就是說除非你的元素是居中的,否則當你的元素是不對稱的話,即使你改變了元素的書寫方向和順序,margin-left 還是指向左邊的,它並不會留出右邊的空白。從下面的圖對比就可以看出:在左右間距不對稱的時候,直接使用 direction 會對我們本來設計的佈局產生效果上的偏差。


基於 direction 通用佈局方案設計
在知道了 direction 的特點和不足之後,那麼如何圍繞 direction 打造一套通用的佈局方案呢?
從上面的分析,對於佈局/間距翻轉能力的缺失,我們可以對 CSS 進行後處理來達到我們需要的效果,舉個例子,可以在 Github 上搜rtlcss 這個模組,它的原理就是對 CSS 檔案進行處理,比如將 CSS 屬性中的 left 改為 right,right 改為 left。
通過這種能力,無論是 float/絕對定位佈局,還是 margin/padding 間距,都可以很好地改變書寫方向。舉個簡單例子:
.test { direction: ltr; float: left; position: relative; left: 20px; margin-left: 100px; padding-right: 30px; }
通過 rtlcss 模組處理後的 CSS 將變成:
.test { direction: rtl; float: right; position: relative; right: 20px; margin-right: 100px; padding-left: 30px; }
通過這樣的處理,大部分場景下的佈局都可以都可以得到很好的處理,比如簡單對比像絕對定位這樣的佈局:

經過 rtlcss 處理後的頁面效果:

上面是基於 direction 佈局方案原理,當然它也有一些能力上的不足和值得去思考的地方:
首先這是針對 CSS 的,也就是頁面的初始化展示效果,但是涉及到 JS 就無能為力了 ,比如在輪播圖中,通過 JS 去控制圖片的下一幀,在不同的 LTR、RTL 佈局中就產生額外的相容程式碼。
其次,它無法處理 html 中內嵌在標籤中的樣式 ,比如我們在寫 React 元件中可以能會寫出這樣的程式碼:
function SomeComponent({ isSomething }) { return <div style={{ marginLeft: isSomething ? 20 : 10 }} ></div>; }
像這樣書寫的方式以後就要改成基於 class 切換:
function SomeComponent({ isSomething }) { const cls = classNames({ marginLeft20: isSometing, marginLeft10: !isSometing }) return <div className={cls}></div>; }
這部分內容可以通過規範去避免寫內聯樣式,也可以通過正則去修改替換修改樣式。
第三點需要考慮的是圖示庫 ,上面的問題解決了佈局,文字排版的問題,但是對於圖示來說僅僅只是佈局上的移動,根據 Google 的 Material Design在雙向性一章 的內容可以看出,有些圖示是需要翻轉的,有些圖示不用,再比如左右箭頭,在不同佈局中的意義也是不一樣的,所以針對 RTL 的佈局,我們需要重新設計一套字型庫用於 RTL 佈局,真正給使用諸如像阿拉伯語、希伯來語的使用者帶來本地化的體驗。
第四點是需要有更加細粒度的控制,因為在 RTL 佈局中,不是所有的內容都一定是從右到左進行排版的, 我們需要在整體 RTL 的頁面中忽視掉某些模組,使其仍然是以從左往右順序的能力。
這部分怎麼做呢?可以給不需要翻轉的模組的 CSS 檔案中新增像 /* rtl:ignore */
,然後讓像 rtlcss 在處理的時候可以忽略掉對該模組的處理,從而讓該模組在 RTL 佈局中保持已有的展示效果。
在真正實現的過程中,肯定還會遇到其它更多的問題,比如像:CSS 的命名規則(直接加 -rtl 或其它來保證非覆蓋釋出),還是說如何進行 CDN 部署釋出等等一系列的工程實踐問題,相信在不久的將來,經過實踐上線後會產出基於 direction 通用佈局的最佳工程實踐方案。
"神奇" 的 transform 映象翻轉
上面介紹完基於 direction 的佈局方案,最後通過一套程式碼編譯成一套 html,多套 css,一套 js 檔案,區分國家使用者來進行訪問。那麼有沒有可能通過一套程式碼,生成一套 html,css,js 檔案供使用者去訪問呢?請聽下文分解。
想必前端工程師都使用過 CSS3 的 transform 屬性,通過 transform: scaleX(-1)
可以使頁面沿著中軸進行水平翻轉(關於 transform scaleX/rotateY
水平翻轉用法可以看 CSS垂直翻轉/水平翻轉提高web頁面資源重用性
通過水平翻轉,原本 LTR 的佈局頁面:

經過水平翻轉之後就變成 RTL 佈局頁面:

並且這種方式在佈局上具有良好的相容性,跟 direction 改變方向不同, 你根本無需考慮你的佈局:flex/浮動/絕對定位等等,都可以很好地從 LTR 佈局變成 RTL 佈局。
解決了佈局問題,但是也引入的新的問題,就是文字,圖片等等資訊全部都翻轉了,所以我們在文字部分需要將文字再翻轉回來,比如說在文字的容器上加上 transform: scaleX(-1)
,這樣就可以保持內容的正確書寫順序。
基於這樣的思路,一種通過 transform 映象翻轉來實現 RTL 佈局的方案設計就應運而生。
基於 transform 映象翻轉通用佈局方案設計
通過 transform 的映象翻轉,可以很好地解決了佈局翻轉的問題,基於 transform 設計通用佈局我的思路是這樣的:
首先編寫一個 npm 模組,它是一個 React 元件,使用它的時候需要引入它的 CSS 檔案和 JS 元件。
如果頁面需要支援,在阿拉伯語頁面上新增上全域性翻轉:
// xxxxx/index.css html[lang="ar"] { transform: scaleX(-1); }
接下來只需要考慮頁面上不需要翻轉的內容,比如:文字,部分圖片,一些圖示等等元素。
對於這些元素,可以通過 React 元件進行包裹,用法如下:
import{ NoFlipOver } from 'xxxxx'; function SomeComp({ title, imgUrl }) { const comp1 = <NoFlipOver> { title } </NoFlipOver>; const comp2 = <NoFlipOver> <Icon type="clock" /> </NoFlipOver>; const comp3 = <NoFlipOver> <img src={ imgUrl } /> </NoFlipOver>; const comp4 = <NoFlipOver> <SomethingYouDontKnow /> </NoFlipOver>; }
通過這種輕量級的入侵程式碼,開發者無需關心具體的翻轉邏輯,只需要將頁面中不需要翻轉的內容進行包裹即可。而我們需要做的是如何編寫一個通用的不翻轉 React 元件,舉個例子,如果接受到的內容是一段文字,就可以像這樣進行處理:
// xxxxx/index.js const NoFlipOver = function({ children, ...props }) { if(typeof children === 'string') { return <span { ...props } className="no-flip-over">{ children }</span>; } } // xxxxx/index.css html[lang="ar"] .no-flip-over { transform: scaleX(-1); }
對文字的處理比較簡單,只需要通過 span 標籤進行包裹(保證文字向右對齊,如果原本是左對齊的話)這樣簡單的文書處理元件就完成,當然這裡只是舉一個簡單的例子,在設計通用佈局 React 容器元件的時候肯定需要考慮到各個方面,這裡需要等我具體實踐之後才能產出更多的經驗。
基於這樣的思路,可以很好, 更加細粒度地去控制頁面模組的展示形態,需要翻轉的內容,無需處理,不需要的翻轉的內容,需要用一個 React 容器元件進行包裹,從而達到頁面自適應 LTR/RTL 佈局效果。
前面介紹了基於 direction/transform 映象翻轉來實現通用佈局方案,下面我就對比來談談 transform 映象翻轉方案相對於 direction 方案具有哪些優勢呢?
首先它不只是針對 CSS 展示效果,因為是將整個頁面沿中軸進行翻轉,margin-left 在瀏覽器理解上是屬於向右的,所以 transform 方案是相容 JS 邏輯的,也就是說無需修改 JS 邏輯,而 direction 方案只是針對 CSS,JS 邏輯需要調整相容。
第二點,它可以直接使用一套圖示庫,一套圖片即可,需要翻轉的無需處理,不需翻轉的就使用 NoFlipOver 進行包裹。 比如說像下面這樣一個圖文分離的 banner:

經過映象翻轉之後,就變成了:

如果是通過 direction 方案的話就需要準備兩張圖片了(當然如果是圖文不分離的話也是需要老老實實準備兩套圖片)
第三點,它不需要考慮 CSS 命名,CDN 部署等一系列工程問題 ,因為它是劃分 CSS 作用域的方式,針對 LTR/RTL 佈局進行隔離適配。
第四點,內嵌樣式 transform 方案也可以很好地做到相容,而 direction 方案是針對 CSS 檔案的,如果要針對 html 檔案則需要另外額外的工作。
說了一些 transform 方案對比與 direction 方案的優勢,下面就講講其缺點:
第一, 它需要對我們已有的業務場景進行改造,入侵業務程式碼 ,也就是說,如果你的場景相對比較分散,公用模組複用率較低,那麼在使用 transform 方案的時候就需要對每個場景單獨進行修改適配,當然如果你的場景公用元件多,對公共模組修改可以很好在各個場景中複用,這樣一次性的成本就相對比較容易。
第二點, 對於一些頁面滾動元件需要做額外的相容操作 ,經過我的實踐發現,滾動元件在經過翻轉之後存在著一些問題,初步認為是因為翻轉之後帶來一些高度屬性值的變化,具體原因需要等相容適配時候才清楚。
最後一點, 需要考慮到 direction 和 transform 方案對效能帶來的影響 ,哪一種渲染成本較高,會對複雜頁面造成卡頓等等因素都是需要去考慮的。
總結
本文通過對 direction/transform 屬性使用和剖析,設計了兩款不同思路的 LTR/RTL 通用佈局方案,兩套方案各有千秋,有優勢,也有自身不足的地方。
在動手準備改造之前,最好先跟 UED 確認好 RTL 佈局的設計規範,避免因為主觀認知導致錯誤的視覺偏差,這樣可以給中東地區的使用者提供更加本地化的體驗,這裡也有關於頁面佈局雙向性的設計規範,感興趣的可以看一看: MATERIAL DESIGN - Bidirectionality
最後針對不同的業務場景選擇,運用合適的通用佈局方案,才能有效地降低開發和維護成本。