New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

国际化 - 通用 LTR/RTL 布局解决方案 #16

Open
happylindz opened this Issue Oct 28, 2018 · 0 comments

Comments

Projects
None yet
1 participant
@happylindz
Copy link
Owner

happylindz commented Oct 28, 2018

原文地址

在英文或者中文的网站,我们习惯的阅读方式都是从左往右的,所以你在访问国内外的网站的时候会发现,不管是文字还是布局,都是从左往右进行排版,而我们也熟悉和适应了这种阅读习惯,但是在中东地区,有很多国家,诸如像阿拉伯语、希伯来语,他们的阅读习惯却是从右到左的,恰好跟我们是相反的,我也查阅了大量阿拉伯语的网站的设计,感兴趣也可以点击下面的网站看看:

通过上面的网站,可以很直观地看出像阿拉伯语,典型 RTL 布局网站的特点:

  1. 文字都是右对齐,并且是从右往左阅读的
  2. 排版都是从右到左的,在一个产品列表中,右边第一个商品是第一个
  3. 箭头代表的意义刚好相反,比如在轮播图中,向左箭头代表下一帧,而向右箭头则代表查看上一张图片

知道了 RTL 布局的特点,我们在使用场景上需要考虑:

  1. 如何以较低的成本,可维护,兼容地改造线上已有的场景支持 RTL 布局网站
  2. 对于未来新的场景,怎么样在编码的环节可以快速支持 LTR、RTL 布局特点的网站

所以本文探究的是在假定语言文案,图片等信息正确的情况下,如何使用一套代码,不仅可以支持像英文,中文等 LTR 布局的网站,也可以支持像阿拉伯,希伯来语等 RTL 布局的网站。

"神奇" 的 direction

在做 RTL 布局的时候,我们自然而然就会想到 direction 这个 CSS 属性,它与在 html 标签上直接添加 dir="rtl" 的作用一样,可以改变我们网站的布局特点,CSS 手册中对 direction 属性是这样描述的:该属性指定了块的基本书写方向,以及针对 Unicode 双向算法的嵌入和覆盖方向。

讲的很绕口,看的云里雾里的,通俗点讲,它改变了部分元素的书写特点:

  1. 定义过 direction:rtl 的元素,如果没有预先定义过 text-align,那么这个元素的 text-align 的值就变成了 right,如果设置了 left/center 则无效
  2. 对于数字和标点符号以外的编码,顺序仍然是从左到右的
  3. 改变了 inline-block 元素的书写顺序

通过下面几个简单例子就可以理解:

<style>
    span {
        display: inline-block;
    }
</style>
<div style="direction: rtl;">1 2 3 4 5 6</div>
<div style="text-align:left;">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,内联,浮动,绝对定位等布局方式。

我也对一些常用的布局方式进行测试:

  1. flex 布局:https://jsfiddle.net/0srfqgnp/1/
  2. inline-block 布局:https://jsfiddle.net/t7kn9dap/
  3. float 布局:https://jsfiddle.net/y0tdv7hn/
  4. 绝对定位布局:https://jsfiddle.net/yopreL9z/

通过上述的测试可以发现 direction 只能改变 display: flex/inline-block 元素的书写方向,对于 float/绝对定位布局就无能为力,更别谈复杂的页面布局,比如 BFC 布局、双飞翼、圣杯布局等等。

另外 direction 无法改变 margin, padding, border 的水平方向,也就是说除非你的元素是居中的,否则当你的元素是不对称的话,即使你改变了元素的书写方向和顺序,margin-left 还是指向左边的,它并不会留出右边的空白。从下面的图对比就可以看出:在左右间距不对称的时候,直接使用 direction 会对我们本来设计的布局产生效果上的偏差。

LTR 布局效果
RTL 布局效果

基于 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;
}

通过这样的处理,大部分场景下的布局都可以都可以得到很好的处理,比如简单对比像绝对定位这样的布局:

LTR 绝对定位布局

经过 rtlcss 处理后的页面效果:

RTL 绝对定位布局

上面是基于 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 的布局页面:

LTR 布局页面

经过水平翻转之后就变成 RTL 布局页面:

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:

LTR 布局

经过镜像翻转之后,就变成了:

RTL 布局

如果是通过 direction 方案的话就需要准备两张图片了(当然如果是图文不分离的话也是需要老老实实准备两套图片)

第三点,它不需要考虑 CSS 命名,CDN 部署等一系列工程问题,因为它是划分 CSS 作用域的方式,针对 LTR/RTL 布局进行隔离适配。

第四点,内嵌样式 transform 方案也可以很好地做到兼容,而 direction 方案是针对 CSS 文件的,如果要针对 html 文件则需要另外额外的工作。

说了一些 transform 方案对比与 direction 方案的优势,下面就讲讲其缺点:

第一,它需要对我们已有的业务场景进行改造,入侵业务代码,也就是说,如果你的场景相对比较分散,公用模块复用率较低,那么在使用 transform 方案的时候就需要对每个场景单独进行修改适配,当然如果你的场景公用组件多,对公共模块修改可以很好在各个场景中复用,这样一次性的成本就相对比较容易。

第二点,对于一些页面滚动组件需要做额外的兼容操作,经过我的实践发现,滚动组件在经过翻转之后存在着一些问题,初步认为是因为翻转之后带来一些高度属性值的变化,具体原因需要等兼容适配时候才清楚。

总结

本文通过对 direction/transform 属性使用和剖析,设计了两款不同思路的 LTR/RTL 通用布局方案,两套方案各有千秋,有优势,也有自身不足的地方。

在动手准备改造之前,最好先跟 UED 确认好 RTL 布局的设计规范,避免因为主观认知导致错误的视觉偏差,这样可以给中东地区的用户提供更加本地化的体验,这里也有关于页面布局双向性的设计规范,感兴趣的可以看一看:MATERIAL DESIGN - Bidirectionality

最后针对不同的业务场景选择,运用合适的通用布局方案,才能有效地降低开发和维护成本。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment