(译)使用和滥用 – CSS继承和层叠的滥用

原文作者:Micah Godbolt
原文地址:http://www.phase2technology.com/blog/used-and-abused-css-inheritance-and-our-misuse-of-the-cascade

我认为我们正滥用CSS的继承模型。我知道CSS中的“C”代表“层叠”,但这不意味着从最简单的选择器到一个复杂组件都需要层叠。经常性的一个h2从根一级的h2标签获得了基础层的样式,然后得到第二个样式应用因为它定位在页面上一个特别区域。第三个样式应用被添加因为它属于一系列共享普遍样式的组件。最后,由于h2属于这个组件的一个特殊变化,它在最上面有第四层样式。

仅仅是标记的一小部分,4个分布各处的样式来源组合在一起。听起来复杂而又疯狂?这也许很疯狂,但是绝不是罕见的。

掉进兔子洞

这都是因为某人说了一句:“我不想每次增加一个h2都写CSS”引起的。所以我们这样写:

    h2 {
      color: #fd7900;
      font-size: 1.8em;
      line-height: 1.1;
      margin-bottom: .3em;
    }

这一开始非常有效,但随后发生了一件事情:故事的主人想要改变```.sidebar h2``的样式。

    .sidebar h2 {
      color: #8cc5e6;
      font-size: 2.2em;
      padding-bottom: .2em;
      border-bottom: 1px solid black;
    }

我们不需要改变所有东西,只是颜色,尺寸和边框。所以我们利用了CSS层叠的好处并扭转需要更新的值。

充满废弃鞋子的储藏室

我们试着聪明的把功能性的小块分离成可复用的侧边栏块,所以我们决定创建一些默认块样式。不管是现在或未来我们通过这种方法创建的所有块都能继承相同的样式。每个块都有一个h2,所以让我们看一看这会是怎样被赋予样式的。

    .block
      border: 1px solid #8cc5e6;
      padding: 0 15px;
    }
    .block h2 {
      /* Color was changed by .sidebar h2, set it back */
      color: #fd7900;
      /* remove border added by .sidebar h2 */
      border: none;
      /* remove the margin set by h2 */
      margin: 0;
      padding: .3em;
      background: #8cc5e6;
      margin: 0 -15px;
    }

下一次冲刺我们会喘不过气来

我们的块表现得很好。现在我们被要求使用最初的侧边栏头部创建一个可选的块级样式,但是填充灰色背景色。

    /* 
    The Markup
    <div class="block alt-block">
      <h1>My Title</h1>
      ...
    </div> 
    */ 
    .alt-block {
      /* Remove border from .block */
      border: none;
      padding: 15px;
      background: #eee;
    }

    .alt-block h2 {
      /* Need to set color set by .block h2 */
      color: #8cc5e6;
      /* Need to set the padding-bottom back to .sidebar h2 styles */
      padding-bottom: .2em;
      /* Need to set the border back to .sidebar h2 styles */
      border-bottom: 1px solid black;
      /* Remove negative margins and background from .sidebar h2 
      but set bottom margin again from root h2 */
      margin: 0 0 .3em;
      /* remove background from .sidebar h2 */
      background: none;
    }

我们做了什么?

到这里!我们已经完成!故事是可接受的,代码被合并到主分支。但代价是什么呢?我们的.alt-block h2只包含覆盖的样式或恢复到我们已经写的样式。

在顶部,我们的标题现在从4个不同的选择器继承样式,显得臃肿,页面表现遭受重击,样式系统难以置信得脆弱。任何对h2,.sidebar h2,.block h2做出的改变都将影响.alt-block h2,无论我们是有意或者无意的。

我的代码是受到约束的

所以到底发生了什么?在上面的例子里我们见识了3种不同的CSS继承的类型:标签继承,上下文继承和类继承。让我们来看一下它们每一个是如何生效的,它们为什么会引起问题以及修复这些问题的解决方案。

标签继承

我们用HTML标签来使我们的页面语义化。当我们在这些标签上应用样式时,
我们最终会混淆语义和表现。我不介意把非常基础的一系列默认样式应用到标签上,但在我们的例子中当我们把完成的表现样式绑定到h2标签上时会陷入问题。这不仅仅是混淆其他h2标签,这也让那些基础样式一旦被覆盖就不可能再复用。

除了依赖标签继承来扩展我们的基础样式,我们应该把这些样式移动到可复用的类或继承上。如果你想的话甚至可以取.h1%h1!但关键是避免把描述绑定到基础标签上,而是移动到可选打印样式上。

上下文继承

我经常听到有关于上下文继承的争论:“我想要侧边栏中的每个h2都看起来一样,我不想每次创建一个新的侧边栏部件时都写CSS。”

这一类型的想法完全违背了组件应该独立,可复用,并且不依赖父容器的宗旨。使用上下文继承意味着你头部的样式取决于是否在侧边栏中(不是独立或者可复用的),并且在写样式前你需要清楚直接父容器。

当我了解了上下文继承的意图后,我个人看到它很多情况下的不足。

  1. 有些人要求你为侧边栏创建一个新的头部样式,你最不想做的事情是尝试所有的6种头部标签(h1-h6)来看一下哪个的样式覆盖最少。
  2. 你最终将被要求把一个主要内容区域的组件移动到侧边栏。你敢打赌有多少的可能性.sidebar h2的样式不会对你要移动的.widget h2产生任何影响。
  3. 你也许认为使用后代选择器(例如.sidebar > h2)会解决所有问题,但你现在的样式取决于标记顺序,没有可以在页面其他地方复用那些h2样式。

最后,如果避免依赖上下继承,你的样式会变得更加灵活并且减少被破坏的可能。

类继承

类继承尝试通过单类来把整个系统的样式应用到标记集合上。在我们的例子中有一个.block类是一个完整的组件,然后我们用.alt-block增加另外一系列规则。在“面向对象”的队伍里,这意味着.alt-block.block紧密联系,或者它“变更或依赖于另一个模块的核心作用”。这不总是一件坏事,但这件事很快会变得复杂而脆弱。

想象一下你写的.block代码,队伍中的其他一个人决定使用.block创建.my-calendar-widget作为一系列基础样式,例如一个"依赖模块"。

    <div class="block my-calendar-widget">
      <h2>My Calendar Title</h2>
      ...
    </div>

除非系统的文件编码关系创建得很好,这会变成我喜欢称为“继承劫持”的情况。我们没有在CSS中指明.block有和它紧密联系的另一个组件,所以我们需要在每次更新.block时处理日历页面的随机bug。

这个困境的解决方法是放弃类继承并且把父类分离成可扩展块,包括容器,标题,文本和图像处理。这个方法通常被推荐来妥协,我只会用在少部分段落里。

系统中继承的问题

继承不是CSS特有的。许多面向对象的编程语言使用继承的概念从父元素向子元素传递属性和方法。这类中的许多语言需要描述这个方法可能带来的陷阱(JavascriptJavaRuby)。CSS也许不是一个像Ruby或者Java之类的编程语言,但是很多时候我们可以从这些语言里学到很多东西来帮助我们解决CSS中继承性的问题。让我们来看一看以下一些经验

多继承

不计代价避免多继承,因为要可靠得使用它会非常复杂。如果你被问题缠住,准备好理清类的层次并且花时间找出每样东西的来源。

如果你的组件使用不同的组合类来获得样式(例如我们的.block.my-calendar-widget),预见CSS组件的改变会如何影响页面将变得非常困难。除了搜索整个页面外(对于某些CMS来说几乎不可能),我们没有其他办法知道我们正在编辑的组件使用了哪些样式,并且这个改变会如何与其他被使用的类进行交互。

第二点,我们要终止把太多时间耗费在尝试理解一个元素样式的层次结构上。样式是从哪里来的?标签,类,父类,上下文,媒体查询,或是它们的结合?当每个元素的样式应用有各自的来源,调试和理解元素的预期行为会更加容易。

结构 vs 继承

使用结构来将许多不同且在互不相关的地方和情况的代码打包成模块

尽管只有单个继承,我们的.block元素依然完全依赖于级联关系。把.block移出侧边栏会导致崩溃。把头部改成H3会崩溃。问题在于.block中的每个元素都从父类继承样式,例如块的位置和自身的HTML标签。因为语义化的关系没有定义样式,这是一个“标题”。

结构考虑的是一个html组件“包含”其他样式,而不是“继承”那些样式。你会发现这个原则被广泛得应用与OOCSS,BEM和Sass @extends里。并不是通过样式层级,每一个覆盖上一个,这个方法通过超级目标选择器使用单一责任原则每次做好一件事并且只做一件事

把所有放在工作中

我们花了很大一部分时间探索不同类型的继承以及传统的面向对象的原则如何帮助我们识别继承模型中的问题并找到解决方案。现在让我们看看如何用Sass通过结构的方式来创建这个.block元素

标记

注意有多少个标题有它特有的类。这允许我们选中标题而不管使用的标签。

    <aside class="sidebar">
      <div class="calendar-widget">
        <h2 class="calendar-widget-title">My Calendar Title</h2>
        ...
      </div>
      <div class="latest-blogs">
        <h2 class="latest-blogs-title">My Blog Title</h2>
        ...
      </div>
      <div class="latest-news">
        <h2 class="latest-news-title">My News Title</h2>
        ...
      </div>
    </aside>

继承

如果你刚接触Sass,我们可以使用extend来传递一系列样式而不需要应用表现类,或者复制那些样式。使用静态的extends例如%primary-header来避免样式表中出现不可用的样式是很重要的。

    %primary-header {
      font-size: 1.8em;
      line-height: 1.1;
      color: #fd7900;
      margin: .3em;
    }
    
    %secondary-header {
      font-size: 2.2em;
      line-height: 1.1;
      color: #8cc5e6;
      margin: 0 0 .3em;
      padding-bottom: .2em;
      border-bottom: 1px solid black;
    }
    
    %block-header {
      font-size: 2.2em;
      line-height: 1.1;
      color: #fd7900;
      margin: 0;
      padding: .3em;
      background: #8cc5e6;
      margin: 0 -15px;
    }
    
    %block {
      border: 1px solid #8cc5e6;
      padding: 0 15px;
    }
    
    %alt-block {
      padding: 15px;
      background: #eee;
    }

给每个块赋予样式

要使用这些扩展中的一个我们只要简单得在选择器里写上@extend %extend-name,Sass会处理剩余的工作。

注意样式表是由多少语义化的选择器组成而不是从标签,上下文或者父类继承样式。这使得我们可以“打包代码并且在许多互不相关的地方和情况下复用。”

    .calendar-widget {
      @extend %block;
    }
    .calendar-widget-title {
      @extend %block-header;
    }
    
    /*...*/
    
    .latest-blogs {
      @extend %alt-block;
    }
    .latest-blogs-title {
      @extend %secondary-header;
    }
    
    /*...*/
    
    .latest-news {
      @extend %alt-block;
    }
    .latest-news-title {
      @extend %secondary-header;
    }

编译后的CSS

在编译后的CSS中你会看到静态的extend%secondary-header被替换成继承它的两个类。你也会注意到我们从来没有停止使用%primary-header,所以那些样式被省略了。这是静态扩展的好处之一。

    .latest-blogs-title,
    .latest-news-title {
      font-size: 2.2em;
      line-height: 1.1;
      color: #8cc5e6;
      margin: 0 0 .3em;
      padding-bottom: .2em;
      border-bottom: 1px solid black;
    }

    .calendar-widget-title {
      font-size: 2.2em;
      line-height: 1.1;
      color: #fd7900;
      margin: 0;
      padding: .3em;
      background: #8cc5e6;
      margin: 0 -15px;
    }

    .calendar-widget {
      border: 1px solid #8cc5e6;
      padding: 0 15px;
    }

    .latest-blogs,
    .latest-news {
      padding: 15px;
      background: #eee;
    }

单一来源的样式表

在这个CSS中我们发现.latest-blogs-titlelatest-news-title很像并且我们通过结构把它们联系在一起。

这会使得我们的CSS膨胀吗?

通常关于extends的抱怨是它比其他方法产生了更大的CSS文件。OOCSS或BEM的方法会像我们上述做的一样除了把.alt-block.secondary-header类直接应用到标签上而不是通过Sass继承。当然这个方法会让我们节约一些比特,这是extends的好处,但是对于一个项目的长期发展来看,这样的好处远大于重复带来的影响。Ben Frain几周前做出了很好得总结

对于我来说,CSS的代码库越大,组成的组件在很多方面与其他互相隔离,这有利于更小的由互相依赖和本质相关样式组成的CSS代码库。

只要我们将这些表象类直接添加到我们博客和新闻标题的标记上就会变得互相依赖并且本质相关。也就是说,我们不能改变它们中的一个而不改变另一个。

旧容换新颜

我没有传统的程序员背景,所以这是我第一次有机会深入研究继承和结构间的区别。讨论这样一个在类似的Java和C++语言下的话题,我经常发现我阅读的文章大部分像网页本身一样过时!最令人惊奇的是它们几乎100%有重大意义甚至是16年以后。摘自“继承与结构:你应该选择哪一个?

“在一个继承关系中,超类通常被认为很“脆弱”,因为在超类上做一个很小的改变会有连带作用并引起应用代码中其他很多地方的改变。”

这是我们确实碰到的问题,当我们改变h2时会引起.block h2的改变并且引起代码的回滚。

这些点子并不是新的,但是随着网页的成熟,以及项目复杂性的增加,它们和我们的行业愈发相关。不管是对我还是我的代码来说,我将花更多的时间来阅读其他的语言,那些语言在12年前也发生过我们现在面对的那些相似的问题。在这里阅读更多Phase2对于CSS的想法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容

  • 问答题47 /72 常见浏览器兼容性问题与解决方案? 参考答案 (1)浏览器兼容问题一:不同浏览器的标签默认的外补...
    _Yfling阅读 13,721评论 1 92
  • 最近学习到CSS的继承属性,正好看到这篇文章,便将它翻译出来。作者的思想,在平时的项目中或多或少都有用过,但是从来...
    hershin阅读 923评论 0 1
  • %1.AJAX = Asynchronous JavaScript and XML(异步的 JavaScript ...
    wangzaiplus阅读 355评论 0 0
  • 在一个组织里,并不是所有的事情都要靠行政的力量去推动,更多的是要靠个体的文化自觉和自律。 但现实...
    松峰说教刘树森阅读 318评论 0 1
  • 陶行知曾经说过。一个人最大的坏习惯,就是不养成习惯的习惯。 读书、写作、健身、记录,都是很好的行为方式,但很多人都...
    我是硬柿子阅读 279评论 0 1