(大神部落)我为css变量狂

2016年05月26日 源自:大神部落

几个星期前,CSS Variables ——CSS 变量草案发布在了W3C官方 ,更准确的应该叫CSS 自定义属性,目前在Chrome Canary 版里面已经支持,开启该功能见附[1]

当Chrome浏览器工程师Addy Osmani第一时间把这消息发布在twitter后,遭到了数万人的否定敌视怀疑。于我而言,更多的感到是一个惊喜,因为这个功能实在让人太兴奋了。

快速的扫了一遍之后,发现99%人抱怨的无外乎这两点:

  • 语法太丑和不够简洁

  • Sass 、Less早就有这些玩意了,不太care

虽然我承认我也对这语法很反感,更重要的是理解语法不只是反复无常的在选择。CSS工作组讨论很久语法的长度,他们提取了一些点,考虑到CSS的语法兼容不会与未来增加的其他语言冲突。

CSS 预处理器是一个非常出色的工具,但是它们的变量是静态的,有语法作用域。Native CSS 变量,从另一面来看,它们是一个完全不同类型的变量:因为它们是动态的,他们的作用域是DOM,事实上,这也是困惑该不该称他们为变量,它们实际上是CSS 属性,这也给了他们一个机会,来解决这个功能完全不同的问题。

在这篇文章中,我将讨论一些CSS 自定义属性这个功能,而且不用CSS 预处理器来做。当然我还演示一些新的设计模式,自定义功能的启用。文章最后讨论一下,我认为在未来最有可能的是预处理变量和自定义变量一起使用,两个东西取长补短,珠联璧合。

注意:这篇文章不是介绍CSS 自定义属性,如果你还从来没听说过他们,不熟悉他们是如何工作的,可以看看这里

预处理器变量的限制

在继续写之前,我想强调的是,我真的很喜欢CSS 预处理器,我的所有项目都在使用它。预处理器做了一件非常了不起的事情,即时你知道他最终出来的就是原始的CSS,任然可以感受这个神器的时代。

任何工具,都有他的局限性,有一个炫酷的外观会让人惊喜而忽略了其中的限制,特别是新用户。

Preprocessor variables aren’t live

也许受预处理限制,在媒体查询中,最常见的新手也无力吐槽定义变量或使用@extend

 $gutter: 1em; @media (min-width: 30em) {   $gutter: 2em; } .Container {   padding: $gutter; }

如果你编译上面的代码,你得到是:

.Container {   padding: 1em; }

如你所见,媒体查询被废弃,变量赋值被忽略。

从理论上讲,虽然sass 负责申明条件变量,但这样做也是一个挑战,枚举所有Permutations—exponentially 会增加CSS的最终大小。

预处理器变量不能级联(层叠)

每当你使用变量,作用域的范围不可避免,这个变量应该全局吗?应该是file/module?还是块作用域?

CSS 最终是为HTML的样式,事实证明还有另外一种有用的方法是变量的范围:DOM 元素,但是preprocessors不能运行在浏览器且从未看见标记

参考一个网站,试图给 的元素添加一个 class user-setting-large-text 他们更倾向于更大的文本大小 。
一旦这个class设置,更大
$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函数用在background-color属性,但button元素继承它的父class元素.alert。如果class info或者error已经添加到alert(或者通过JavaScript设置背景颜色或用户样式),按钮元素希望能够得到这两个颜色。

现在这个虽然不会在sass 运行,因为预处理器不知道DOM结构,但还是希望搞清楚这类型的东西可能有哪些用处。

说一个特定的用例:这也是在继承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的值跟随窗口的变化而变化,浏览器的支持自定义属性的话,效果屌屌的!
custom-properties-responsive-1400w
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使用语境样式方法,使用子代选择器

/* Regular button styles. */ .Button { } /* Button styles that are different when inside the header. */ .Header .Button { }

这种方法有很多问题(在我的文章有解释),这种模式一个代码味道,它违反了 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 组件:我要设置这些属性值,由我的子代来确定和如何使用它们。

主要的区别是,该扩展由按钮组件选择,并轻易消除例外情况。

下面的演示说明了语境样式的链接和按钮在网站的标题及内容区

custom-properties-contextual-styling-1400w

在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; }

跟React学

当我第一次探索自定义属性语境样式的时候,我很怀疑自己。像前面说的,我倾向于喜欢组件自己定义自己的变化,而不是任何属性都继承自父元素。

但是有一件事,动摇了我在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; }

管理全局names

如果你一直关注自定义属性,那你可能已经注意到本身带有components-specific前缀的组件,如--Button-backgroundColor.

与CSS 大多数名字一样,自定义属性是全局,很是有可能将正在使用命名与其他开发团队的名称产生冲突。

有一个简单的方法可以避免这个问题,就是坚持命名约定,我现在团队就是这么做的。

对于更复杂的项目,你可以考虑像CSS模块 localifies所有全局名称,而且他们最近也表示有兴趣支持自定义属性。

结束语

如果你在阅读这篇文章之前,不熟悉CSS 自定义属性,我希望你能给他一个机会。如果你还在怀疑他的必要性,希望我能改变你的

深圳自适应网站建设 深圳响应式网站建设 大神部落 UI设计 软件界面设计 移动端UI设计 手机端UI设计 交互设计 用户体验 IOS开发 Android(安卓)开发 高端品牌网站建设 商城网站建设 手机网站建设 Html5响应式网站建设 网站推广营销 微信开发 系统开发 营销型网站

扫一扫

关注大神部落

长按二维码

关注大神部落

地址:深圳市龙华新区东环一路天汇大厦B座520室

电话:13530491906

Copyright © 2014 - 2018 大神部落 & 版权所有

粤ICP备15047144号