bigo-frontend / blog

👨🏻‍💻👩🏻‍💻 bigo前端技术博客
https://juejin.cn/user/4450420286057022/posts
MIT License
129 stars 9 forks source link

【bigo】网页RTL布局适配方案和rtlcss插件在项目中实践 #37

Open lchreal6 opened 3 years ago

lchreal6 commented 3 years ago

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

前言

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

何为RTL布局

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

compare

LTR和RTL布局的主要区别

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

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

总而言之,页面用户体验和界面设计在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语句之间,它能作用于单个或多个节点

//转换后 .float-left { float: left; }


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

- 添加/插入/替换/忽略 属性值
```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标签设置属性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版本
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/