RTL改造
背景及意义
什么是RTL?
RTL为right to left的UI样式,因中东地区语言的阅读习惯为从右向左阅读,所以UI界面上,所有元素均需要靠右侧排列。包括文字、icon、图片等。尤其是表示方向性的icon或图片需要注意镜像处理。
RTL适用语言
| 语言 | 缩写 | 英文名 | |
|---|---|---|---|
| 阿拉伯语 | ar | Arbic | العربية |
| 波斯语 | fa | Persian | فارسی |
| 希伯来语 | iw | Hebrew | עברית |
| 乌尔都语(印度、巴基斯坦) | ur | Urdu | اردو |
| 维吾尔语 | Uyghur |
技术难点(思考过程)
适配产品范围:
- 当地真实用户使用的产品,目前包括乘客端、司机端、增长工具、fleet、clipper和客服B端部分功能(当地客服使用相关),增长后台部分功能(用户触达相关)、官网
- 内部员工使用的产品可不适配(使用英文)
适配需求类型:
- 需求会在前端展现文案、图片、布局即需要适配RTL
- 前端无任何感知的需求不需要适配RTL
当前现状:
- 老项目较多,且各有各的技术架构,怎么样兼容到所有的项目呢?
- 接入到每个项目中,怎么样实现无侵扰呢?
技术方案(思考过程)
老项目较多,所以需要做无侵扰式接入;
-
局部调整:有的项目不能全局配置RTL,比如规则配置类的平台,需要针对某个组件、某个输入框做处理; 分模块动态控制RTL,进一步根据标识限定当前组件的RTL展示,所以做一层HOC处理,供给RTL标识; (1)无侵入的局部调整,定位是增量找到所有需要调整的组件,直接注入CSS,工具会处理; (2)用户处理标识,做自定义调整;
-
全局调整:比如一些端上H5页面,需要全局RTL; 判定RTL后,供给RTL标识,将结果全局挂载,全局处理样式即可。
技术实现
RTLTools 主要功能:
- 向外提供统一的标志位,以供个性化处理,解耦业务逻辑;
- 统一处理节点RTL(直接注入cssStr/cssStrList);
insert RTL css
=========================================================================================
^ ^
| |
| Y | Y
Y Y
$0 isRTL---> $1 (exist && (string[] or string)) ---> $1 includes '{}' ---> add '{style}'
| |
| N | N
v v
=========================================================================================
insert LTR css
特点:RTL无侵入式改造并提供标准化的复用逻辑
业务方介入,只需要提供:
- 判断函数;
- 以及要注入的css, 多个的情况下第二个参数向内部传入css List;
- 以及判断函数所需要的参数。
支持的两种css注入形式(以及单个和多个):
两种形式区别:是否有具体的css配置(比如direction),一定要提供足够精准定位的css路径; 均可可支持cssStr(单个)及cssStrList(多个);
注入css的具体传参形式
// 方式1
const CSS_TEMPLATE_01 = `body
div.ant-drawer.ant-drawer-open.ora_drawer.strategy-drawer
div.ant-drawer-content-wrapper
div.ant-drawer-content
div.ant-drawer-wrapper-body
div.ant-drawer-body
div.ora_drawer_body
.ant-input`;
// 方式2
const CSS_TEMPLATE_02 = `body
div.ant-drawer.ant-drawer-open.ora_drawer.strategy-drawer
div.ant-drawer-content-wrapper
div.ant-drawer-content
div.ant-drawer-wrapper-body
div.ant-drawer-body
div.ora_drawer_body
.ant-input {
text-align: right !important;
direction: rtl !important;
}`;
// 方式 3
const CSS_TEMPLATE = [CSS_TEMPLATE01];
标志位处理
为了支持更多的场景,需要自行传入具体的判断逻辑函数及其所需要的实时变化参数,向外提供统一的标志位处理函数;
处理逻辑:
static isRTL(judgeFn: Function, ...others) {
if (judgeFn && typeof judgeFn === 'function') {
return Boolean(judgeFn(...others));
}
return false;
}
调用:
export const isRTL = (nodeType, nodeId) => {
return RTLTool.isRTL(judge, nodeType, nodeId);
};
处理RTL & LTR
为了支持更多的场景,需要自行传入具体的判断逻辑函数及其所需要的实时变化参数,向外提供统一的标志位处理函数; 并且需要传入要注入的css模板;
处理逻辑:
static handleCSSItem(judgeFn: Function, insertCssStr, ...others) {
if (insertCssStr) {
// 切换rtl & ltr注入
const regFlag = /{(\S|\s)*}/g.test(insertCssStr);
let tempRTLStr = '';
if (regFlag) {
tempRTLStr = insertCssStr;
} else {
tempRTLStr = `${insertCssStr} {
text-align: right !important;
direction: rtl !important;
}`;
}
const tempLTRStr = tempRTLStr.replace(
/{(\S|\s)*}/g,
`{
text-align: left !important;
direction: ltr !important;
}`
);
switch (Boolean(RTLTool.isRTL(judgeFn, ...others))) {
case true:
insertCss(tempRTLStr);
break;
default:
insertCss(tempLTRStr);
}
}
}
/**
* 统一处理节点(直接传css)
*/
static handleCSS(judgeFn: Function, cssInsert: string | string[], ...others) {
// insertCssStr 考虑 数组与 单个 的情况
if (cssInsert && Array.isArray(cssInsert)) {
cssInsert.forEach(item => {
RTLTool.handleCSSItem(judgeFn, item, ...others);
});
} else if (cssInsert && typeof cssInsert === 'string') {
RTLTool.handleCSSItem(judgeFn, cssInsert, ...others);
}
}
调用:
export const handleCSS = (CSS_TEMPLATE, nodeType, nodeId) => {
RTLTool.handleCSS(judge, CSS_TEMPLATE, nodeType, nodeId);
};
接入:对于项目中公共组件的改造方案
特点: 无侵入式 方案: 提升HOC 具体: 利用HOC做统一的数据处理, 比如要根据currentNode的node_type和template_id进行标识为判定,先做统一的标志为处理,然后不侵入公共组件的逻辑,直接传入标志位
/**
* @author nikangdi
* @description RTL 无侵入式修改富文本公共组件
*/
import React from 'react';
import { isRTL } from 'services';
import { StoreState } from 'reducers';
import { useSelector } from 'react-redux';
import { RichText as Rich, RichTextProps } from './richtext';
export const RichText = React.forwardRef((props: RichTextProps, ref: any) => {
const { currentNode } = useSelector((s: StoreState) => ({
currentNode: s.strategy.currentNode,
}));
const [RTLFlag, setRTLFlag] = React.useState<boolean>(false);
React.useEffect(() => {
setRTLFlag(Boolean(isRTL(currentNode?.node_type, currentNode?.template_id)));
}, [currentNode]);
return <Rich {...props} ref={ref} isRTL={RTLFlag} />;
});