ホームページ > 記事 > ウェブフロントエンド > CSS 前処理言語のモジュール形式の実践
CSS を記述することは、フロントエンドの作業ではよくある頻繁な作業です。CSS は言語ではないため、プログラミングは少し大雑把に見えます。小規模なプロジェクトの場合、CSS の量はそれほど多くないため、問題は浮き彫りになりませんが、より大きなプロジェクトを開発および保守したい場合は、CSS を管理および標準化する必要があります。そうしないと、取り返しのつかない結果が生じます。怖い?)
前のセクション [CSS のモジュール性について語る] では、持続可能な開発ルートを形成するために、標準化された制約を通じて CSS の記述方法を最適化および改善しました。しかし、冗長性というものが残ります。パブリック モジュールとプライベート モジュールを定義することで、婉曲的に共通のボリュームを共有していますが、共通のボリュームは依然として大きすぎるため、設計の観点から、再利用をより適切に達成するには、できるだけ多くのパブリック モジュールを改良する必要があります。最も理想的な状況は、すべてのモジュールが共通のライブラリに保存され、必要なときにライブラリから直接転送できることです。この美しい願いは、前処理言語の助けを借りて簡単に達成できます。
前処理言語は CSS に似た言語であることはわかっていますが、CSS 自体は言語ではなく、前処理言語は言語機能のこの部分を埋めるために生まれました。変数、関数、ミックスの定義に加え、ファイル参照、マージ、圧縮などの機能を実装し、CSSをオブジェクト指向化し、複雑かつ大規模なビジネスにも対応できるようにしています。
現在、よく使われている前処理言語は、less と sass の 2 つです。勉強の場合は両方から始めてもいいですし、仕事の場合はどちらかに慣れるようにしましょう。私は sass を使用することが多いため、以下の内容は sass を基本言語として紹介します。この 2 つは機能的に類似点が多いため、実装に大きな違いがあることを心配する必要はありません。
基本的な文法については、公式 Web サイト (英語) または w3cplus sass ガイド (中国語) で学習できます。ここでは簡単に説明するだけで、使用する必要がある内容の一部については説明しません。
Sass には 2 つの接尾辞ファイル名があります。1 つは接尾辞名 sass で中括弧とセミコロンを使用しません。もう 1 つはここで使用する scss ファイルで、中括弧とセミコロンを使用して通常作成する CSS ファイル形式に似ています。このチュートリアルで言及されているすべての sass ファイルは、接尾辞 scss が付いているファイルを指します。また、sass 接尾辞の厳密な形式要件によるエラーを避けるために、接尾辞 scss の付いたファイルを使用することをお勧めします。 ——w3cplus sass ガイドからの抜粋
Sass には 2 種類のネストがあります。1 つはセレクターのネストで、もう 1 つは属性のネストです。私たちが通常話したり使用したりするのは、セレクターのネストです。 ——w3cplus sass ガイドからの抜粋
セレクターのネスティング いわゆるセレクターのネスティングとは、あるセレクターを別のセレクター内にネストして継承を実現し、それによって Sass ファイルの構造と可読性を向上させることを指します。セレクターのネストでは、& を使用して親要素セレクターを表すことができます。 ——w3cplus sass ガイドからの抜粋
// index.scss .g-index { ... .g-hd { ... .m-nav { ... } } .g-bd { ... .m-news { ... } } .g-ft { ... .m-copy_right { ... } } .m-dialog { display: none; &.z-active { // 留意此处&的用法 display: block; } } }
コンパイル後:
/* index.css */ .g-index { ... } .g-index .g-hd { ... } .g-index .g-hd .m-nav { ... } .g-index .g-bd { ... } .g-index .g-bd .m-news { ... } .g-index .g-ft { ... } .g-index .g-ft .m-copy_right { ... } .g-index .m-dialog { display: none; } .g-index .m-dialog.z-active { // 留意此处&的编译结果 display: block; }
クールじゃないですか?多数のセレクターを何度もコピーして変更する必要はなく、それらの間の関係を整理する必要もなく、それらをネストするだけで済み、すべての関係は DOM を見るのと同じくらい単純かつ明確です。直接!手を解放し、目を解放し、同時に効率を高めます。 Sass を作成するときは、Sass のネスト順序が DOM と一致するように努める必要があることに注意してください。DOM 内のすべての要素をスタイル設定する必要があるわけではないため、ネスト順序はレベルではなく一貫していることに注意してください。
sass のネストされた記述方法が保守しやすいことを説明するために、別のシナリオについて説明します。元々 g-bd の下に m-article_box モジュールがあると仮定します。次に、m-article_box を g-bd から g-hd に移行したいとします。もちろん、この要件には多少無理があります~)、元のコードを見てみましょう:
<!-- index.html --> <!DOCTYPE html> <html> <head> <title>index</title> </head> <body> <p class="g-index"> <p class="g-bd"> <p class="m-article_box"> <p class="hd"> 最新文章 </p> <p class="bd"> <p class="list"> <p class="item"> <img class="cover" /> <p class="info"> <p class="title"> <a href="#">文章标题</a> </p> <p class="desc">文章简介</p> </p> </p> </p> </p> <p class="ft"> <p class="page"> <a href="#" class="pg">1</a> <a href="#" class="pg">2</a> <a href="#" class="pg">3</a> <a href="#" class="pg">4</a> </p> </p> </p> </p> </p> </body> </html>
.g-bd { ... } .g-bd .m-article_box { ... } .g-bd .m-article_box .hd { ... } .g-bd .m-article_box .bd { ... } .g-bd .m-article_box .bd .list { ... } .g-bd .m-article_box .bd .list .item { ... } .g-bd .m-article_box .bd .list .item .cover { ... } .g-bd .m-article_box .bd .list .item .info { ... } .g-bd .m-article_box .bd .list .item .info .title { ... } .g-bd .m-article_box .bd .list .item .info .desc { ... } .g-bd .m-article_box .ft { ... } .g-bd .m-article_box .ft .page { ... } .g-bd .m-article_box .ft .page .pg { ... }
css の方法によると、m-article_box に関連する部分をすべて g-bd から g-hd にコピーする必要があります。これはモジュールの記述が仕様に準拠していることが前提ですが、モジュールの記述が仕様に準拠していない場合、m-article_box クラス以下のすべての構造がハングしないと、本当に悲惨になります。しかし、sass を使用することで、m-article_box のブロック全体を g-bd から g-hd に切り取るだけで済みます (変更の負荷が大きいことを強調するために、モジュール構造全体を特別に書きました。文字数... ):
// 修改前 .g-hd { ... } .g-bd { .m-article_box { .hd { ... } .bd { .list { .item { .cover { ... } .info { .title { ... } .desc { ... } } } } } .ft { .page { .pg { ... } } } } } // 修改后 .g-hd { .m-article_box { .hd { ... } .bd { .list { .item { .cover { ... } .info { .title { ... } .desc { ... } } } } } .ft { .page { .pg { ... } } } } } .g-bd { ... }
非常に便利でエラーも少なくなります。
コードに直接行きましょう:
// index.scss $fontSize: 16px; $grey: #ccc; .m-nav { font-size: $fontSize; color: $grey; }
コンパイル結果:
/* index.css */ .m-nav { font-size: 16px; color: #ccc; }
コードを書いたことがある人なら誰でも パラメーター の使い方に精通しています。あまりにも簡単に説明したくありません。
// pixels to rems @function rem($px) { @return $px / 640 * 16rem; }
単純すぎてあまり言いたくありませんが、ご自身で理解してください。
混合,顾名思义,就是混合的意思。。。也就是我们可以事先定义一段代码块,在需要使用到的地方,直接引用(include),而在引用之前,这段代码都不会出现在编译文件中,也就是不会生成任何内容。
这也是非常重要的一个特性!我们知道common的体积非常大,而体积大的根本原因是它存放了许许多多的模块。我们设想一下,如果将每一个模块都打包成mixin,那common不就减肥成功了?!多年的顽疾终于看到希望,没有比这更让人惊喜的了!我们这就上车:
/* common.css */ .m-nav { ... } .m-news { ... } .m-copy_right { ... }
改造后
// common.scss @mixin m-nav { .m-nav { ... } } @mixin m-news { .m-news { ... } } @mixin m-copy_right { .m-copy_right { ... } } // index.scss .g-index { @include m-nav; @include m-news; @include m-copy_right; }
这个属性很眼熟?没错,事实上,css本身就有这个属性实现,我们可以在css文件中直接使用import来引入其他文件。那么css的import和sass的import有什么区别?从含义和用法上来说,没有区别,区别在于工作原理。css的import是阻塞的,而sass的import在编译后,其实是合并文件,最后只产出一个css文件,而css则没有合并,该多少个文件就还是多少个文件。
注意:
只有import一个.sass/.scss文件的时候,才可以省去后缀名,如果是直接import一个.css文件,要补全文件名;
import之后的分号;不要漏写,会报错;
sass如果import的是一个.css文件的话,那它的作用就跟css原生的import作用一样,只有import一个sass文件的时候,才是合并文件。
如下:
// index.scss @import 'common'; @import 'a.css';
编译结果:
/* index.scss */ .m-nav { ... } .m-news { ... } .m-copy_right { ... } @import url('a.css');
css的import之所以没有被普遍使用是有原因的。我们可以大概猜到它的工作原理:a.css import b.css以后,当浏览器加载到页面中的a.css的时候,已经准备按照a.css的内容来渲染页面了,刚解析到第一行,发现a.css居然还import了一个b.css,于是它不得不先放下a.css(既阻塞a.css),去加载b.css,直到b.css加载完,并且优先解析它,然后才开始回来解析a.css——鬼知道b.css会不会又import了c.css……这直接导致了渲染工作滞后,引发性能问题。
说实话我还不如直接用两个link标签去同步加载a.css和b.css,效率会高一些。
所以css的import基本是被抛弃了的属性。
sass的import主要的好处就是把文件合并了,减少了请求。原本需要link好几个css文件的页面,现在只需要一个。
终于要开始干点正事了,首先我们来回顾一下,上一节我们以规范为基础构建的模块化项目,遗留了一些什么问题。
冗余 体积庞大的common;
使用cm-模块区别m-模块,使得后期开发过程中,m-模块向cm-模块转变过程比较繁琐;
……
好像,问题也不是特别多,我们一个一个解决。
为了方便,在这里我们把每个页面所对应的scss文件叫做 页面scss;把变量、函数、混合等(没有被引用或者执行的情况下)编译后不产生实际内容的代码叫做 定义类代码 ,那么相对应的其他内容就是 实际内容代码。
我们知道,一方面,在common中过多地添加模块最终会导致common的体积过大,使得资源冗余,另一方面,为了方便维护,我们又希望尽量多地把模块公有化。
这是一对矛盾,仅靠css本身是无法解决的,但sass可以!如果我们使用mixin来代替直接书写模块,由于mixin并不直接生成代码,而是通过主动引用,才能生成对应内容,那么理论上,common就可以无限多地存放模块而不必占用一点空间!
(注意,这里说的是理论上,实际应用中,文件太过庞大的话,免不了还是要受到命名冲突的限制的,不过这问题不大。)
说干就干,我们把common中的模块全部打包成mixin:
/* common.css */ .m-nav { ... } .m-news { ... } .m-copy_right { ... }
改造后
// common.scss @mixin m-nav { .m-nav { ... } } @mixin m-news { .m-news { ... } } @mixin m-copy_right { .m-copy_right { ... } }
调用方式如下:
// index.scss @import 'common'; // 记得先引入common .g-index { @include m-nav; @include m-news; @include m-copy_right; }
原本我们会在每个需要用到公共模块的页面中,先引用common,然后再引用页面css,而现在,我们只需要在页面scss中直接@import common;就可以了。
使用common:
<!-- index.html --> <!DOCTYPE html> <html> <head> <title>index</title> <link rel="stylesheet" type="text/css" href="./style/common.css"> <link rel="stylesheet" type="text/css" href="./style/index.css"> </head> <body> ... </body> </html>
改造后:
// index.scss @import 'common';
<!-- index.html --> <!DOCTYPE html> <html> <head> <title>index</title> <link rel="stylesheet" type="text/css" href="./style/index.css"> </head> <body> ... </body> </html>
很完美,——至少目前为止是这样。
我们思考一个问题,common除了存放模块之外,还有没有其他内容?答案是肯定的,大家一定知道有个东西叫做css reset(或者normalize.css),这肯定是全局的;另外,如果是做后台管理系统的话,可能还会有bootstrap。当然,还有一些自定义的全局的样式,比如常见的.clearfix,等等。
这些东西目前我们也堆积在common当中,而且合情合理,因为它们都是全局的样式。但是对比起mixin来说,这些实际内容代码显得很少量,有种被淹没的感觉,使得整个common看上去就像只有mixin。但是这些实际内容代码的作用却又非常重要。为了使common的构成更加直观,我们把mixin全部都抽离出来,单独存放一个叫做mixin.scss的文件中,然后在common引用它,这样,mixin的管理更加的规范,而且common的结构也更加清晰了。
抽离mixin还有另外一个重要原因,后面会讲到的,我们希望mixin作为一个纯粹定义类代码文件,随处可以引用而不会生成多余的代码。
原本我们会在每个需要用到公共模块的页面中,先引用common,然后再引用页面css,而现在,我们只需要在页面scss中直接@import mixin;就可以了。
使用mixin:
// index.scss @import 'common'; // 引入common,如果有需要,common里一样可以引入mixin @import 'mixin'; // 引入mixin .g-index { ... }
<!-- index.html --> <!DOCTYPE html> <html> <head> <title>index</title> <link rel="stylesheet" type="text/css" href="./style/index.css"> </head> <body> ... </body> </html>
好,抽离了mixin之后,我们现在来重新看回common,common里应该是些什么样的内容。上面的内容我们稍稍提到了一点,我们来展开一下。
我们知道浏览器千差万别,各浏览器的默认样式也是不尽相同,最常见的比如body的默认内边距,p标签的默认内边距,以及ul/ol等等。这些不统一的默认样式经常让我们感到头疼,所以就有人提出一开始写样式就先把它们消除的想法,于是就催生了后来非常流行的reset.css。
起初的reset.css很简单,大概是这样的:
html, body, h1, h2, h3, h4, h5, h6, p, dl, dt, dd, ul, ol, li, p { margin: 0; padding: 0; }
没错,就是把几乎所有会用到的标签都给去了内边距和外边距,简单粗暴,这样所有的标签就都统一了,而且在不同的浏览器下也是统一的。
其他的部分每个人有各自的补充,比如有人会把h1~h6的所有字号给定义一遍,以保证在不同浏览器下他们有统一的大小;有人会给a标签设置统一的字体颜色和hover效果,诸如此类等等。
很好,没毛病。我们把这些统称为css reset,然后再统一封装到一个叫做reset.css的文件中,然后每个页面都引用。
这种方式一直以来都挺实用,而且大家也都这么用,没出过什么问题。只是后来有人提出,这种方式太过粗暴(居然还心疼浏览器了)。。。而且会降低页面渲染的性能,最重要的是,这使得我们原本设计出来的表达各种含义的标签儿们,变得毫无特点了。。。
说的好有道理,如果你家里所有人名字不一样但是都长一个样,还有啥意思。
于是,就出现了normalize.css,normalize的目的同样是为了统一各个浏览器下各不相同的默认样式,不过它并不是简单粗暴地全部抹平,而是根据规范,来人为地把那些不符合规范的默认样式“扶正”,从而达到统一各个浏览器默认样式,同时保留各个标签原有特点的目的。
我们不能说reset与normalize这两种思想孰好孰坏,只能说各有各的特点和作用,它们的存在都是为了解决同样的问题。
一般来说,一个ui插件都会至少包括一个css文件,像bootstrap、datepicker等等。假设我们项目中需要以bootstrap为基础框架,实现快速开发,那么这时候我们就需要在项目中全局引入bootstrap.min.css,当然,还有bootstrap.min.js。说到全局暴露,我们第一时间想到的是common,没错,我们可以在common中引入。
有人问,插件的.css文件怎么import?额,改一下扩展名为.scss就可以了,scss是兼容原生css语法的~
所以最终,我们的common大概是这样子的:
// common.scss @import './reset'; @import './bootstrap.min'; @import './mixin';
事实上,如果我们不需要使用到 mixin.scss 中的变量和mixin的话,我们可以不引用它。
那么我们的页面scss应该是这样的:
// index.scss @import './common'; @import './mixin'; .g-index { ... }
干净,整洁。
每添加一个新角色,我们就要及时给它设置规范,好让它按照我们的期望工作别添乱。我们接下来来归纳一下mixin的书写规范。
シナリオ 1: プロジェクトには、mixin.scss、a.scss (これが特定の関数ファイルであると仮定)、index.scss という 3 つのファイルがあります。変数 $fontSize: 16px と変数 $color が定義されています。 : #ccc; で定義されている場合、インデックス内でこれら 2 つのファイルを同時に参照すると、インデックス内で 2 つの変数 $fontSize と $color を直接使用できます。これら 2 つの変数の宣言と定義へのインデックスですが、これらはただ存在しているだけです。
これは良いことですか、それとも悪いことですか?私の直感では、これには何か問題があるのではないかと思われます。はい、これは前に議論した汚染と似ていますか?ただ、前に common を参照した後、何かを書き込む前に、index が多くのモジュール名を占有していましたが、今度は他のファイルが参照されているため、index の変数名が多く占有されています。また、メンテナンスの観点から、これも事前に伝えないと問題になります。インデックスの $color がどこから来たのか知っていますか。フォント サイズが必要な場合、どのファイルを変更すればよいかわかりますか?さらに、mixin と a を同時に参照するときに、それらの間に同じ名前の変数が存在する可能性があることをどのように確認しますか?それで、誰が誰をカバーするのでしょうか?これらの問題は小さいように見えるかもしれませんが、プロジェクトの規模が大きい場合、取り返しのつかない大惨事になる可能性があります (誰を怖がらせているのですか?)。
シナリオ 2: プロジェクトにテーマ カラーがあるとします。使いやすさを考慮して、境界線、タブの背景、ナビゲーション バーの背景、フォントの色などはすべてこのテーマ カラーです。値を取得するので、ミックスインでグローバル変数 $color: #ff9900 を定義すると、どこでも問題なく使用できるようになります。
ウェブサイト全体の開発が完了してから 1 か月後、デザイナーが突然やって来て、「上司がこのテーマカラーを変更する必要があると言いました。ちょっと土臭いので、明るい赤に変更しましょう。」と言いました。気が進まなかったが、内心喜びながら mixin を開いて、$color の値を明るい赤に変更して、デザイナーに誇らしげにこう言いました。 、ページを開いて見てください、デザイナーと私の顔はすべて緑色です、なぜページはとても醜いのですか? 一部の単語は元々テーマカラーでしたが、背景は赤でしたが、変更後、ブロック全体が黒になりました。一部の枠線が赤くなっており、内容がよく見えません。 フォントは元のテーマカラーですが、変更され、枠線とフォントが赤くなっています。
デザイナー: 「いいえ、いいえ、いいえ、背景色を変更したいだけです。」
あなた:「テーマカラーを変えるって言いませんでしたか?それだけです。」
デザイナー: 「いいえ、背景を変更するだけです。」
あなた:「まさか…」
デザイナー: 「どうしてですか? 背景色を変更するだけではだめですか? 設定に従って元に戻すだけです。」
あなた: 「思っているほど簡単ではありません...」
……
大丈夫、私はあなたを怖がらせるためだけにここにいます。あなたが本当に上手なら、これは大したことではありません。
したがって、ミックスインを管理したのと同じように、(グローバル) 変数を管理する必要があります。どこでも変数を定義したり、毎回グローバル変数を変更したりすることはできません。 グローバル変数はミックスインでのみ定義されます。他の scss ファイルで定義された変数 (グローバルまたはローカルに公開されるかどうか) はローカル変数とみなされ、現在のファイルの外部では使用されません (たとえ参照できたとしても、使用は避けてください)。
グローバル変数を使用する必要がある場合は、
を直接インポートします。
一般に、グローバル変数を定義するときは注意が必要で、グローバル変数の数はできるだけ少なくする必要があります
。
要件が変更された場合は、目的が明確でない限り、変更が必要な部分を徐々に置き換えてください。
color などの一般的な名詞をグローバル変数として使用しないでください。$orange: #ff9900 などの色の値の説明を直接使用することをお勧めします。これにより、色の値の維持と拡張が容易になります。すべての場所を変更する必要がある場合は、$red: red などの新しい変数を定義して展開できます。
これらの点は少し奇妙に思えますが、実際、なぜこれを行う必要があるのかを説明するのは非常に困難です。結局のところ、これらはすべて経験の要約なので、考える前に一定期間 Sass の使用に慣れておくとよいでしょう。これらの問題について詳しく説明します。
呼び出しモジュール
在页面scss中调用模块是一个好习惯,它使得我们在每个页面所用到的模块既是一致的又是互相隔离的,不像在common中直接引用模块那样,使得一个页面scss还没有内容的时候就已经被很多模块名污染了。
再提个问题,在页面scss的哪里调用模块?
例一,根类外:
// index.scss @import './common'; @import './mixin'; @include m-nav; @include m-news; @include m-copy_right; .g-index { ... }
例二,根类内:
// index.scss @import './common'; @import './mixin'; .g-index { @include m-nav; @include m-news; @include m-copy_right; ... }
目前为止,这两种方式都是可以的,至于我为什么用“目前为止”这个词,那是因为我们后面将要讲到的SPA,如果用例一的方式是有问题的。所以我比较鼓励使用例二的方式。当然,我说了,目前为止例一也是没问题的。
目前为止,我们的模块化工作已经算是完成了,其实已经可以收工了。不过我们还是可以稍微做一下优化。
我们需要考虑一个问题:缓存。
缓存是我们web开发中最常见的情况之一,很多时候我们都需要跟缓存打交道,特别是在做性能优化的时候。
一般来说,静态资源在被加载到浏览器之后,浏览器会把它本地缓存下来,以便下次请求同个资源的时候可以快速响应,不需要再去远程服务器加载。
我们就css来说,假设我们按照原来的方式,使用多个link去加载reset、bootstrap、common、index这几个文件的话,这几个文件都会被缓存下来,以使得下次再访问这个页面,这个页面的加载速度会快很多。
如果是从index页面跳转到about页面呢?你会发现也很快,因为about页面的全局css(reset、bootstrap、common)和index页面是一样的,而它们在你访问index的时候,已经加载过了,得益于缓存的作用,之后的页面打开都快。
我们现在的方式是,一个页面所用到的所有css文件都被合并成一个,也就不存在相同的文件可以利用缓存这样的优势了。
那我们有办法改进吗?有的!我们只需要把common独立出来,那么common就可以做为被缓存的公共文件了。最终我们从一个页面只引用一个文件变成了一个页面引用两个文件,即common和页面css:
// common.scss @import './reset'; @import './bootstrap.min'; @import './mixin';
// index.scss @import './mixin'; .g-index { ... }
注意,不同于之前,我们这里的index.scss不再引入common.scss,所以我们最终是得到了两个css文件,而common.css是在所有页面中通过link标签引入的。
如此一来,我们就实现了既能够合并文件,减少请求数,又可以利用缓存,提高加载速度。
代码压缩是优化工作中最基本的一步,css的压缩空间是很大的,尤其是我们这种 垂直的书写方式 ,压缩起来是相当高效的。
在sass中这很简单,sass在编译的时候提供了几种模式,其中的compressed模式是最高效的压缩模式,记得在编译打包的时候选择compressed模式就行了。
总的来说,预处理语言在使我们编程更加美好的同时,也使得规范更加的完善。在css本身无法实现的情况下,我们通过工具来完成了模块化开发。
我不会讲如何去安装和配置sass环境,因为这些w3cplus sass guide有详细的介绍了,建议使用nodejs的方式,不会捣鼓nodejs/npm的前端不是好前端。
最后,我们回到一开始提到的问题——为什么要模块化?现在我们可以先从css的工作来回答,从某种意义上讲,模块化提高了我们编程能力和解决问题的能力,使得构建一个庞大而可扩展可维护的项目成为可能,使得我们能够以架构的思维和眼光去搭建整个项目。
以上がCSS 前処理言語のモジュール形式の実践の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。