[ISUX译]我为css变量狂 - 腾讯ISUX

几个星期前,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 自定义属性,如果你还从来没听说过他们,不熟悉他们是如何工作的,可以看看 https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_variables

预处理器变量的限制

在继续写之前,我想强调的是,我真的很喜欢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不能运行在浏览器且从未看见标记

参考一个网站,试图给<html> 的元素添加一个 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的值跟随窗口的变化而变化,浏览器的支持自定义属性的话,效果屌屌的!

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 组件:我要设置这些属性值,由我的子代来确定和如何使用它们。

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

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

在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 自定义属性,我希望你能给他一个机会。如果你还在怀疑他的必要性,希望我能改变你的想法。

我敢肯定,自定义属性能给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/

感谢你的阅读,本文由 腾讯ISUX 版权所有,转载时请注明出处,违者必究,谢谢你的合作。

注明出处格式:腾讯ISUX (http://isux.tencent.com/why-im-excited-about-native-css-variables.html)

原文发布于微信公众号 - 腾讯ISUX(tencent_isux)

原文发表时间:2016-01-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏前端说吧

css3-巧用选择器 “:target”

3196
来自专栏AndroidTv

属性动画 ValueAnimator 运行原理全解析

好,废话不多说,之前我们已经分析过 View 动画 Animation 运行原理解析,那么这次就来学习下属性动画的运行原理。

3628
来自专栏Alice

ios tableview 上加 textfiled

ios tableview 上加 textfiled  首先附上我项目中用曾经用到的几张图  并说明一下我的用法: 图1: ? 图2: ? 图3: ? 心在你我...

1825
来自专栏程序员互动联盟

【专业文章】六种常见的HTML5写法误用(一)

一、不要使用section作为div的替代品 人们在标签使用中最常见到的错误之一就是随意将HTML5的<section>等价于<div>——具体地说,就是直接用...

3065
来自专栏从零开始学自动化测试

appium+python自动化32-android_uiautomator定位进阶版

前言 上一盘介绍uiautomator的定位方式都是类似这种'new UiSelector().xxx("xxx")',看起非常长,我也记不住,这很不pytho...

3153
来自专栏前端儿

Flex 布局相关用法

布局的传统解决方案,基于盒状模型,依赖 display属性 + position属性 + float属性。它对于那些特殊布局非常不方便,比如,垂直居中 就不容易...

861
来自专栏cs

学markdown怎能不知道与之相关的html了include <iostream>

<h2>导言</h2> <strong> markdown,hthl都是超文本标记语言,markdown是简化版本的html,兼容html语言,熟悉一下ht...

2224
来自专栏Coco的专栏

不可思议的纯CSS导航栏下划线跟随效果

1293
来自专栏web编程技术分享

《从案例中学习JavaScript》之实现对话效果(2)-- 附超简单函数封装技巧

3126
来自专栏进步博客

纯CSS实现响应式表格

自从转岗至腾讯云后,项目中接触到大量的数据表格。多列数据表格在空间有限的手机屏幕下,难以完美呈现,需要做响应式处理。本文介绍一种使用纯CSS实现响应式表格的方法...

882

扫码关注云+社区