本文首发于:https://github.com/bigo-frontend/blog/ 欢迎关注、转载。
前言
bigo作为全球化的互联网企业,产品体验要求国际化,本地化,所面向的用户来自世界各地,他们在产品使用习惯各有不同。尤其对于使用诸如阿拉伯语、乌尔都语、希伯来语等用户,拥有着庞大的数量群体,他们的阅读习惯与中、英文大为不同,是从右到左的顺序进行阅读,从产品使用上需要兼顾这部分用户需求。为了更好地符合用户习惯,作为一名前端开发,我们更应该在页面针对不同语言进行布局适配,努力地提高用户使用体验。
何为RTL布局
RTL布局通常称为LTR的镜像布局,整体上是与我们日常看到的页面布局对称,从右往左显示内容。例如显示的文字右侧排列,从右向左阅读,导航顺序相反布局,带有方向性的图标镜像显示等。
LTR和RTL布局的主要区别
元素 | LTR | RTL |
---|---|---|
文本 | 句子从左向右阅读。 | 句子从右向左阅读。 |
时间线 | 事件序列从左向右进行。 | 事件序列从右向左进行。 |
图像 | 从左向右的箭头表示向前运动:→ | 从右向左的箭头表示向前运动:← |
虽然RTL大体上是镜像布局,但并不是所有地方都需要这样处理,其中有些细节需要注意:
- 页面交互操作方向同样需要改变方向,例如跑马灯、tab组件,向左滑动代表后退,向右滑动代表前进
-
某些图标依然按照原来的方向显示,对于不传递方向性的图标、环形流逝方向、代表右手持有的物体和带有斜杠的图标按原有图案展示即可,无需处理。
-
媒体播放按钮和进度指示器反映的是播放方向,依然按LTR方向展示
总而言之,页面用户体验和界面设计在RTL布局下需要以RTL阅读思维为核心进行设计,了解了RTL布局的特点后接下来总结下现在比较流行常用的页面适配方案
RTL适配方案
direction
最常用的适配方法是在标签中添加dir
属性或使用css的属性direction
,指定值为rtl
<body dir="rtl">
content
</body>
html {
direction: rtl;
}
设置后,你能够看到页面的文字从右往左的顺序显示。但某些地方会看上去觉得奇怪,这个css属性对于带有左右方向调整的样式例如left
,magin-left
等属性无效,需要额外的处理,在RTL里将left
修改成right
,margin-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);
}
从上图可以看出,使用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浏览器完全不支持
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;
}
更详尽的使用方法可查看官网:https://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标签设置属性dir
为ltr
或rtl
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
版本
https://stackoverflow.com/questions/66082397/typeerror-this-getoptions-is-not-a-function
使用该插件其中遇到另一个问题是,假如在项目中使用sass或less语言,直接使用rtlcss的指令注释写法是不生效的
https://rtlcss.com/learn/usage-guide/value-directives/#Tip
https://sass-lang.com/documentation/syntax/comments#in-scss
对于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
插件的基础上封装的,具体的使用方法可以阅读官方文档
https://github.com/elchininet/postcss-rtlcss
总结
RTL的适配方案众多,开发者需要结合项目的需求特点来选取和组合方案,以达到最优选型,因地制宜,目的是最大程度上在提高开发效率,节省开发周期的基础上提高可维护性
相关资料:
https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/RTL_Guidelines
https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Logical_Properties
https://www.mdui.org/design/usability/bidirectionality.html#bidirectionality-ui-mirroring-overview
https://hacks.mozilla.org/2015/09/building-rtl-aware-web-apps-and-websites-part-1/
https://hacks.mozilla.org/2015/10/building-rtl-aware-web-apps-websites-part-2/
欢迎大家留言讨论,祝工作顺利、生活愉快!
我是bigo前端,下期见。