网页RTL布局适配方案和rtlcss插件在项目中实践

前端开发工程师 @ bigo

file

本文首发于:github.com/bigo-fronte… 欢迎关注、转载。

前言

bigo作为全球化的互联网企业,产品体验要求国际化,本地化,所面向的用户来自世界各地,他们在产品使用习惯各有不同。尤其对于使用诸如阿拉伯语、乌尔都语、希伯来语等用户,拥有着庞大的数量群体,他们的阅读习惯与中、英文大为不同,是从右到左的顺序进行阅读,从产品使用上需要兼顾这部分用户需求。为了更好地符合用户习惯,作为一名前端开发,我们更应该在页面针对不同语言进行布局适配,努力地提高用户使用体验。

何为RTL布局

RTL布局通常称为LTR的镜像布局,整体上是与我们日常看到的页面布局对称,从右往左显示内容。例如显示的文字右侧排列,从右向左阅读,导航顺序相反布局,带有方向性的图标镜像显示等。

compare

LTR和RTL布局的主要区别

元素LTRRTL
文本句子从左向右阅读。句子从右向左阅读。
时间线事件序列从左向右进行。事件序列从右向左进行。
图像从左向右的箭头表示向前运动:→从右向左的箭头表示向前运动:←

虽然RTL大体上是镜像布局,但并不是所有地方都需要这样处理,其中有些细节需要注意:

  • 页面交互操作方向同样需要改变方向,例如跑马灯、tab组件,向左滑动代表后退,向右滑动代表前进

115487974-53cd0b80-a28c-11eb-812a-d61e91b99d90.gif

tab

  • 某些图标依然按照原来的方向显示,对于不传递方向性的图标、环形流逝方向、代表右手持有的物体和带有斜杠的图标按原有图案展示即可,无需处理。

icon

  • 媒体播放按钮和进度指示器反映的是播放方向,依然按LTR方向展示

mediaplay

总而言之,页面用户体验和界面设计在RTL布局下需要以RTL阅读思维为核心进行设计,了解了RTL布局的特点后接下来总结下现在比较流行常用的页面适配方案

RTL适配方案

direction

最常用的适配方法是在标签中添加dir属性或使用css的属性direction,指定值为rtl

<body dir="rtl">
content
</body>
复制代码
html {
  direction: rtl;
}
复制代码

设置后,你能够看到页面的文字从右往左的顺序显示。但某些地方会看上去觉得奇怪,这个css属性对于带有左右方向调整的样式例如leftmagin-left等属性无效,需要额外的处理,在RTL里将left修改成rightmargin-left修改成margin-rigtht。例如:

.box {
  left: 10px;
  margin-left: 10px;
}
复制代码

你需要额外添加样式,并进行样式覆盖:

.box {
  left: 10px;
  margin-left: 10px;
}

[dir="rtl"] .box {
  left: 0;
  margin-left: 0;
  right: 10px;
  margin-right: 10px;
}
复制代码

以下这些css属性在RTL布局需要重新正确地设置:

background-position
background-position-x
border-bottom-left-radius
border-bottom-right-radius
border-color
border-left
border-left-color
border-left-style
border-left-width
border-radius
border-right
border-right-color
border-right-style
border-right-width
border-style
border-top-left-radius
border-top-right-radius
border-width
box-shadow
clear
direction
float
left
margin
margin-left
margin-right
padding
padding-left
padding-right
right
text-align
transition
transition-property
复制代码

另外,direction改变flex和inline-block元素的方向,flex布局适配RTL,在遇到RTL布局的场景下,请尽可能地使用flexbox布局。

transform

另一个简单粗暴的方法是使页面水平翻转,实现水平镜像对称,用到了transform: scaleX(-1)属性。

html {
  transform: scaleX(-1);
}
复制代码

scale

从上图可以看出,使用scaleX(-1),页面整体上实现了镜像对称,我们无需在css层面上做过多的细节处理,但相应地文字和图像也翻转了,文字显示上会变得非常奇怪,对于文字要再进行翻转处理一次,涉及文字和非对称的图片也要重新设计。

css逻辑属性

CSS逻辑属性定义:是CSS的一个模块,其引入的属性与值能做从逻辑角度控制布局,而不是从物理、方向或维度来控制。
简单地说,CSS逻辑属性没有左右物理方向性的概念,基于参照物来描述起点和终点,如LTR布局下start代表left方向,end代表right方向,RTL布局下start代表right方向,end代表left方向,从而提供原生能力去适配LTR和RTL布局,前端样式开发无需考虑布局适配问题

如使用margin-inline-start来代替margin-left,在RTL布局里相当于设置了margin-right的效果,我们无需额外兼容,类似的属性还有:

使用不要使用
margin-inline-start: 5px;margin-left: 5px;
padding-inline-end: 5px;padding-right: 5px;
float: inline-start;float: left;
inset-inline-start: 5px;left: 5px;
border-inline-end: 1px;border-right: 1px;
border-{start/end}-{start/end}-radius: 2px;border-{top/bottom}-{left/right}-radius: 2px;
padding: 1px 2px;padding: 1px 2px 1px 2px;
margin-block: 1px 3px; && margin-inline: 4px 2px;margin: 1px 2px 3px 4px;
text-align: start; or text-align: match-parent;text-align: left;

在某些css属性没有对应的逻辑属性的时候,我们还是要单独对RTL布局定义

在LTR布局中显示

.search-box {
  background-image: url(chrome://path/to/searchicon.svg);
  background-position: 7px center;
}
复制代码

在RTL布局时,添加如下样式进行覆盖

// 需自行在html标签设置dir属性以作区分
[dir="rtl"] .search-box {
  background-position-x: right 7px;
}
复制代码

然而这些css逻辑属性具有兼容性的问题,大部分的浏览器版本部分属性不支持、IE浏览器完全不支持 logical_properties

查看网站

css in js

该方案是基于css in js的思想,即是使用js来编写css样式,将css和js代码合并在js文件里,常用于jsx组件语法里面。在React组件里,通过定义样式对象来赋予元素样式,实现组件样式,目前比较流行的css in js库有styled-components

import React from 'react';

import styled from 'styled-components';

const Title = styled.h1`
  font-size: 1.5em;
  text-align: center;
  color: palevioletred;
`;

const Wrapper = styled.section`
  padding: 4em;
  background: papayawhip;
`;

<Wrapper>
  <Title>Hello World, this is my first styled component!</Title>
</Wrapper>
复制代码

使用js操作样式的好处是能够在运行时判断是否在rtl语言环境,并相应地修改css样式代码。可以用到styled-components的一个插件stylis-plugin-rtl来处理RTL布局,其背后原理是使用cssjanus库通过js进行rtl样式转换。

import styled, { StyleSheetManager } from "styled-components";
import rtlPlugin from "stylis-plugin-rtl";

const Box = styled.div`
  padding-left: 10px;
`;

function MakeItRTL() {
  return (
    <StyleSheetManager stylisPlugins={[rtlPlugin]}>
      <Box>My padding will be on the right!</Box>
    </StyleSheetManager>
  );
}
复制代码

less/sass预处理语言

利用css预处理语言的mixin混合指令功能,预先写好混合指令,额外生成RTL布局代码。

@mixin margin-left($val: 0) {
  margin-left: $val;
  [dir='rtl'] & {
    margin-left: initial;
    margin-right: $val;
  }
}
复制代码

引入公共mixin指令scss文件,在需要的时候使用@inlcude方法

@import '@assets/rtl.scss';

.el {
  border: 1px solid #000;

  @include margin-left(10px);
}
复制代码

生成后的代码分别适配LTR和RTL布局:

.el {
  margin-left: 10px;
  border: 1px solid #000;
}

[dir='rtl'] .el {
  margin-left: initial;
  margin-right: 10px;
}
复制代码

使用rtlcss、css-flip转换工具

可以通过css转换工具如rtlcss/css-flip,无需提前写好混合指令,即可输出RTL样式布局代码。它能够根据所写的css代码自动转编译成带有rtl布局的css代码,无需额外大量添加rtl布局的css代码,编译过程中自动帮我们处理,省时省力,提高我们项目开发效率。

原来的样式

.example {
  display:inline-block;
  padding:5px 10px 15px 20px;
  margin:5px 10px 15px 20px;
  border-style:dotted dashed double solid;
  border-width:1px 2px 3px 4px;
  border-color:red green blue black;
  box-shadow: -1em 0 0.4em gray, 3px 3px 30px black;
}
复制代码
npm install -g rtlcss
rtlcss input.ltr.css output.rtl.css
复制代码

编译后会转换成

.example {
  display:inline-block;
  padding:5px 20px 15px 10px;
  margin:5px 20px 15px 10px;
  border-style:dotted solid double dashed;
  border-width:1px 4px 3px 2px;
  border-color:red black blue green;
  box-shadow: 1em 0 0.4em gray, -3px 3px 30px black;
}
复制代码

rtlcss提供丰富的特性来满足适配需求:

1、 Control Directives(控制指令)
控制指令放置在css声明或css语句之间,它能作用于单个或多个节点

  • 忽略属性
.code {
  /*rtl:ignore*/
  direction:ltr;
  /*rtl:ignore*/
  text-align:left;
}
复制代码
  • 添加额外的样式
  /*rtl:raw:
  #example {
      border-radius: 25px 0 0 25px;
  }
  */
复制代码
  • 去除属性
div {
  /*rtl:remove*/
  direction: rtl;
  /*rtl:remove*/
  text-align: right;
  padding: 10px;
}
复制代码
  • 重命名选择器
/*rtl:rename*/
.float-right {
    float: right;
}

//转换后
.float-left {
    float: left;
}
复制代码

2、Value Directives(值指令)
值指令放置在css声明的值里,它能作用于所包含的声明节点

  • 添加/插入/替换/忽略 属性值
.sample {
  font-family:"Droid Sans", "Helvetica Neue", Arial /*rtl:prepend:"Droid Arabic Kufi",*/;
  direction:ltr /*rtl:ignore*/;
  font-size:16px /*rtl:14px*/;
  transform:rotate(45deg) /*rtl:append:scaleX(-1)*/;
  background: #00FF00 url(bgimage.gif) no-repeat /*rtl:insert:fixed*/ top;
}

// 转换后
.sample {
  font-family: "Droid Arabic Kufi", "Droid Sans", "Helvetica Neue", Arial;
  font-size: 14px;
  transform:rotate(45deg) scaleX(-1);
  background: #00FF00 url(bgimage.gif) no-repeat fixed top;
}
复制代码

更详尽的使用方法可查看官网:rtlcss.com/

这里编译后的文件会将LTR相关的属性转换成RTL样式,只保留RTL布局的代码,假如想在一份css文件都存在LTR和RTL布局的样式呢,我们需要用到postcss的插件postcss-rtlcss,结合webpack构建工具,实现自动化添加RTL布局样式

方案比较

方案结论
direction="rtl"影响了文字排列和布局,需要手动适配的范围大,要尽可能地使用flex和内联块元素进行布局
transform: scaleX(-1)处理简单,布局镜像,但文本和图片显示会有翻转问题
css逻辑属性原生适配,但浏览器兼容性差,部分属性仍需要另外处理,如background-position
css in js适合jsx语法开发,需要基于css in js模式编写,js动态生成css,运行耗时,有性能代价,可读性差
less/scss 预处理语言混合指令特性增加额外rtl样式代码大小,虽然减少了一部分代码编写工作量,但还是要手工引入混合指令处理
rtlcss、css-flip 转换工具增加额外rtl样式代码大小,在构建时自动化生成rtl代码,基本不增加适配开发工作量

rtlcss插件在项目开发中实践

rtlcss方案虽然会增加样式代码,但是不用另外额外增加工作量,构建工具帮我们完成了脏活,也不失为较优方案,下面是使用postcss-rtlcss插件在项目开发中的实践,基于此工具的应用,比平时正常开发项目至少节省了0.5天工作量来处理多语言适配。

首先安装需要的插件

npm i postcss-rtlcss --save-dev

// 将postcss升级到8.0.0版本
npm i postcss@8.0.0 --save-dev
复制代码

然后在.postcssrc.js文件中添加配置

module.exports = {
  'plugins': {
    'postcss-rtlcss': {} // postcss-rtl插件配置,可以添加插件配置选项
  }
}
复制代码

基于用户所使用的语言,在html标签设置属性dirltrrtl

const rtlLangs = ['ar', 'ur', 'fa', 'pr'];

if (rtlLangs.includes(navigator.language)) {
  document.documentElement.setAttribute('dir', 'rtl');
} else {
  document.documentElement.setAttribute('dir', 'ltr');
}
复制代码

这样在webpack打包时就会使用postcss-rtlcss插件进行处理,项目样式无需过多改动

在bigo内部前端旧项目里直接使用postcss-rtlcss插件,可能会遇到以下问题,有可能是postcss-loader版本不兼容造成的:

1、报Error: true is not a PostCSS plugin错误
解决方法:尝试升级postcss-loader到4.2.0版本

2、报this.getOptions is not a function错误
解决方法:尝试降级postcss-loader到4.2.0版本或降级sass-loader版本
stackoverflow.com/questions/6…

使用该插件其中遇到另一个问题是,假如在项目中使用sass或less语言,直接使用rtlcss的指令注释写法是不生效的 rtlcss.com/learn/usage…
sass-lang.com/documentati…

对于Control Directives语法,需要/*!开头

// 自闭合
.code {
  /*!rtl:ignore*/
  text-align:left;
}

// 区块
.code {
  /*!rtl:begin:ignore*/
  direction:ltr;
  /*!rtl:end:ignore*/
  text-align:left;
}
复制代码

对于Value Directives语法,需要使用插值语法
SASS/SCSS会忽略放置在声明里的注释,为了确保Value Directives有效需要使用SASS的插值语法

.example {
  text-align: left #{"/*!rtl:ignore*/"};
}
复制代码

该postcss插件是基于rtlcss插件的基础上封装的,具体的使用方法可以阅读官方文档 github.com/elchininet/…

总结

RTL的适配方案众多,开发者需要结合项目的需求特点来选取和组合方案,以达到最优选型,因地制宜,目的是最大程度上在提高开发效率,节省开发周期的基础上提高可维护性

相关资料:

developer.mozilla.org/en-US/docs/…
developer.mozilla.org/zh-CN/docs/…
www.mdui.org/design/usab…
hacks.mozilla.org/2015/09/bui…
hacks.mozilla.org/2015/10/bui…

欢迎大家留言讨论,祝工作顺利、生活愉快!

我是bigo前端,下期见。

文章分类
前端
文章标签