0.1.1 • Published 6 months ago

postcss-change-dir v0.1.1

Weekly downloads
-
License
-
Repository
-
Last release
6 months ago

PostCSS RTLCSS 是一个 PostCSS 插件,用于构建包含左到右(LTR)和右到左(RTL)规则的层叠样式表(CSS)。RTLCSS 允许用户翻转整个 CSS 文件,目的是使用原始 CSS 用于一个方向,而新生成的 CSS 用于另一个方向。PostCSS RTLCSS 的作用是创建一个包含两个方向的单一 CSS 文件,或者创建一个仅包含翻转规则的最小 CSS 文件,目的是覆盖主文件。

安装

npm

npm install postcss-change-dir --save-dev

pnpm

pnpm add -D postcss-change-dir

yarn

yarn add postcss-change-dir -D

基本用法

与 CommonJS 一起使用

const postcss = require('postcss');
const postcssRTLCSS = require('postcss-change-dir');
const { Mode, Source } = require('postcss-change-dir/options');

const options = { ...可用选项... };
const result = postcss([
    postcssRTLCSS(options)
]).process(cssInput);

const rtlCSS = result.css;

与 ES6 模块一起使用

import postcss from 'postcss';
import postcssRTLCSS from 'postcss-change-dir';
import { Mode, Source } from 'postcss-change-dir/options';

const options = { ...可用选项... };
const result = postcss([
    postcssRTLCSS(options)
]).process(cssInput);

const rtlCSS = result.css;

在 Webpack 中与 postcss-loader 一起使用

rules: [
  {
    test: /\.css$/,
    use: [
      { loader: 'style-loader' },
      { loader: 'css-loader' },
      {
        loader: 'postcss-loader',
        options: {
          postcssOptions: {
            plugins: [postcssRTLCSS(options)]
          }
        }
      }
    ]
  }
]

示例

输入

.test1,
.test2 {
  background-color: #fff;
  background-position: 10px 20px;
  border-radius: 0 2px 0 8px;
  color: #666;
  padding-right: 20px;
  text-align: left;
  transform: translate(-50%, 50%);
  width: 100%;
}

.test3 {
  direction: ltr;
  margin: 1px 2px 3px;
  padding: 10px 20px;
  text-align: center;
}

使用组合模式(默认推荐)

这是推荐的方法,它将生成更多的 CSS 代码,因为每个方向都将具有其特定的前缀规则,但这是最安全的选择。

.test1,
.test2 {
  background-color: #fff;
  background-position: 10px 20px;
  color: #666;
  width: 100%;
}

[dir='ltr'] .test1,
[dir='ltr'] .test2 {
  border-radius: 0 2px 0 8px;
  padding-right: 20px;
  text-align: left;
  transform: translate(-50%, 50%);
}

[dir='rtl'] .test1,
[dir='rtl'] .test2 {
  border-radius: 2px 0 8px 0;
  padding-left: 20px;
  text-align: right;
  transform: translate(50%, 50%);
}

.test3 {
  margin: 1px 2px 3px;
  padding: 10px 20px;
  text-align: center;
}

[dir='ltr'] .test3 {
  direction: ltr;
}

[dir='rtl'] .test3 {
  direction: rtl;
}

使用覆盖模式输出

!重要 不推荐使用此方法,查看下面的原因

这是覆盖的另一种替代方法。它将生成较少的代码,因为它让主规则保持不变,并且生成较短的特定规则来覆盖受文本方向影响的属性。

.test1,
.test2 {
  background-color: #fff;
  background-position: 10px 20px;
  border-radius: 0 2px 0 8px;
  color: #666;
  padding-right: 20px;
  text-align: left;
  transform: translate(-50%, 50%);
  width: 100%;
}

[dir='rtl'] .test1,
[dir='rtl'] .test2 {
  border-radius: 2px 0 8px 0;
  padding-right: 0;
  padding-left: 20px;
  text-align: right;
  transform: translate(50%, 50%);
}

.test3 {
  direction: ltr;
  margin: 1px 2px 3px;
  padding: 10px 20px;
  text-align: center;
}

[dir='rtl'] .test3 {
  direction: rtl;
}

使用差异模式输出

!重要 不推荐使用此方法,查看下面的原因

这是第二种替代的覆盖方法。它生成的代码量最少,因为它只输出被翻转的规则,而不加前缀。这种方法的目的是生成一个单独的样式表文件,该文件将被加载在原始文件之上,以覆盖需要在特定方向翻转的规则。

.test1,
.test2 {
  border-radius: 2px 0 8px 0;
  padding-right: 0;
  padding-left: 20px;
  text-align: right;
  transform: translate(50%, 50%);
}

.test3 {
  direction: rtl;
}

两种覆盖方法的缺点

  1. 一些指令如 /*rtl:freeze*/, /*rtl:begin:freeze*//*rtl:end:freeze*/ 在这些方法中不起作用。
  2. 它们可能会覆盖来自另一个类的属性,如果同时使用多个类。查看下一个 HTMLCSS 代码:
<div class="test1 test2">这是一个示例</div>
.test1 {
  background: #666;
  color: #fff;
  padding: 20px;
}

.test2 {
  padding-right: 10px;
}

使用 combined 方法,生成的代码将是下一个:

.test1 {
  background: #666;
  color: #fff;
  padding: 20px;
}

[dir='ltr'] .test2 {
  padding-right: 10px;
}

[dir='rtl'] .test2 {
  padding-left: 10px;
}

所以,divLTR 中的 padding 将是 20px 10px 20px 20px,在 RTL 中将是 20px 20px 20px 10px。这里一切都按预期工作。

然而,使用 override 方法,生成的代码将是下一个:

.test1 {
  background: #666;
  color: #fff;
  padding: 20px;
}

.test2 {
  padding-right: 10px;
}

[dir='rtl'] .test2 {
  padding-right: 0;
  padding-left: 10px;
}

使用 diff 方法,生成的代码将是下一个:

.test2 {
  padding-right: 0;
  padding-left: 10px;
}

现在 divLTR 中的 padding 将是 20px 10px 20px 20px,在 RTL 中将是 20px 0 20px 10px,因为当类 test2 被覆盖时,没有考虑到它可能与 test1 一起使用,具有相同的属性。在这种情况下的解决方案是提供被继承的属性:

.test1 {
  background: #666;
  color: #fff;
  padding: 20px;
}

.test2 {
  padding-left: 20px;
  padding-right: 10px;
}

所以,使用 override 方法,生成的代码将是:

.test1 {
  background: #666;
  color: #fff;
  padding: 20px;
}

.test2 {
  padding-left: 20px;
  padding-right: 10px;
}

[dir='rtl'] .test2 {
  padding-right: 20px;
  padding-left: 10px;
}

使用 diff 方法,生成的代码将是:

.test2 {
  padding-right: 20px;
  padding-left: 10px;
}

插件选项

所有选项都是可选的,如果省略了任何一个选项或者它们的类型或格式错误,将使用默认值

选项类型默认值描述
modeMode (string)Mode.combined生成最终 CSS 规则的模式
ltrPrefixstring or string[][dir="ltr"]左到右 CSS 规则中使用的前缀
rtlPrefixstring or string[][dir="rtl"]右到左 CSS 规则中使用的前缀
bothPrefixstring or string[][dir]创建一个新规则,当 ltr 或 rtl 规则的特异性覆盖其声明时,影响两个方向
prefixSelectorTransformerfunctionnull转换函数,以更控制选择器前缀逻辑
safeBothPrefixbooleanfalsebothPrefix 添加到可能受方向影响的声明中,以避免被特异性覆盖
ignorePrefixedRulesbooleantrue忽略已经被 ltrPrefix, rtlPrefix, 或 bothPrefix 中的一些前缀标记的规则
sourceSource (string)Source.ltr从哪个方向生成最终 CSS
processUrlsbooleanfalse使用字符串映射更改 URL 中的字符串
processRuleNamesbooleanfalse如果它们与 stringMap 中的任何条目匹配,则在方向变化时交换两个规则,即使它们不包含方向属性
processKeyFramesbooleanfalse翻转关键帧动画
processEnvbooleantrue当 processEnv 为 false 时,防止翻转代理定义的环境变量(safe-area-inset-leftsafe-area-inset-right
useCalcbooleanfalse如果它们使用 calc 表示长度单位,则翻转 background-position-xtransform-origin 属性
stringMapPluginStringMap[]查看下面用于替换声明的 URL 和匹配规则选择器名称的字符串映射数组,如果 processRuleNamestrue
greedybooleanfalse当 greedy 为 true 时,stringMap 的匹配不考虑单词边界
aliasesRecord<string, string>{}字符串映射,将一些声明视为其他声明
processDeclarationPluginsDeclarationPlugin[][]处理 CSS 声明时应用的插件

mode

mode 选项已在 使用组合模式输出, 使用覆盖模式输出, 和 使用差异模式输出 部分中解释。为了避免使用魔术字符串,该包暴露了一个具有这些值的对象,但无论如何都可以使用字符串值:

import postcss from 'postcss'
import postcssRTLCSS from 'postcss-change-dir'
import { Mode } from 'postcss-change-dir/options'

const input = '... css 代码 ...'
const optionsCombined = { mode: Mode.combined } // 这是默认值
const optionsOverride = { mode: Mode.override }
const optionsDiff = { mode: Mode.diff }

const outputCombined = postcss([postcssRTLCSS(optionsCombined)]).process(input)

const outputOverride = postcss([postcssRTLCSS(optionsOverride)]).process(input)

const outputDiff = postcss([postcssRTLCSS(optionsDiff)]).process(input)

ltrPrefix 和 rtlPrefix

这两个选项管理每个方向的前缀字符串。它们可以是字符串或字符串数组:

输入
.test1,
.test2 {
  left: 10px;
}

.test3,
.test4 {
  text-align: left;
}
使用字符串
const options = {
  ltrPrefix: '.ltr',
  rtlPrefix: '.rtl'
}
输出
.ltr .test1,
.ltr .test2 {
  left: 10px;
}

.rtl .test1,
.rtl .test2 {
  right: 10px;
}

.ltr .test3,
.ltr .test4 {
  text-align: left;
}

.rtl .test3,
.rtl .test4 {
  text-align: right;
}
使用字符串数组
const options = {
  ltrPrefix: ['[dir="ltr"]', '.ltr'],
  rtlPrefix: ['[dir="rtl"]', '.rtl']
}
输出
[dir='ltr'] .test1,
.ltr .test1,
[dir='ltr'] .test2,
.ltr .test2 {
  left: 10px;
}

[dir='rtl'] .test1,
.rtl .test1,
[dir='rtl'] .test2,
.rtl .test2 {
  right: 10px;
}

[dir='ltr'] .test3,
.ltr .test3,
[dir='ltr'] .test4,
.ltr .test4 {
  text-align: left;
}

[dir='rtl'] .test3,
.rtl .test3,
[dir='rtl'] .test4,
.rtl .test4 {
  text-align: right;
}

bothPrefix

这个前缀将在一些特定情况下使用,其中一个 ltr 或 rtl 规则将由于特异性覆盖主规则中位于的声明。考虑使用 processUrls 选项作为 true 的下一个示例:

.test1 {
  background: url('icons/ltr/arrow.png');
  background-size: 10px 20px;
  width: 10px;
}

生成的 CSS 将是:

.test1 {
  background-size: 10px 20px;
  width: 10px;
}

[dir='ltr'] .test1 {
  background: url('icons/ltr/arrow.png');
}

[dir='rtl'] .test1 {
  background: url('icons/rtl/arrow.png');
}

在之前的案例中,background-size 属性被 background 属性覆盖。即使我们改变规则的顺序,最后一组规则具有更高的特异性,因此它们将覆盖第一组规则。

为了解决这个问题,最后将使用 bothPrefix 参数创建另一个规则:

.test1 {
  width: 10px;
}

[dir='ltr'] .test1 {
  background: url('icons/ltr/arrow.png');
}

[dir='rtl'] .test1 {
  background: url('icons/rtl/arrow.png');
}

[dir] {
  background-size: 10px 20px;
}

不管怎样的方向,background-size 属性都受到尊重。


prefixSelectorTransformer

这个函数将用于转换选择器,并根据我们的意愿前缀它们。第一个参数将是使用的前缀,第二个是当前选择器:

!注意

  • 如果该函数不返回字符串,则将使用默认的前缀逻辑。
  • 如果使用这个函数,请确保 html, :root::view-transition 使用自定义前缀逻辑。您应该涵盖这些情况。
输入
.test1 {
  left: 10px;
  padding-right: 5px;
  padding-inline-end: 20px;
}

如果 prefixSelectorTransformer 没有发送(默认):

输出
[dir='ltr'] .test1 {
  left: 10px;
  padding-right: 5px;
}

[dir='rtl'] .test1 {
  right: 10px;
  padding-left: 5px;
}

[dir] .test1 {
  padding-inline-end: 20px;
}

设置 prefixSelectorTransformer 函数

const options = {
  prefixSelectorTransformer: function (prefix, selector) {
    if (prefix === '[dir]') {
      return `.container > ${prefix} > ${selector}`
    }
    return `${selector}${prefix}`
  }
}
输出
.test1[dir='ltr'] {
  left: 10px;
  padding-right: 5px;
}

.test1[dir='rtl'] {
  right: 10px;
  padding-left: 5px;
}

.container > [dir] > .test1 {
  padding-inline-end: 20px;
}

safeBothPrefix

这个选项将 boxPrefix 选项添加到可以翻转的声明中,不管它们是否在同一规则中被覆盖。这避免了它们被其他规则中包含的翻转声明的特异性覆盖。例如,让我们考虑一个 div 元素具有以下规则:

<div class="test1 test2">这是一个示例</div>
.test1 {
  color: #fff;
  padding: 4px 10px 4px 20px;
  width: 100%;
}

.test2 {
  padding: 0;
}

预期的结果是元素的 padding 变为 0,因为它被 test2 重置了。使用 safeBothPrefixfalse 时,生成的 CSS 将是:

.test1 {
  color: #fff;
  width: 100%;
}

[dir='ltr'] .test1 {
  padding: 4px 10px 4px 20px;
}

[dir='rtl'] .test1 {
  padding: 4px 20px 4px 10px;
}

.test2 {
  padding: 0;
}

结果是 test1padding 属性的特异性高于 test2 中的同一属性,所以如果同时应用两个规则,则不会被重置。让我们检查如果 safeBothPrefixtrue 的结果:

.test1 {
  color: #fff;
  width: 100%;
}

[dir='ltr'] .test1 {
  padding: 4px 10px 4px 20px;
}

[dir='rtl'] .test1 {
  padding: 4px 20px 4px 10px;
}

[dir] .test2 {
  padding: 0;
}

由于 test2 具有与 test1 相同的特异性水平,现在结果是如果同时使用两个规则,则 padding 将被重置。


ignorePrefixedRules

这个选项用于忽略已经被 ltrPrefix, rtlPrefix, 或 bothPrefix 中的一些前缀标记的规则:

输入
[dir='ltr'] test {
  left: 10px;
}

[dir='rtl'] test {
  right: 10px;
}
ignorePrefixedRules 为 true
const options = { ignorePrefixedRules: true } // 这是默认值
输出
[dir='ltr'] test {
  left: 10px;
}

[dir='rtl'] test {
  right: 10px;
}
ignorePrefixedRules 为 false
const options = { ignorePrefixedRules: false }
输出
[dir='ltr'] [dir='ltr'] test {
  left: 10px;
}

[dir='rtl'] [dir='ltr'] test {
  right: 10px;
}

[dir='ltr'] [dir='rtl'] test {
  right: 10px;
}

[dir='rtl'] [dir='rtl'] test {
  left: 10px;
}

source

这个选项管理转换是从 LTRRTL 还是反之。

输入
.test1,
.test2 {
  left: 10px;
}
使用 Source.ltr 在组合模式
import { Mode, Source } from 'postcss-change-dir/options'

const options = {
  mode: Mode.combined,
  source: Source.ltr // 这是默认值
}
输出
[dir='ltr'] .test1,
[dir='ltr'] .test2 {
  left: 10px;
}

[dir='rtl'] .test1,
[dir='rtl'] .test2 {
  right: 10px;
}
使用 Source.rtl 在覆盖模式
import { Mode, Source } from 'postcss-change-dir/options'

const options = {
  mode: Mode.override,
  source: Source.rtl
}
输出
.test1,
.test2 {
  left: 10px;
}

[dir='ltr'] .test1,
[dir='ltr'] .test2 {
  left: auto;
  right: 10px;
}

processUrls

这个选项管理是否应该翻转 URL 中的字符串,考虑到字符串映射:

输入
.test1,
.test2 {
  background-image: url('./folder/subfolder/icons/ltr/chevron-left.png');
  left: 10px;
}
processUrls 为 false
const options = { processUrls: false } // 这是默认值
输出
.test1,
.test2 {
  background-image: url('./folder/subfolder/icons/ltr/chevron-left.png');
}

[dir='ltr'] .test1,
[dir='ltr'] .test2 {
  left: 10px;
}

[dir='rtl'] .test1,
[dir='rtl'] .test2 {
  right: 10px;
}
processUrls 为 true
const options = { processUrls: true }
输出
[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;
}

processRuleNames

如果为 true,则在方向变化时,如果它们与 stringMap 中的任何条目匹配,则交换两个不包含方向属性的规则

!重要 这个选项不会前缀已经被处理过的规则,因为它们具有方向属性。

输入
.test1-ltr {
  color: #fff;
}

.test2-left::before {
  content: '\f007';
}

.test2-right::before {
  content: '\f010';
}
processRuleNames 为 true
const options = {
  processRuleNames: true
}
输出
/* 这个选择器将不会被处理,因为它没有对应的 */
.test1-ltr {
  color: #fff;
}

[dir='ltr'] .test2-left::before {
  content: '\f007';
}

[dir='rtl'] .test2-left::before {
  content: '\f010';
}

[dir='ltr'] .test2-right::before {
  content: '\f010';
}

[dir='rtl'] .test2-right::before {
  content: '\f007';
}

processKeyFrames

这个选项管理是否应该翻转 @keyframes 动画规则:

输入
.test1 {
  animation: 5s flip 1s ease-in-out;
  color: #fff;
}

@keyframes flip {
  from {
    transform: translateX(100px);
  }
  to {
    transform: translateX(0);
  }
}
processKeyFrames 为 false
const options = { processKeyFrames: false } // 这是默认值
输出
.test1 {
  animation: 5s flip 1s ease-in-out;
  color: #fff;
}

@keyframes flip {
  from {
    transform: translateX(100px);
  }
  to {
    transform: translateX(0);
  }
}
processKeyFrames 为 true
const options = { processKeyFrames: true }
输出
.test1 {
  color: #fff;
}

[dir='ltr'] .test1 {
  animation: 5s flip-ltr 1s ease-in-out;
}

[dir='rtl'] .test1 {
  animation: 5s flip-rtl 1s ease-in-out;
}

@keyframes flip-ltr {
  from {
    transform: translateX(100px);
  }
  to {
    transform: translateX(0);
  }
}

@keyframes flip-rtl {
  from {
    transform: translateX(-100px);
  }
  to {
    transform: translateX(0);
  }
}

processEnv

这个选项管理是否应该翻转代理定义的环境变量:

输入
body {
  padding: env(safe-area-inset-top, 10px) env(safe-area-inset-right, 20px) env(safe-area-inset-bottom, 30px) env(
      safe-area-inset-left,
      40px
    );
}

.test1 {
  margin-right: env(safe-area-inset-right, 10px);
  margin-left: env(safe-area-inset-left, 20px);
}
processEnv 为 true
const options = { processEnv: true } // 这是默认值
输出
[dir='ltr'] body {
  padding: env(safe-area-inset-top, 10px) env(safe-area-inset-right, 20px) env(safe-area-inset-bottom, 30px) env(
      safe-area-inset-left,
      40px
    );
}

[dir='rtl'] body {
  padding: env(safe-area-inset-top, 10px) env(safe-area-inset-right, 40px) env(safe-area-inset-bottom, 30px) env(
      safe-area-inset-left,
      20px
    );
}

[dir='ltr'] .test1 {
  margin-right: env(safe-area-inset-right, 10px);
  margin-left: env(safe-area-inset-left, 20px);
}

[dir='rtl'] .test1 {
  margin-left: env(safe-area-inset-left, 10px);
  margin-right: env(safe-area-inset-right, 20px);
}
processEnv 为 false
const options = { processEnv: false }
输出
[dir='ltr'] body {
  padding: env(safe-area-inset-top, 10px) env(safe-area-inset-right, 20px) env(safe-area-inset-bottom, 30px) env(
      safe-area-inset-left,
      40px
    );
}

[dir='rtl'] body {
  padding: env(safe-area-inset-top, 10px) env(safe-area-inset-left, 40px) env(safe-area-inset-bottom, 30px) env(
      safe-area-inset-right,
      20px
    );
}

[dir='ltr'] .test1 {
  margin-right: env(safe-area-inset-right, 10px);
  margin-left: env(safe-area-inset-left, 20px);
}

[dir='rtl'] .test1 {
  margin-left: env(safe-area-inset-right, 10px);
  margin-right: env(safe-area-inset-left, 20px);
}

useCalc

当这个选项启用时,如果它们使用 calc 表示长度单位,则翻转 background-position-xtransform-origin 属性:

输入
.test {
  background-image: url('./folder/subfolder/icons/ltr/chevron-left.png');
  background-position-x: 5px;
  left: 10px;
  transform-origin: 10px 20px;
  transform: scale(0.5, 0.5);
}
useCalc 为 false
const options = { useCalc: false } // 这是默认值
输出
.test {
  background-image: url('./folder/subfolder/icons/ltr/chevron-left.png');
  background-position-x: 5px;
  transform-origin: 10px 20px;
  transform: scale(0.5, 0.5);
}

[dir='ltr'] .test {
  left: 10px;
}

[dir='rtl'] .test {
  right: 10px;
}
useCalc 为 true
const options = { useCalc: true }
输出
.test {
  background-image: url('./folder/subfolder/icons/ltr/chevron-left.png');
  transform: scale(0.5, 0.5);
}

[dir='ltr'] .test {
  background-position-x: 5px;
  left: 10px;
  transform-origin: 10px 20px;
}

[dir='rtl'] .test {
  background-position-x: calc(100% - 5px);
  right: 10px;
  transform-origin: calc(100% - 10px) 20px;
}

stringMap

一个字符串映射数组,用于替换声明的 URL 和匹配规则选择器名称,如果 processRuleNames 选项为 true。名称参数是可选的,但如果您想覆盖任何默认字符串映射,只需使用相同的名称添加您自己的。

// 这是默认的字符串映射对象
const options = {
  stringMap: [
    {
      name: 'left-right',
      search: ['left', 'Left', 'LEFT'],
      replace: ['right', 'Right', 'RIGHT']
    },
    {
      name: 'ltr-rtl',
      search: ['ltr', 'Ltr', 'LTR'],
      replace: ['rtl', 'Rtl', 'RTL']
    }
  ]
}

greedy

greedytrue 时,stringMap 的匹配不考虑单词边界。

输入
.test1 {
  background: url('icon-left.png');
}

.test2 {
  background: url('icon-ultra.png');
}
greedy 为 false
const options = {
  processUrls: true,
  greedy: false // 这是默认值
}
输出
[dir='ltr'] .test1 {
  background: url('icon-left.png');
}

[dir='rtl'] .test1 {
  background: url('icon-right.png');
}

.test2 {
  background: url('icon-ultra.png');
}
greedy 为 true
const options = {
  processUrls: true,
  greedy: true
}
输出
[dir='ltr'] .test1 {
  background: url('icon-left.png');
}

[dir='rtl'] .test1 {
  background: url('icon-right.png');
}

[dir='ltr'] .test2 {
  background: url('icon-ultra.png');
}

[dir='rtl'] .test2 {
  background: url('icon-urtla.png');
}

aliases

这个属性由字符串映射组成,将一些声明视为其他声明,这对于翻转 CSS 变量 的值非常有用。

输入
:root {
  --my-padding: 1rem 1rem 1.5rem 1.5rem;
}

.test {
  padding: var(--my-padding);
}
没有别名字符串映射(默认)
输出
:root {
  --my-padding: 1rem 1rem 1.5rem 1.5rem;
}

.test {
  padding: var(--my-padding);
}
设置别名字符串映射
const options = {
  aliases: {
    '--my-padding': 'padding'
  }
}
输出
[dir='ltr']:root {
  --my-padding: 1rem 1rem 1.5rem 1.5rem;
}

[dir='rtl']:root {
  --my-padding: 1rem 1.5rem 1.5rem 1rem;
}

.test {
  padding: var(--my-padding);
}

processDeclarationPlugins

processDeclarationPlugins 选项的目的是处理声明,以扩展或覆盖 RTLCSS 功能。例如,我们可以避免自动翻转 background-position

输入
.test {
  background-position: 0 100%;
}
0 转换为 100%(默认)
输出
.test {
  background-position: 100% 100%;
}
设置插件以避免翻转
const options = {
  processDeclarationPlugins: [
    {
      name: 'avoid-flipping-background',
      priority: 99, // 高于核心 RTLCSS 插件的优先值 100
      processors: [
        {
          expr: /(background|object)(-position(-x)?|-image)?$/i,
          action: (prop, value) => ({ prop, value })
        }
      ]
    }
  ]
}
输出
.test {
  background-position: 0 100%;
}

控制指令

控制指令放置在规则或声明之间。它们可以针对单个节点或一组节点。

!重要 块指令(以 begin 开始和以 end 结束的那些)应该放置在规则外部以应用到多个规则,或放置在规则内部以应用到多个声明。您不应将指令的开始放置在规则外部,将结束放置在内部(或反之),否则将得到不期望的结果。

指令描述
/*rtl:ignore*/忽略处理以下规则或声明
/*rtl:begin:ignore*/开始忽略块
/*rtl:end:ignore*/结束忽略块
/*rtl:freeze*/在当前方向冻结规则或声明
/*rtl:begin:freeze*/开始冻结块
/*rtl:end:freeze*/结束冻结块
/*rtl:urls*/此指令在下一个声明或下一个规则的声明中将 processUrls 选项设置为 true,不管全局 processUrls 选项的值如何
/*rtl:begin:urls*/开始 processUrls
/*rtl:end:urls*/结束 processUrls
/*rtl:rules*/此指令在下一个规则中将 processRuleNames 选项设置为 true,不管全局 processRuleNames 选项的值如何
/*rtl:begin:rules*/开始 processRuleNames
/*rtl:end:rules*/结束 processRuleNames
/*rtl:source:{source}*/设置规则或声明的源,不管 source 属性的值如何
/*rtl:begin:source:{source}*/开始源块
/*rtl:end:source*/结束源块
/*rtl:raw:{CSS}*/解析 CSS 参数并插入到其位置。根据 source 参数,解析的 CSS 将被视为 rtlltr

/*rtl:ignore*/

这个指令忽略了对以下规则或声明的处理。在下一个块中,整个声明将被忽略。

输入
/*rtl:ignore*/
.test1,
.test2 {
  text-align: left;
  left: 10px;
}
输出
.test1,
.test2 {
  text-align: left;
  left: 10px;
}

在下一个块中,只有 left 属性将被忽略:

输入
.test3,
.test4 {
  text-align: left;
  /*rtl:ignore*/
  left: 10px;
}
输出
.test3,
.test4 {
  left: 10px;
}

[dir='ltr'] .test3,
[dir='ltr'] .test4 {
  text-align: left;
}

[dir='rtl'] .test3,
[dir='rtl'] .test4 {
  text-align: right;
}

/*rtl:begin:ignore*/ 和 /*rtl:end:ignore*/

这两个指令应该一起使用,它们将提供忽略规则或声明的开始和结束。

!注意 插入在这些块之间的指令将被忽略,并保留在最终输出中。

忽略多个规则:

输入
/*rtl:begin:ignore*/
.test1,
.test2 {
  left: 10px;
  text-align: left;
}

.test3 {
  padding: 1px 2px 3px 4px;
}
/*rtl:end:ignore*/
输出
.test1,
.test2 {
  left: 10px;
  text-align: left;
}

.test3 {
  padding: 1px 2px 3px 4px;
}

忽略多个声明:

输入
.test1,
.test2 {
  left: 10px;
  /*rtl:begin:ignore*/
  margin-left: 4em;
  padding: 1px 2px 3px 4px;
  /*rtl:end:ignore*/
  text-align: left;
}
输出
.test1,
.test2 {
  margin-left: 4em;
  padding: 1px 2px 3px 4px;
}

[dir='ltr'] .test1,
[dir='ltr'] .test2 {
  left: 10px;
  text-align: left;
}

[dir='rtl'] .test1,
[dir='rtl'] .test2 {
  right: 10px;
  text-align: right;
}

/*rtl:freeze*/

!重要 此指令仅在 combined 模式下工作。如果您在 overridediff 模式下使用它,它将被忽略。

这个指令在当前方向冻结规则或声明,但对 counterpart direction 如果有可翻转声明则不采取任何操作。当用于规则时,即使它不包含可翻转声明,也会在当前方向冻结它。当它用于声明时,即使它不可翻转,也会在当前方向冻结该声明。

输入
/*rtl:freeze*/
.test1,
.test2 {
  color: red;
  text-align: left;
  left: 10px;
}

.test3 {
  /*rtl:freeze*/
  text-align: center;
  /*rtl:freeze*/
  padding: 10px 20px 30px 40px;
  margin: 1px 2px 3px 4px;
}
输出
[dir='ltr'] .test1,
[dir='ltr'] .test2 {
  color: red;
  text-align: left;
  left: 10px;
}

[dir='ltr'] .test3 {
  text-align: center;
  padding: 10px 40px 30px 20px;
  margin: 1px 4px 3px 2px;
}

[dir='rtl'] .test3 {
  margin: 1px 4px 3px 2px;
}

/*rtl:begin:freeze*/ 和 /*rtl:end:freeze*/

!重要 此指令仅在 combined 模式下工作。如果您在 overridediff 模式下使用它,它将被忽略。

这两个指令应该一起使用,它们将提供冻结规则或声明的开始和结束。这些块之间的规则或声明,将即使没有涉及可翻转声明,也会在当前方向冻结。

冻结多个规则:

输入
/*rtl:begin:freeze*/
.test1,
.test2 {
  color: #fff;
  left: 10px;
  text-align: left;
}

.test3 {
  padding: 1px 2px 3px 4px;
}
/*rtl:end:freeze*/
输出
[dir='ltr'] .test1,
[dir='ltr'] .test2 {
  color: #fff;
  left: 10px;
  text-align: left;
}

[dir='ltr'] .test3 {
  padding: 1px 2px 3px 4px;
}

冻结多个声明:

输入
.test1,
.test2 {
  color: red;
  left: 10px;
  /*rtl:begin:freeze*/
  margin-left: 4em;
  padding: 1px 2px 3px 4px;
  /*rtl:end:freeze*/
  text-align: left;
}
输出
.test1,
.test2 {
  color: red;
}

[dir='ltr'] .test1,
[dir='ltr'] .test2 {
  left: 10px;
  margin-left: 4em;
  padding: 1px 2px 3px 4px;
  text-align: left;
}

[dir='rtl'] .test1,
[dir='rtl'] .test2 {
  right: 10px;
  text-align: right;
}

/*rtl:urls*/

这个指令在下一个声明或下一个规则的声明中将 processUrls 选项设置为 true,不管全局 processUrls 选项的值如何:

输入
/*rtl:urls*/
.test1 {
  background-image: url('/buttons/button-ltr.png');
}

.test2 {
  /*rtl:urls*/
  background-image: url('/icons/icon-left.png');
}
输出
[dir='ltr'] .test1 {
  background-image: url('/buttons/button-ltr.png');
}

[dir='rtl'] .test1 {
  background-image: url('/buttons/button-rtl.png');
}

[dir='ltr'] .test2 {
  background-image: url('/icons/icon-left.png');
}

[dir='rtl'] .test2 {
  background-image: url('/icons/icon-right.png');
}

/*rtl:begin:urls*/ 和 /*rtl:end:urls*/

这两个指令应该一起使用,它们将提供 processUrls 块的开始和结束。

输入
/*rtl:begin:urls*/
.test1 {
  background-image: url('/buttons/button-ltr.png');
}

.test2 {
  background-image: url('/icons/icon-left.png');
}
/*rtl:end:urls*/

.test3 {
  /*rtl:begin:urls*/
  background-image: url('/images/background-left.png');
  cursor: url('/images/cursor-ltr.png');
  /*rtl:end:urls*/
}
输出
[dir='ltr'] .test1 {
  background-image: url('/buttons/button-ltr.png');
}

[dir='rtl'] .test1 {
  background-image: url('/buttons/button-rtl.png');
}

[dir='ltr'] .test2 {
  background-image: url('/icons/icon-left.png');
}

[dir='rtl'] .test2 {
  background-image: url('/icons/icon-right.png');
}

[dir='ltr'] .test3 {
  background-image: url('/images/background-left.png');
  cursor: url('/images/cursor-ltr.png');
}

[dir='rtl'] .test3 {
  background-image: url('/images/background-right.png');
  cursor: url('/images/cursor-rtl.png');
}

/*rtl:rules*/

这个指令在下一个规则中将 processRuleNames 选项设置为 true,不管全局 processRuleNames 选项的值如何:

输入
/*rtl:rules*/
.test1-ltr {
  background-image: url('/images/test1-l.png');
}

/*rtl:rules*/
.test1-rtl {
  background-image: url('/images/test1-r.png');
}

/*rtl:rules*/
.test2-left::before {
  content: '\f007';
}

.test2-right::before {
  content: '\f010';
}
输出
[dir='ltr'] .test1-ltr {
  background-image: url('/images/test1-l.png');
}

[dir='rtl'] .test1-ltr {
  background-image: url('/images/test1-r.png');
}

[dir='ltr'] .test1-rtl {
  background-image: url('/images/test1-r.png');
}

[dir='rtl'] .test1-rtl {
  background-image: url('/images/test1-l.png');
}

/* 这些选择器将不会被处理,因为只有其中一个有 rtl:rules 指令 */
.test2-left::before {
  content: '\f007';
}

.test2-right::before {
  content: '\f010';
}

/*rtl:begin:rules*/ 和 /*rtl:end:rules*/

这两个指令应该一起使用,它们将提供 processRuleNames 块的开始和结束。

输入
.test1-ltr {
  background-image: url('/images/test1-l.png');
}

.test1-rtl {
  background-image: url('/images/test1-r.png');
}

/*rtl:begin:rules*/
.test2-left::before {
  content: '\f007';
}

.test2-right::before {
  content: '\f010';
}
/*rtl:end:rules*/
输出
.test1-ltr {
  background-image: url('/images/test1-l.png');
}

.test1-rtl {
  background-image: url('/images/test1-r.png');
}

[dir='ltr'] .test2-left::before {
  content: '\f007';
}

[dir='rtl'] .test2-left::before {
  content: '\f010';
}

[dir='ltr'] .test2-right::before {
  content: '\f010';
}

[dir='rtl'] .test2-right::before {
  content: '\f007';
}

/*rtl:source:{source}*/

这个指令设置规则或指令的源,忽略 source 属性的值:

输入
/*rtl:source:rtl*/
.test {
  color: #fff;
  border-left: 1px solid #666;
  padding: 10px 5px 10px 20px;
  text-align: left;
  width: 100%;
}
输出
.test {
  color: #fff;
  width: 100%;
}

[dir='ltr'] .test {
  border-right: 1px solid #666;
  padding: 10px 20px 10px 5px;
  text-align: right;
}

[dir='rtl'] .test {
  border-left: 1px solid #666;
  padding: 10px 5px 10px 20px;
  text-align: left;
}

/*rtl:begin:source:{source}*/ 和 /*rtl:end:source*/

这两个指令应该一起使用,它们将提供规则或声明的源块的开始和结束:

输入
.test {
  color: #fff;
  border-left: 1px solid #666;
  /*rtl:begin:source:rtl*/
  padding: 10px 5px 10px 20px;
  text-align: left;
  /*rtl:end:source*/
  width: 100%;
}
输出
.test {
  color: #fff;
  width: 100%;
}

[dir='ltr'] .test {
  border-left: 1px solid #666;
  padding: 10px 20px 10px 5px;
  text-align: right;
}

[dir='rtl'] .test {
  border-right: 1px solid #666;
  padding: 10px 5px 10px 20px;
  text-align: left;
}

/*rtl:raw:{CSS}*/

解析 CSS 参数并插入到其位置。根据 source 参数,解析的 CSS 将被视为 rtlltr

输入
.test1 {
  color: #efefef;
  left: 10px;
  /*rtl:raw:
    height: 50px;
    width: 100px;*/
}

/*rtl:raw:.test2 {
    color: #EFEFEF;
    left: 10px;
    width: 100%;
}

.test3 {
    transform: translate(10px, 20px);
}
*/
输出
.test1 {
  color: #efefef;
}

[dir='ltr'] .test1 {
  left: 10px;
}

[dir='rtl'] .test1 {
  right: 10px;
  height: 50px;
  width: 100px;
}

[dir='rtl'] .test2 {
  color: #efefef;
  left: 10px;
  width: 100%;
}

[dir='rtl'] .test3 {
  transform: translate(10px, 20px);
}

值指令

值指令放置在声明值内的任何位置。它们针对包含的声明节点。

指令描述
/*rtl:ignore*/忽略处理当前声明
/*rtl:append{value}*/{value} 追加到声明值的末尾
/*rtl:insert:{value}*/{value} 插入到指令所在位置
/*rtl:prepend:{value}*/{value} 预置到声明值的开头
/*rtl:{value}*/{value} 替换声明值

/*rtl:ignore*/

这个指令忽略了对当前声明的处理:

输入
.test1,
.test2 {
  text-align: left /*rtl:ignore*/;
  left: 10px;
}
输出
.test1,
.test2 {
  text-align: left;
}

[dir='ltr'] .test1,
[dir='ltr'] .test2 {
  left: 10px;
}

[dir='rtl'] .test1,
[dir='rtl'] .test2 {
  right: 10px;
}

/*rtl:append{value}*/

这个指令将 {value} 追加到声明值的末尾:

输入
.test1,
.test2 {
  padding: 10px /*rtl:append20px*/;
  left: 10px;
}
输出
[dir='ltr'] .test1,
[dir='ltr'] .test2 {
  padding: 10px;
  left: 10px;
}

[dir='rtl'] .test1,
[dir='rtl'] .test2 {
  padding: 10px 20px;
  right: 10px;
}

/*rtl:insert:{value}*/

这个指令将 {value} 插入到指令所在位置的声明值内:

输入
.test1,
.test2 {
  padding: 10px /*rtl:insert 20px*/ 5px;
  left: 10px;
}
输出
[dir='ltr'] .test1,
[dir='ltr'] .test2 {
  padding: 10px 5px;
  left: 10px;
}

[dir='rtl'] .test1,
[dir='rtl'] .test2 {
  padding: 10px 20px 5px;
  right: 10px;
}

/*rtl:prepend:{value}*/

这个指令将 {value} 预置到声明值的开头:

输入
.test1,
.test2 {
  font-family:
    Arial,
    Helvetica /*rtl:prepend:"Droid Arabic Kufi", */;
  left: 10px;
}
输出
[dir='ltr'] .test1,
[dir='ltr'] .test2 {
  font-family: Arial, Helvetica;
  left: 10px;
}

[dir='rtl'] .test1,
[dir='rtl'] .test2 {
  font-family: 'Droid Arabic Kufi', Arial, Helvetica;
  right: 10px;
}

/*rtl:{value}*/

这个指令用 {value} 替换声明值:

输入
.test1,
.test2 {
  font-family:
    Arial,
    Helvetica /*rtl:"Droid Arabic Kufi"*/;
  left: 10px;
}
输出
[dir='ltr'] .test1,
[dir='ltr'] .test2 {
  font-family: Arial, Helvetica;
  left: 10px;
}

[dir='rtl'] .test1,
[dir='rtl'] .test2 {
  font-family: 'Droid Arabic Kufi';
  right: 10px;
}

如果您不使用 PostCSS,请根据官方文档添加它并在此设置中配置此插件。