数週間前、CSS 変数 - CSS 変数のドラフトが W3C 公式にリリースされました。これは、現在 Chrome Canary に含まれています。
Chrome ブラウザ エンジニアの Addy Osmani が初めてこのニュースを Twitter に投稿したとき、何万人もの人々から否定、敵意、疑惑の声が上がりました。 。この機能は非常に興味深いものなので、私にとってはむしろ驚きです。
ざっと調べてみたところ、99% の人が次の 2 点以外について何も不満を抱いていないことがわかりました:
私もこの文法に嫌悪感を持っていることを認めますが、より重要なことは理解することです。文法とは単なる気まぐれな選択ではないということ。 CSS ワーキング グループでは構文の長さについて長い間議論され、CSS 構文の互換性が将来追加される他の言語と競合しないように考慮すべき点がいくつか抽出されました。
CSS プリプロセッサは優れたツールですが、その変数は静的であり、構文的にスコープが設定されています。一方、ネイティブ CSS 変数はまったく異なるタイプの変数です。これらは動的であるため、そのスコープは DOM であり、実際には変数と呼ぶべきかどうかも混乱します。これにより、まったく異なる機能の問題を解決する機会も得られます。
この記事では、CSS プリプロセッサを使用しない CSS カスタム プロパティの機能のいくつかについて説明します。もちろん、いくつかの新しい設計パターンも示し、カスタム機能を有効にします。記事の最後で説明しますが、将来的には前処理変数とカスタム変数が一緒に使用され、相互に完全に補完される可能性が高くなります。
注: この記事は CSS カスタム プロパティの紹介ではありません。CSS カスタム プロパティについて聞いたことがなく、その仕組みがよくわからない場合は、ここを参照してください。
続行する前に、私は CSS プリプロセッサがとても気に入っており、すべてのプロジェクトで CSS プリプロセッサを使用していることを強調したいと思います。最終出力がオリジナルの CSS であることがわかっていても、このプリプロセッサは非常に注目すべきことを行っています。このアーティファクトの時代を感じることができます。
どんなツールにも制限があります。見た目がクールであれば、特に新規ユーザーはその制限を無視します。
メディア クエリの前処理によって制限されている可能性があり、最も一般的な初心者は変数の定義や @extend の使用について文句を言うことができません
<code>$gutter: 1em; @media (min-width: 30em) { $gutter: 2em;} .Container { padding: $gutter;}</code>
上記のコードをコンパイルすると、次の結果が得られます:
.Container { padding: 1em;}
ご覧のとおり、メディア クエリは破棄され、変数の割り当ては無視されます。
理論的には、Sass は条件変数の宣言を担当しますが、それを行うことも困難であり、すべての Permutation を列挙すると、CSS の最終的なサイズが指数関数的に増加します。
変数を使用するときは必ずスコープの範囲が決まりますが、この変数はグローバルにする必要がありますか?ファイル/モジュールにする必要がありますか?それともブロックスコープでしょうか?
CSS は最終的には HTML のスタイルを設定するためのもので、変数のスコープである DOM 要素という別の便利な方法があることが判明しましたが、プリプロセッサはブラウザ内で実行されず、タグは決して表示されません
クラス user-setting-large-text を 要素に追加しようとした Web サイトを参照してください。
このクラスが設定されると、より大きな $font-size の変数割り当てが使用されます:
$font-size: 1em; .user-setting-large-text { $font-size: 1.5em;} body { font-size: $font-size;}
ただし、上記のメディア クエリの例のように、 Sass は単に変数の割り当てを無視します。つまり、これは不可能です。彼の出力:
body { font-size: 1em;}
継承はカスケードの一部ですが、それでも言及する必要があります。この機能を使いたいのですが、使えません。
色のスタイルに基づいて変更が加えられた Dom 要素がある場合、その親要素でそれを使用できます。
.alert { background-color: lightyellow; }.alert.info { background-color: lightblue; }.alert.error { background-color: orangered; } .alert button { border-color: darken(background-color, 25%);}
上記の Sass コードは無効ですが、それが何を達成しようとしているのかは理解できるはずです。
最終的には背景色プロパティに sass の darken 関数を使用しようとしますが、ボタン要素は親クラス要素 .alert を継承します。クラス情報またはエラーがアラートに追加されている場合 (または背景色またはユーザー スタイルが JavaScript によって設定されている場合)、ボタン要素はこれら 2 つの色を取得することを期待します。
プリプロセッサが DOM 構造を認識していないため、これは現時点では Sass では実行できませんが、このタイプのものがどのような用途に使用されるのかを理解したいと考えています。
特定のユースケースについて話します。これは、継承された DOM プロパティのアクセシビリティに対して color 関数を実行する理由でもあります。たとえば、テキストが常に読みやすく、背景色とのコントラストが適切であることを確認します。カスタム プロパティと新しい CSS Color 関数を使用すると、これが間もなく可能になります。
这是一个明显呈下降趋势的预处理器,如果你用PostCSS 建立一个网站,你想使用第三方组件,不好意思,你只有通过Sass的 themeable
与第三方分享预处理器变量在不同的工具集成或第三方托管的CND样式与都非常困难(至少不容易)
本地CSS自定义属性将与任何CSS预处理或者原CSS正好相反。
你可能已经猜到了,我上面列出的适用于CSS 自定义属性没有任何限制,但也许更重要的不是说他们不适用,而是为什么他们不用。
CSS自定义属性就像常规的CSS属性一样,他们的操作方式完全相同
像普通的CSS属性,自定义属性是动态的,他们可以在运行时修改,也可以在媒体查询时通过更改DOM添加一个新类,同时也可以指派内联元素和一个常规CSS里申明选择器。还可以通过正常的cascade规则或者使用JavaScript覆盖。最主要的是,他们的可以继承的,所以当他们应用到DOM元素的时候,他们的子元素也会继承属性。
为了更简洁,预处理器变量是语法作用域和编译后静态。自定义属性作用域是DOM,他们都很灵活。
如果你仍然不确定自定义属性可以做到这一点,而预处理器不行,我这里给一些例子。
不论真假,有大量非常好的例子我都很想展示,但为了不让这篇文章太丑,我选了两个。
我选择这些例子不仅仅因为它们的理论,它们也是我们过去实际面临的挑战,我依然记得试图用预处理器,但这是不可能的。现在好了,直接自定义属性走起。
很多网站在项目布局使用“ gap ”和“ gutter ” 定义默认间距和填充页面各个部分,很多时候,你想要这个“ gutter ”的值根据浏览器窗口的大小而不同。在大屏幕上你想要每一项之间有足够的空间,但小屏幕又负担不起那么大的空间,所以“ gutter ”的值要较小。
正如我上面提到的,在媒体查询里面Sass 不能正常运行,所以你必须每个单独处理。
下面的例子定义了变量 $gutterSm , $gutterMd 和 $gutterLg ,然后给每个变量申明一个单独的规则:
/* Declares three gutter values, one for each breakpoint */ $gutterSm: 1em;$gutterMd: 2em;$gutterLg: 3em; /* Base styles for small screens, using $gutterSm. */ .Container { margin: 0 auto; max-width: 60em; padding: $gutterSm;}.Grid { display: flex; margin: -$gutterSm 0 0 -$gutterSm;}.Grid-cell { flex: 1; padding: $gutterSm 0 0 $gutterSm;} /* Override styles for medium screens, using $gutterMd. */ @media (min-width: 30em) { .Container { padding: $gutterMd; } .Grid { margin: -$gutterMd 0 0 -$gutterMd; } .Grid-cell { padding: $gutterMd 0 0 $gutterMd; }} /* Override styles for large screens, using $gutterLg. */ @media (min-width: 48em) { .Container { padding: $gutterLg; } .Grid { margin: -$gutterLg 0 0 -$gutterLg; } .Grid-cell { padding: $gutterLg 0 0 $gutterLg; }}
使用自定义属性来完成相同的东西,你只需要定义样式即可。你可以使用一个 gutter 属性,然后随着媒体查询的变化,更新 gutter 的值,它就会做出相应的变化。
:root { --gutter: 1.5em; } @media (min-width: 30em) { :root { --gutter: 2em; }}@media (min-width: 48em) { :root { --gutter: 3em; }} /* * Styles only need to be defined once because * the custom property values automatically update. */ .Container { margin: 0 auto; max-width: 60em; padding: var(--gutter);}.Grid { --gutterNegative: calc(-1 * var(--gutter)); display: flex; margin-left: var(--gutterNegative); margin-top: var(--gutterNegative);}.Grid-cell { flex: 1; margin-left: var(--gutter); margin-top: var(--gutter);}
虽然有额外增加的自定义属性语法,但是相比冗长的代码完成同样的事明显好很多。这里只考虑了三个变量,如果变量越多,这将节省更多的代码。
下面的演示使用的是上面的代码自动构建的一个基本的网站布局,gutter的值跟随窗口的变化而变化,浏览器的支持自定义属性的话,效果屌屌的!
View the demo on CodePen: editor view / full page
语境样式(样式元素根据它出现在Dom)在CSS里是一个有争议的话题。 一方面,它是最受人尊敬的CSS开发者警告,另一方面,大多数人每天都还要用它。
Harry Roberts 最近写了 这篇 文章以及他对此的看法:
If you need to change the cosmetics of a UI component based on where it is placed, your design system is failing…Things should be designed to be ignorant; things should be designed so that we always just have “this component” and not “this component when inside…
当我站在Harry这一边,我认为大多数人走捷径这种情况可能表面一个更大的问题:CSS 表现能力是有限的,大部分人不满意当前的“最佳实践”。
下面例子显示了大部分人在CSS使用语境样式方法,使用子代选择器
<code><span class="comment">/* Regular button styles. */</span><span class="variable">.Button</span> { } <span class="comment">/* Button styles that are different when inside the header. */</span><span class="variable">.Header</span> <span class="variable">.Button</span> { }</code>
这种方法有很多问题(在我的文章有 解释 ),这种模式一个代码味道,它违反了 open/closed 软件开发原则;修改了一个封闭组件的实现细节
软件体 (类, 模块, 函数等) 扩展开放, 对修改关闭。
自定义属性的改变范围式定义组件是一个有趣的方式,用自定义属性,我们可以在第一次就写一个实际上是开放扩展的组件,这里有一个例子:
.Button { background: var(--Button-backgroundColor, #eee); border: 1px solid var(--Button-borderColor, #333); color: var(--Button-color, #333); /* ... */} .Header { --Button-backgroundColor: purple; --Button-borderColor: transparent; --Button-color: white;}
这和子选择器之间的区别很微妙而且很重要。
当使用子选择器我们宣传在页眉按钮会这样,这样不同的按钮如何定义自己,这样的声明是独裁(借Harry’s 的词),很难撤销例外的情况,页眉的一个按钮不需要这样的方式。
另外,自定义属性,按钮组件仍是没有语境且不能完全与header 组件解耦,
按钮组件简单的说申明:无论它们现状如何,我要自己的风格基于这些自定义属性;
header 组件:我要设置这些属性值,由我的子代来确定和如何使用它们。
主要的区别是,该扩展由按钮组件选择,并轻易消除例外情况。
下面的演示说明了语境样式的链接和按钮在网站的标题及内容区
在CodePen查看demo: editor view / full page
如果像 .promo 的组件加到header,然后buttons又加到 .promo 里面,使其看起来像一个正常按钮,而不是标题按钮。
如果你用子代选择器,那你将要给header buttons写一大串样式,而且还不能影响promo buttons,混乱,容易出错,而且容易失控的数量会增加:
/* Regular button styles. */.Button { } /* Button styles that are different when inside the header. */.Header .Button { } /* Undo button styles in the header that are also in promo. */.Header .Promo .Button { }
使用自定义属性,你可以简单的更新任何你想要的新按钮属性,或重置他们回默认样式,无视这些例外,改变的方式总是相同的。
.Promo { --Button-backgroundColor: initial; --Button-borderColor: initial; --Button-color: initial;}
当我第一次探索自定义属性语境样式的时候,我很怀疑自己。像前面说的,我倾向于喜欢组件自己定义自己的变化,而不是任何属性都继承自父元素。
但是有一件事,动摇了我在CSS自定义属性的观点,那就是React的 props 的
React的 props 依然是动态的,DOM-scoped variables,他们继承,允许组件上下文关联,在React,父组件将数据传递给子组件,然后子组件定义 props ,他们愿意接受和使用它们。这种建筑模型通常被称为one-way data flow。
尽管自定义组件是全新的未测试的领域,我认为React model 给了成功的信心,一个复杂的系统可以建立在属性继承——此外,DOM-scoped variables 是一个非常有用的设计模式。
CSS 自定义属性继承默认,在某些情况下,这导致组件的样式可能没有达到他们的预期。
在文章上一节中,我提到可以重置单个属性,这可以防止未知值被应用到元素的子元素:
.MyComponent { --propertyName: initial;}
尽管这不是规范的一部分,——正在讨论属性附[2],这个可以用来重置所有自定义属性,如果你想白名单几个属性,你可以将他们单独继承,其他的正常即可:
.MyComponent { /* Resets all custom properties. */ --: initial; /* Whitelists these individual custom properties */ --someProperty: inherit; --someOtherProperty: inherit;}
如果你一直关注自定义属性,那你可能已经注意到本身带有components-specific前缀的组件,如 --Button-backgroundColor.
与CSS 大多数名字一样,自定义属性是全局,很是有可能将正在使用命名与其他开发团队的名称产生冲突。
有一个简单的方法可以避免这个问题,就是坚持命名约定,我现在团队就是这么做的。
对于更复杂的项目,你可以考虑像 CSS模块 localifies所有全局名称,而且他们最近也 表示有兴趣 支持自定义属性。
如果你在阅读这篇文章之前,不熟悉CSS 自定义属性,我希望你能给他一个机会。如果你还在怀疑他的必要性,希望我能改变你的想法。
我敢肯定,自定义属性能给CSS带来一系列的强大的功能和面貌,它还有更多的优势等待我们去发现。
自定义属性preprocessor 变量是无可替代的。尽管如此,preprocessor variables 仍然是许多情况下的不二选择。正因如此,我坚信未来很多网站都会结合使用二者。自定义属性为动态主题和预处理器变量静态模板。
我不认为这是二选一的情况,让他们相互竞争,就像对手一样伤害每一个人。
特别感谢 Addy Osmani 和 Matt Gaunt 审查文章 ,Shane Stephens并及时修复了一些bug才能使demo正常运行,再次感谢。
脚注:
1.你可以启用chrome 的”Experimental Web Platform Features”功能,方法是:地址输入 about:flags 然后搜索“Experimental Web Platform Features”,然后点击“开启”按钮
2.使用——属性(如定制相关样式元素)是Atkins 在 github comment 提到的,此外,给www-style 发送 建议邮件 ,也会很快得到处理的。
本文原文地址: http://philipwalton.com/articles/why-im-excited-about-native-css-variables/
出典: http://isux.tencent.com/why-im-excited-about-native-css-variables.html