Web RTL 左右镜像
点击关注公众号,“技术干货”及时达!
前言做过海外业务同学肯定对 RTL( Right-To-Left)有所了解,阿拉伯语、希伯来语的的文字书写是从右到左的,为了确保页面在所有语种下需要对 RTL 做好适配工作,我们知道通过 dir 属性或者 direction样式可以让设置文字书写顺序
!DOCTYPEhtml
htmldir="ltr"
bodystyle="direction:rtl;"
Thisisatext.
/body
/html
其实即使不设置这些属性、样式,当网页上包含阿拉伯语文本时,现代浏览器能够根据字符的 Unicode 范围和语言特性自动判断该文本的方向
现实的困境其实对于 RTL 习惯的语种,不仅仅是文字从右到左,而是几乎所有的交互表达都需要做左右镜像表达,即使是一个 loading 的图标的旋转方向在 RTL 下也是逆转的
--
因此友好的 RTL 体验需要兼顾到
文字书写顺序横向排布的图标顺序返回、前进的图标新页面加载或者页面后退对应的交互方向横向滚动条滑动方向横向排列 Tab Item 顺序及左右方向...
仅仅通过 dir 或 direaction 是做不到的上述的交互左右镜像的,除了文字的方向 CSS 中涉及到左右的属性都需要镜像设置才可以
方案一:使用 CSS 逻辑属性实现 RTLCSS 逻辑属性(Logical Properties)和逻辑值(Logical Values)是为了在不同书写模式(writing mode)之间提供一致的布局和样式控制。通过使用逻辑属性,你可以创建更具适应性的布局,无需根据书写方向手动调整
「物理属性」: 传统的 CSS 属性如 margin-left, padding-right, top 等,它们直接指定页面的物理方向(如左、右、上、下)「逻辑属性」: 根据书写模式动态地映射到物理方向,它们提供一种更抽象的方式来定义布局,从而在不同的文本方向下自动适应逻辑属性分类Block 和 Inline 尺寸属性block-size: 设置元素在块级方向(通常是垂直方向)的尺寸inline-size: 设置元素在内联方向(通常是水平方向)的尺寸这两个属性在 writing-mode 不设置为垂直书写时对 RTL 无影响,本文不做展开
.element{
block-size:50%;/*在LTR模式下等同于height:*/
inline-size:100px;/*在LTR模式下等同于width:100px;*/
}
边距(Margin)、填充(Padding)和边框(Border)margin-block-start/margin-block-end: 设置元素在块级方向上的起始或结束边距padding-block-start/padding-block-end: 设置元素在块级方向上的起始或结束填充border-block-start/border-block-end: 设置元素在块级方向上的起始或结束边框margin-inline-start/margin-inline-end: 设置元素在内联方向上的起始或结束边距padding-inline-start/padding-inline-end: 设置元素在内联方向上的起始或结束填充border-inline-start/border-inline-end: 设置元素在内联方向上的起始或结束边框常规写法的样式
.element{
margin-left:10px;
padding-right:20px;
border-right:2pxsolidblack;
}
使用逻辑属性改写后,当父元素设置dir="rtl"后可以自动 RTL
.element{
margin-inline-start:10px;
padding-inline-end:20px;
border-inline-end:2pxsolidblack;
}
边框圆角border-start-start-radius: 设置块级起始方向和内联起始方向交汇处的圆角半径(在 LTR 模式下,通常对应左上角)border-start-end-radius: 设置块级起始方向和内联结束方向交汇处的圆角半径(在 LTR 模式下,通常对应右上角)border-end-start-radius: 设置块级结束方向和内联起始方向交汇处的圆角半径(在 LTR 模式下,通常对应左下角)border-end-end-radius: 设置块级结束方向和内联结束方向交汇处的圆角半径(在 LTR 模式下,通常对应右下角).element{
/*LTR时候左上角为圆角,RTL时候自动变为右上角为圆角*/
border-start-start-radius:50%;
}
位置属性inset-block-start/inset-block-end: 指定元素相对于其包含块在块级方向上的起始或结束位置inset-inline-start/inset-inline-end: 指定元素相对于其包含块在内联方向上的起始或结束位置.element{
position:fixed;
/*LTR时候在ViewPort右下角,RTL时候在ViewPort左下角*/
inset-block-end:10px;
inset-inline-end:20px;
}
问题看起来使用 CSS 逻辑属性看起来是可行的,不过业界没人用,主要有几个原因
浏览器兼容性:根据 caniuse IE 全部不支持 CSS 逻辑属性,不过考虑到 IE 即将退役问题其实不算大
代码可读性:在项目中混合使用物理属性和逻辑属性,可能会让代码变得不易读和不易维护,很多开发甚至从来没有接触过 CSS 逻辑属性
不支持 flex、grid、transform 等属性方向属性:逻辑属性不能直接控制 flex-direction、align-items、transform 等属性的行为,在这些布局场景下仍需要依赖传统的物理属性手动调整特定样式
第三点最为致命,几乎导致了方案的不可用
方案二:transform: scaleX(-1)说到完全的镜像翻转貌似通过transform: scaleX(-1)就可以实现了,几乎没有任何成本,拿网页的一块区域做个测试
额,明明是英语却一个字也不认识了,因为文案也被镜像反转了,这不是我们预期的效果,可以在文案的父元素上添加一个类反弹翻转
.contains-text{
transform:scaleX(-1);
}
这样文字的问题解决了
左右箭头被翻转符合预期,可是仔细观察却发现怪怪的,调整时间的旋钮位置和表盘内的数字,也就是实际商品图片也被翻转了,这就不对劲了,总不能为了 RTL 扭曲现实
实际交互起来问题更大,使用 scaleX(-1) 仅仅是视觉上的翻转,元素的实际点击区域和交互行为没有发生改变。总结而言使用 transform 方案有几个问题
「文本和图像的误读」:scaleX(-1) 会翻转元素的所有内容,包括文本、图像、图标等,虽然这在某些情况下可以有效地翻转布局,但会导致文本和图像也被翻转,这通常不是预期的效果「悬停和焦点效果的错位」:由于元素的实际位置没有变化,悬停、焦点等效果可能会出现位置错位或行为异常「鼠标点击区域的问题」:点击区域仍然位于元素原始的物理位置,而不是翻转后视觉上的位置,这可能导致用户点击无效或者点击不准确「难以扩展和适应变化」:在扩展复杂功能或调整布局时,需要不停的翻转再翻转,需要处理更多的异常情况或逻辑分支,增加代码的复杂性「SEO 影响」:使用 scaleX(-1) 的翻转内容可能会影响搜索引擎的索引和排名,因为代码只是改变了视觉显示位置,在 HTML 代码中优先级高的 HTML 出现在了 靠后的位置,对搜索引擎形成误导方案三:为 RTL 适配代码启示我们很早就知道 RTL 的答案——为所有元素适配一套样式代码就可以了
.element{
display:inline-block;
padding-left:10px;
}
[dir="rtl"].element{
display:inline-block;
padding-right:10px;
}
只不过这样做成本太高了,而且开发过程中很容易忘记某个元素的处理,被我们第一时间拒绝了
rtlcss 构建时自动生成由于 RTL css 的样式非常有规则,所以可以在构建代码时使用工具处理,rtlcss 就是这类工具中的佼佼者,rtlcss 配备了一个 playground 方便开发者预览器生成的 RTL css
?Instead of authoring two sets of CSS files, one for each language direction. Now you can author the LTR version and RTLCSS will automatically create the RTL counterpart for you!
?可能各位前端都没意识到有这么多 css 属性和左右方向相关
通过 webpack 接入使用 postcss-rtlcss 可以很方便的把 rtlcss 集成到项目中
rules:[
{
test:/.css$/,
use:[
{loader:'style-loader'},
{loader:'css-loader'},
{
loader:'postcss-loader',
options:{
postcssOptions:{
plugins:[
postcssRTLCSS(options)
]
}
}
}
]
}
]
mode在 options 中各种能力可以对生成代码风格进行调整,其中影响最大的是 mode
.test1,.test2{
background-color:#FFF;
background-position:10px20px;
border-radius:02px08px;
color:#666;
padding-right:20px;
text-align:left;
transform:translate(-50%,50%);
width:100%;
}
「combine」:为 LTR 和 RTL 生成两份独立、完整的代码,这是官方推荐的方式,虽然生成的代码量比较大,但非常安全
.test1,.test2{
background-color:#FFF;
background-position:10px20px;
color:#666;
width:100%;
}
[dir="ltr"].test1,[dir="ltr"].test2{
border-radius:02px08px;
padding-right:20px;
text-align:left;
transform:translate(-50%,50%);
}
[dir="rtl"].test1,[dir="rtl"].test2{
border-radius:2px08px0;
padding-left:20px;
text-align:right;
transform:translate(50%,50%);
}
「override」:只对 RTL 相关的代码提取出来后做 override,代码量较少
.test1,.test2{
background-color:#FFF;
background-position:10px20px;
border-radius:02px08px;
color:#666;
padding-right:20px;
text-align:left;
transform:translate(-50%,50%);
width:100%;
}
[dir="rtl"].test1,[dir="rtl"].test2{
border-radius:2px08px0;
padding-right:0;
padding-left:20px;
text-align:right;
transform:translate(50%,50%);
}
我们可以注意到对于源码中的 padding-right: 20px; RTL 需要生成两句代码才能做到正确的 override
[dir="rtl"].test1,[dir="rtl"].test2{
padding-right:0;
padding-left:20px;
}
「processUrls」 自带左右的图片地址处理使用 rtlcss 不会让图片误翻转,但类似前进、后退的箭头需要左右翻转,这时候可以通过图片地址和 rtlcss 做配合实现
.test1,.test2{
background-image:url("./folder/subfolder/icons/ltr/chevron-left.png");
left:10px;
}
开启 process Urls
constoptions={processUrls:true
这样就可以在生成的代码中自动修改图片地址中的 left、right
[dir="ltr"].test1,[dir="ltr"].test2{
background-image:url("./folder/subfolder/icons/ltr/chevron-left.png");
left:10px;
}
[dir="rtl"].test1,[dir="rtl"].test2{
background-image:url("./folder/subfolder/icons/rtl/chevron-right.png");
right:10px;
}
逃生出口即使使用 rtlcss 在部分场景开发者可以通过注释禁用或者特殊处理源码,介绍几个常用的,完整使用参考这里
/*rtl:ignore*//*选择器内全部禁用*/
.test1,.test2{
text-align:left;
left:10px;
}
.test3,.test4{
text-align:left;
/*rtl:ignore*//*下一行禁用*/
left:10px;
}
/*指定RTL时候生成的value*/
.test1,.test2{
font-family:Arial,Helvetica/*rtl:"DroidArabicKufi"*/;
left:10px;
}
内联样式处理rtlcss 不支持内联样式,或者使用 JavaScript 生成的动态样式,尽量避免在 css 文件外设置方向性相关的样式,可以使用 JavaScript 动态添加 className
direction 设置让 RTL 相关的 CSS 生效,需要在 html 节点添加dir="rtl"属性,为了让 RTL 用户的页面不发生抖动,添加的时间越早越好
「根据用户设置的 cookie 或 App setting」
一般支持多语言的网站都会允许用户选择偏好的语种,信息可以存储在 cookie 中,这样在可以在服务器端识别并设置 dir 属性
app.get('/',(req,res)={
//从会话或cookie获取用户偏好
constisRtl=req.session.isRtl||req.cookies.isRtl;
res.render('index',{isRtl
});
「根据 HTTP request header Accept-Language」
现代浏览器会在 HTTP request header 通过 Accept-Language携带用户浏览器设置的偏好语种,一定程度上也可以代表用户自身的偏好,在用户第一次访问网站时候可以根据此信息决定 dir 属性的 value
app.get('/',(req,res)={
//获取用户的语言设置
constuserLang=req.headers['accept-language']||'en';
constrtlLanguages=['ar','he','fa','ur'];//波斯语和乌尔都语也是RTL的
//检查语言是否属于RTL语言
constisRtl=rtlLanguages.some(lang=userLang.includes(lang));
//根据isRtl决定使用的模板或样式
res.render('index',{isRtl
});
「在页面顶部执行的 JavaScript」
部分页面可能缓存在 CDN,根据用户请求设置 RTL 属性,那么需要让设置 dir 属性的 JavaScript 尽量在页面内容渲染之前执行
constuserLang=navigator.language||navigator.userLanguage;
constrtlLanguages=['ar','he','fa','ur'];
constisRtl=rtlLanguages.some(lang=userLang.includes(lang));
if(isRtl){
document.documentElement.setAttribute('dir','rtl');
}
三方库适配一些广泛使用的优秀 UI 组件已经自己实现了 RTL,在项目中需要对其 exclude
Tailwind 支持 RTL带前缀的类名Tailwind 在 3.0 版本支持了 RTL,支持的方式也颇为落后
htmldir="rtl"
body
divclass="groupflexitems-center"
divclass="ltr:ml-3rtl:mr-3"
pclass="text-smfont-mediumtext-slate-300".../p
pclass="text-smfont-mediumtext-slate-500".../p
/div
/div
/body
/html
没错,在源码中把 LTR 和 RTL class 都写上,通过 dir 决定哪个生效,本来就很多 class 的 html 有雪上加霜了。。。
CSS 逻辑属性Tailwind 3.3 支持 CSS 逻辑属性来支持 RTL,添加了很多相关的类名,例如
ms-0: margin-inline-start: 0px;me-0: margin-inline-end: 0px;ps-0: padding-inline-start: 0px;pe-0: padding-inline-end: 0px;CSS 逻辑属性虽然本身不好推广,不过在 Tailwind 中难度可以小很多,反正所有类名都是新的,不过局限性依然存在
插件简化可以通过@tailwindcss/rtl简化 Tailwind RTL支持,其作用和 rtlcss 类似,将类名下的内容做左右镜像处理,不过它会尽量使用 CSS 逻辑属性
源代码
.container{
margin-left:20px;
padding-right:30px;
justify-content:flex-start;
text-align:left;
}
.button{
margin-right:10px;
padding-left:20px;
}
.navbar{
float:left;
}
处理后追加内容
.container{
margin-inline-start:20px;
padding-inline-end:30px;
text-align:start;
}
[dir="rtl"].container{
justify-content:flex-end;
}
.button{
margin-inline-end:10px;
padding-inline-start:20px;
}
.navbar{
float:inline-start;
}
antd 支持 RTL相对 Tailwind 而言 antd RTL 对开发者友好了很多,antd 提供了一个全局配置 ConfigProvider,通过在应用的根组件中使用 ConfigProvider,可以很容易地切换整个应用的 RTL 模式。
import{ConfigProvider}from'antd';
importarEGfrom'antd/es/locale/ar_EG';//阿拉伯语
ConfigProviderlocale={arEG}direction="rtl"
{/*你的组件*/}
/ConfigProvider
在 antd 内部使用多种技术混合实现自动 RTL
「CSS-in-JS 运行期替换类名」:antd 有一套和 LTR 对应的 RTL 类名,如果 Provider 传入了 direction="rtl",在运行期这些类会自动以不同的方式添加前缀或后缀,这是 antd 支持 RTL 的主要方式「CSS-in-JS 运行期动态覆盖」:antd 利用 CSS-in-JS 解决方案,允许在组件级别动态生成样式。启用 RTL 模式后 antd 会生成特定的 CSS 规则覆盖默认的 LTR样式,例如margin-left 和 margin-right 等属性被交换「CSS 逻辑属性」:antd 也会利用 CSS 逻辑属性,例如 margin-inline-start 和 margin-inline-end等更多特殊处理RTL Styling 101 - An extensive guide on how to style for RTL in CSS
点击关注公众号,“技术干货”及时达!
阅读原文
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。 项目经理在线