FLOCSS(フロックス)とは何か?
CSSを使ってスタイル処理を書いていると、いつのまにやら同じ処理を重複して記載していたり、使いたかった処理が既に使用されていて、スタイルの適用を強制する「!import」を大量に使わなければいけないといったファイルやコードのカオスが発生することがあります。
そのようなCSSファイルをメンテナンスするのは地獄です。
そのようなCSSで陥りがちなカオスを避ける方法にFLOCSSがあります。
FLOCSSとは、WEBサイト(プロジェクト)の中でCSSスタイルをどのように適用させていくかをまとめた作り方のルールです。
なお、細かく見ると、「Foundation Layout Object CSS」の略で、従来からあったCSSの作り方のルールであるOOCSSやSMACSS、BEM、SuitCSSなどのコンセプトを取り入れたものです。
>(参考)Github FLOCSS公式ページ
FLOCSSの考え方
FLOCSSを使う上で重要な概念は以下の3つです。
カスケーディングとはスタイルを適用する際の優先度(強度)のことです。この優先度に合わせてレイヤーを作成し、その中に命名規則にそってセレクタ名をつけて処理を記述していきます。
以下でそれぞれの詳細について解説していきます。
カスケーディングとは何か?
カスケーディングとは何か?
CSSにおけるカスケーディングとは、ある要素に対して、複数のスタイル処理を指定した場合に、どの処理を優先して適用するかの順序です。
英語ではCascadingと書き、Cascade(カスケード)の進行形になっています。
Cascade(カスケード)とは滝が幾段にも重なった状態を表しています。
CSSでは、滝のように段々とスタイルを適用させていくイメージになぞらえています。
スタイル適用の優先順位
CSSのカスケーディング、すなわちスタイル適用の優先順位は大きく次の2つに分類することができます。
ファイルの中のスタイル適用の優先順位
CSSファイルの中では、下側に記述されている処理ほど、スタイル適用の優先順位が上がります。
このため、SASSの@import機能で他のCSSファイルを読み込むときに、上側で読み込んだファイルで指定しているプロパティと、後から読み込んだファイルで指定しているプロパティに同じ記述があった場合、後から読み込んでいるスタイルが優先されます。
実例:CSSファイル内の場合
例えば、以下のようにクラス名「red-color」と「blue-color」がついたdivタグがあるとします。
<p class="red-color blue-color">ダミーテキスト</p>
これに対して、それぞれのクラスを指定してCSSを記述したとします。
.red-color{
color: red;
background-color: yellow;
}
.blue-color{
color: blue;
}
ブラウザの表示は以下のようになります。
指定したpタグに適用されるのは、下側に記述した「blue-color」の処理が優先的になり、上側の「red-color」のプロパティの値は上書きされます。
なお、「background-color」のように、他に処理が出てこない場合は上書きは発生しません。
実例:他のファイルを読み込む場合
例えば、@importを使って、以下のように「fundation/_base.scss」と「layout/_footer.scss」ファイルの2つを「app.scss」というファイルの中で読み込んだとします。
@import "foundation/_base";
@import "layout/_footer";
すると、app.scssの中にはファイルを読み込んだ順番に、そのファイルの内容が入ります。
このため、後から読み込んでいる「layout/_footer.scss」の中に、「fundation/_base.scss」と重複する指定があると、後から読み込んだファイルの内容で上書きが発生してしまいます。
セレクタの種類によるスタイル適用の優先順位
CSSではスタイル適用の処理を各順番以外にも、どのセレクタや書き方を使うかで適用の優先順位が変わります。
例えば、aタグに直接スタイルを指定したものよりも、a:linkのように疑似クラスを使った処理の方が優先されます。
このため、a:linkの中に重複するプロパティがあれば、その値で処理を上書きします。
さらに、タグの種類よりも、クラスで指定したり、id名で指定する方が優先度は強くなります。
セレクタとは異なりますが、更に上の優先度になるのが、HTML上に直接スタイルを記述するインラインスタイルです。
そして最強なのがプロパティの後ろに「!important」を宣言する書き方です。
このため、CSS/SASSファイルの読み込み順序や書き方がカオスになっていると、スタイルの強制適用を表す「!important」の記述が乱用されることになります。(非常にメンテ性が低い状態です)
なお、FLOCSSではLayoutレイヤーのみid名を使ったidセレクタ(#セレクタ名)の使用が許可されています。その他のレイヤーではクラス名を使ったクラスセレクタを使用します。
各セレクタや記述の優先度をまとめると以下のようになります。
適用の優先度 | セレクタや適用の種類 | 処理の例 |
---|---|---|
6 | !important宣言 | color: red !important; |
5 | インライン(style属性) | style=”color: red;” |
4 | IDセレクタ | #hoge |
3 | クラスセレクタ | .hoge |
3 | 属性セレクタ | a[href=”https://example.org”]{color: red;} |
3 | 擬似クラス | button:hover {color: red;} |
2 | 疑似要素 | p::first-line {color: red;} |
2 | 要素セレクタ | p {color: red;} |
1 | ユニバーサルセレクタ | * {color: red;} |
- 優先度は最弱を1として+1ずつで表示しています。
!important
はセレクタではありませんが、すべてのルールを無視して適用されるため最上位の優先度として記載しています。
>(参考)htmq.com セレクタの種類一覧
レイヤー(ディレクトリ構造)
レイヤーとは何か?
FLOCSSでは上記のカスケーディングを考慮しスタイルの優先度を反映した形で、かつ機能毎に分けてディレクトリ構造が定義されています。
そして、このディレクトリのことをレイヤーと呼びます。
主体となる3つのレイヤーがあり、その中に更に3つのレイヤーがあります。合計すると6つのレイヤーで構成されます。
さらに、そこに各SASSファイルを取りまとめる用の「app.scss」というファイルを設置します。
主な3つのレイヤー
- Foundation: スタイルのリセットや、ページの下地となる背景のスタイルなどを指定するファイルを置く。
- Layout: ヘッダー、フッター、メインコンテンツエリアなど全体で共通となる主要なエリアのスタイルを指定するファイルを置く。
- Object: WEBサイト内の複数のヵ所で再利用する可能性のある要素のスタイルを指定するファイルを置く。この中に更に3つのレイヤーがある(component, project, utility)
objectの中の3つのレイヤー
- Component: ボタンなどの繰り返し再利用する要素(モジュール)のスタイルを指定するファイルを置く。
- Project: プロジェクトに固有のページなどのスタイルを指定するファイルを置く。(ページ単位でスタイルを用意する)
- Utility: わずかなスタイルの調整をするためのファイル。例えばmargin-topを8pxずつ0~40pxまで用意するなど。(処理の中で!important宣言が使われることが多い)
統合用ファイル
各レイヤーの中に設置したファイルを読み込むために、app.scssというとりまとめようのファイルを作成します。
その中に@importで各ファイルを読み込んでいきます。
ファイルの冒頭に「_(アンダースコア)」をつける
各レイヤーの配下に配置するファイル名の冒頭に「_(アンダースコア)」をつけます。
ファイル名の冒頭に「_」をつけることで、SASSをCSSにコンパイルしないようにする指示となります。
コンパイルするファイルは統合用のapp.scssファイルのみです。
レイヤー構造の例
FLOCSSのレイヤー構造は以下のようになります。
中に設置してあるファイルは例です。
sass
│
├── foundation
│ ├── _base.scss
│ └── _reset.scss
|
├── layout
│ ├── _footer.scss
│ ├── _header.scss
│ ├── _main.scss
│ └── _sidebar.scss
|
└── object
│ ├── component
│ │ ├── _button.scss
│ │ ├── _dialog.scss
│ │ ├── _grid.scss
│ │ └── _media.scss
│ │
│ ├── project
│ │ ├── _articles.scss
│ │ ├── _comments.scss
│ │ ├── _gallery.scss
│ │ └── _profile.scss
│ │
│ └── utility
│ ├── _align.scss
│ ├── _clearfix.scss
│ ├── _margin.scss
│ ├── _position.scss
│ ├── _size.scss
│ └── _text.scss
│
└── app.scss
各レイヤーのスタイル適用の優先度
なお6つのレイヤーは上が最もスタイルの適用優先度が低く、下に行くほど優先度が高くなっていきます。
弱い
F: Fundation
L: Layout
C: ObjectのComponent
P: ObjectのProject
U: ObjectのUtility
強い
統合用ファイルapp.scssの記述例
統合用(コンパイル用)のapp.scssファイルでは、CSSを適用する順番を守るため、レイヤーの順番に従ってファイルを読み込んでいきます。
実例
上記のレイヤー構造の例に従ってapp.scssの中で各ファイルを読み込むと以下のようになります。
// ==========================================================================
// Foundation
// ==========================================================================
@import "foundation/_reset";
@import "foundation/_base";
// ==========================================================================
// Layout
// ==========================================================================
@import "layout/_footer";
@import "layout/_header";
@import "layout/_main";
@import "layout/_sidebar";
// ==========================================================================
// Object
// ==========================================================================
// -----------------------------------------------------------------
// Component
// -----------------------------------------------------------------
@import "object/component/_button";
@import "object/component/_dialog";
@import "object/component/_grid";
@import "object/component/_media";
// -----------------------------------------------------------------
// Project
// -----------------------------------------------------------------
@import "object/project/_articles";
@import "object/project/_comments";
@import "object/project/_gallery";
@import "object/project/_profile";
// -----------------------------------------------------------------
// Utility
// -----------------------------------------------------------------
@import "object/utility/_align";
@import "object/utility/_clearfix";
@import "object/utility/_margin";
@import "object/utility/_position";
@import "object/utility/_size";
@import "object/utility/_text";
@importとは何か?
@importとはSASSの記述方法で、以下のようにすることでSASS(.scss, .sass)やCSS(.css)ファイルを読み込むことができます。
@import "ファイルパス"
実例
例えば以下のパスにある「_align.scss」を読み込むとします。
sass > object > utility > _align.scss
この場合、app.scssでの記述は以下のようになります。
@import(object/utility/align)
「_align.scss」の「_」と「.scss」を省略しています。
命名規則
FLOCSSを使う時はHTMLタグに適用するクラス名に命名規則があります。
考えるべき命名規則は大きく次の2つに分かれます。
BEMとは既に提唱されているCSSの命名規則です。(詳しくは後述hします)
FLOCSSでは、レイヤー名をクラス名のプレフィックス(接頭語)で指定し、モジュールや適用するスタイルの内容をBEMの命名規則に則ってサフィックス(接尾語)で指定します。
レイヤー毎の命名規則
レイヤー毎の命名規則では、WEBページや各要素が分類されるレイヤーに従って、クラス名の冒頭に各レイヤーの冒頭位置文字を記載します。
fundationレイヤーでは、bodyやタグなどの基本設定を記述するため、クラス名を指定することはありません。
layoutレイヤーのみid名を使うことも許容されています。
なお、選択中などの状態を表すものは「.is-」をつかって記述します。
レイヤー | 接頭語 | セレクタ(クラス名)の例 |
---|---|---|
fundation | なし | body { margin: 0 } |
layout | .l または #l | .l-footer |
component | .c | .c-label |
project | .p | .p-profile |
utility | .u | .u-black |
状態 | .is | .is-selected |
BEMによる命名規則
BEMとは何か?
BEMとは「Block Element Modifier」の略で、CSSを適用するためのクラス名(セレクタ名)の付け方を定義したものです。
名前が指すとおり「Block(ブロック)」「Element(エレメント)」「Modifier(モデファイヤ)」の3つの機能に分けて命名していくルールです。
Yandex(ヤンデックス)というロシアの検索エンジンを開発する人たちによって使われたのが最初と言われています。
(参考)Wikipedia Block, Element, Modifier
Block, Element, Modifierとは何か?
BEMの「Block(ブロック)」「Element(エレメント)」「Modifier(モデファイヤ)」はそれぞれ、以下のように定義されています。
Block(ブロック)とは何か?
Block(ブロック)は大きな一つの塊・機能です。例えば、レビューなどの複数のタグで構成される一番外側のタグ(ラッパー)に付けるのがBlock名です。
なお、FLOCSSではBlockの命名ルールはレイヤーの命名ルールになります。
また、ブロック名がユーザー詳細(user detail)といったように複数の単語になる場合は、ハイフンでつないで「user-detail」とします。
Element(エレメント)とは何か?
Element(エレメント)はBlockの構成要素です。例えば、レビューの中に、ユーザーアイコン、プロフィール、レーティング、コメントなどの要素があります。それぞれが一つ一つのElementとなります。
アンダーバー2つを使って「 __エレメント名 」として記述します。
Modifier(モディファイヤ)とは何か?
Modifier(モディファイヤ)は色やサイズを変えるといった微調整をするときに指定する命名規則です。例えば、レビューの中のアイコンに赤色を適用するといった場合にModefierを使います。
ハイフン2つを使って「 –モデファイヤ名 」として記述します。
BEMの命名規則一覧と実例(FLOCSSの場合)
BEMの命名規則をまとめると以下のようになります。
項目 | 内容 | 命名規則 | クラス名の例 |
---|---|---|---|
Block | 独立して再利用できる要素 | FLOCSSの規則に沿う | c-review |
Element | Blockの中にのみ存在できる要素 | __ (アンダーバー2つ) | c-review__icon |
Modifier | BlockやElementを修飾する | — (ハイフン2つ) | c-review__icon–red |
FLOCSSにおけるBEMの仕様
SASS(.scss)でFLOCSSを使ってBEMの命名規則を適用すると以下のような記述になります。
.c-key-visual {
background-repeat: no-repeat;
background-position: center;
background-size: cover;
height: 340px;
&__title{
font-size: 36px;
font-weight: bold;
}
&__lead{
font-size: 24px;
}
&__title--black,
&__lead--black {
color: #333;
}
}
- Block(ブロック):
c-key-visual
- Element(エレメント):
__title
,__lead
- Modifier(モデファイヤ):
--black
なお、HTMLタグの例は以下のようになります。
<div class="c-key-visual">
<img src="../images/kv.jpg">
<p class="c-key-visual__title c-key-visual__title--black">タイトル</p>
<p class="c-key-visual__lead">リード文</p>
</div>
modifierかutilityどちらを使うべきか?
FLOCSSを使っていると、既存のモジュールに追加でちょっとした修正を加えたいときに、「modifier」か「utility」のどちらを使うべきかで悩むことがあるかもしれません。
例えば、あるページの見出しに「margin-top: 8px」を設定するといった場合です。
そういったときは、他のページや要素でも使う頻度が高い場合は、utilityとしてそのプロパティを登録します。
特定のページやブロック、エレメント内などのみで使い、あまり頻出しない場合はmodefierとして「–モデファイヤ名」で新たなクラスを作成します。
例えば、marginの場合は、他の要素でも指定することが多いためutilityに登録しておきます。その際「_margin.scss」というファイル名で、複数のマージン設定を用意しておくのが一般的です。
utilityの例
utility
├── _align.scss
├── _clearfix.scss
├── _margin.scss
├── _padding.scss
├── _position.scss
├── _size.scss
└── _text.scss
FLOCSS使用時の注意点
FLOCSSを使うときの注意点は以下のようになります。
SASSに同じブロック名を複数記載しない
FLOCSSでは意図しないCSSの適用を避けるため。SASS(.scss)の中に複数のブロック名をセレクタとして指定しないようにします。
OKな書き方
例えば、.c-reviewに対する処理を記述する場合は以下のように「.c-review」を一つだけ記述します。
.c-review{
display: flex;
position: relative;
color: #333;
background-color: lightyellow;
&__icon{
font-size: 12px;
position: absolute;
top: 10px;
&--black{
color: black;
}
}
p {
font-size: 16px;
}
}
NGな書き方
例えば、「.c-review」が何度も登場すると、記述が分散してわかりにくくなってしまいます。
.c-review {
display: flex;
position: relative;
}
.c-review__icon {
font-size: 12px;
position: absolute;
top: 10px;
}
.c-review {
color: #333;
background-color: lightyellow;
}
.c-review__icon--black {
color: black;
}
.c-review p {
font-size: 16px;
}
モジュール内で完結しない@extendを使用しない
SASSの@extendを使うと、指定したセレクタの処理の内容を呼び出すことができます。
ところが、これを他のファイルや、呼び出したファイルの中などで行ってしまうと、FLOCSSの構成・設計は破綻し、カスケーディングも複雑になってしまう可能性が高いため、@extendは一つのファイル内に留めておきます。
▼モジュール内なら使用OK
.button {
display: inline-block;
padding: 0.5em 1em;
cursor: pointer;
}
.button--primary {
@extend .button
background-color: #CCAA00;
color: #FFFFFF;
}
.button--secondary {
@extend .button
background-color: #FFCC00;
}
ファイル名の冒頭に_をつける
統合ファイル(app.scss)を除いて、SASSからCSSへのコンパイルを避けるため、ファイル名の冒頭に_
をつけます。
統括ファイル(app.scss)の@import順序を変えない
FLOCSSのカスケーディング(スタイル適用順序)を壊さないようにするためapp.scssの中で読み込むファイルの順番はレイヤーの順番とします。
(参考)CSSプロパティの記述順序
CSSを記述する場合にも、どういったプロパティから順番に記述していくかをある程度決めておくと、各処理毎に統一感が出て可読性が上がります。
例えば、以下のよな優先順位にします。(決まりはないので自由にオリジナルルールを作ってください)
FLOCSSの各ファイルの実例
参考として、ページの速度改善対応を施したFLOCSSの構造と各ファイルの例を記載しておきます。
通常、コンパイル用のファイルはapp.scssの一つにまとめますが、ブラウザの読み込み速度を上げるために、コンパイル用のファイルをファーストビューとなる全体の共通部分と、ファーストビュー以外のページ毎にまとめた二種類に分けて読み込みます。
ファーストビューで使用するスタイルの内容はheadタグの中のstyleタグに直接入れます。
ファーストビュー以外で使用するスタイルはbodyの直前でlinkタグを使って読み込みます。
こうすることで、ブラウザが1つのCSSファイルを読み込む時間が早くなり、レンダリングブロックによる読み込み遅延が発生しにくくなります。
ファーストビューの読み込み速度を上げるSEO対策です。
FLOCSSの構造
FLOCSSの各レイヤーとファイルは以下のようにします。
foundation サイト全体で共通のデフォルト設定。
├ _normalize.scss 不要なデフォルトスタイルをリセット(reset.cssよりも軽量)
├ _base.scss 全体で共通のリセットスタイル
├ _variable.scss 変数をまとめるファイル
├ _mixin.scss mixinをまとめるファイル
|
layout 全体で共通のページのレイアウト
├ grid グリッド関連のCSSを保存(googleのマテリアルデザインを使用)
├ _grid.scss
├ _mixin.scss
├ _variable.scss
├ _container.scss
├ _header.scss
├ _footer.scss
|
object プロジェクト毎や再利用可能など細かいスタイルを保存するディレクトリ
├ component いくつかのページで共通するモジュール
├ _button.scss
├ _link.scss
├ …
|
├ project ページ毎に固有のモジュール(他のページで使わない)
├ top
├ _card.scss
├ _profile.scss
├ _ページ名…
|
├ utility 強制的に適用するスタイル(!importantがつくもの)
├ _display.scss
├ _margin.scss
├ _padding.scss
├ _typography.scss
|
├ common_inview.scss 全体で共通のファーストビュー用のスタイル。
├ common_overview.scss 全体で共通のファーストビュー以外のスタイル。
├ top_inview.scss トップページ固有のファーストビュー用のスタイル。
├ top_overview.scss トップページ固有のファーストビュー以外のスタイル。
├ ページ名_inview.scss ページ毎のファーストビュー用のスタイルをまとめたコンパイル用のファイル。
├ ページ名_poverview.scss ページ毎のファーストビュー以外のスタイルをまとめたコンパイル用のファイル。
....
foundation :サイト全体で共通のデフォルト設定
foundationには以下のファイルを設置しています。
foundation サイト全体で共通のデフォルト設定。
├ _normalize.scss 不要なデフォルトスタイルをリセット(reset.cssよりも軽量)
├ _base.scss 全体で共通のリセットスタイル
├ _variable.scss 変数をまとめるファイル
├ _mixin.scss mixinをまとめるファイル
_normalize.scss
hタグなどスタイルを初期化します。MIT Licenseのコードを使用しています。公式ページからダウンロードできます。
normalize.cssとreset.cssの違い
normalize.cssはreset.cssと異なり有効なスタイルを残しています。
_base.scss
全体で共通となるスタイルを指定しています。
html {
height: 100%;
font-size: 62.5%;
}
body {
font-family: 'YuGothic', 'Noto Sans JP', 'Hiragino Kaku Gothic Pro', 'メイリオ', Meiryo, 'Roboto', Roboto, Osaka, sans-serif;
display: flex;
flex-flow: column;
min-height: 100vh;
color: text-color(base);
}
main { flex: 1; }
a {
color: text-color(link01);
text-decoration: none;
cursor: pointer;
}
button {
appearance: none;
padding: 0;
background-color: transparent;
border: none;
outline: none;
cursor: pointer;
}
html, body,
p, ol, ul, li, dl, dt, dd,
blockquote, figure, fieldset, legend, textarea, pre, iframe,
h1, h2, h3, h4, h5, h6, hr {
margin: 0;
padding: 0;
}
ul, ol, li {
list-style: none;
}
・text-color(変数名)
変数名で値を呼び出す記述です。変数は別ファイルのvariable.scssに記述してあります。
_variable.scss
変数を記述するファイルです。
// size
$sizes: tb, sp;
// font size
$fontSizes: 10, 12, 14, 16, 18, 20, 22, 24, 26, 28;
// text line
$lines: 2, 3, 4;
// space
$spaces: 0, 8, 16, 24, 32, 40;
// breakpoint
$breakpoints: (
pc: 1280px
tb: 960px,
sp: 560px,
min: 360px,
) !default;
// key color
$key-colors: (
pink01: #f47aa9,
pink02: #cb678d,
black: #333,
white: #fff,
);
@function key-color($key) {
@return map-get($key-colors, $key);
}
// text color
$text-colors: (
base: #333,
white: #fff,
gray: #999,
link01: #0074ad,
link02: #666,
link02-hover: #333,
);
@function text-color($key) {
@return map-get($text-colors, $key);
}
// btn color
$btn-colors: (
normal: #fff,
normal-hover: #333,
cv: #f47aa9,
cv-hover: rgba(244,122,169,.8),
);
@function btn-color($key) {
@return map-get($btn-colors, $key);
}
text-colorやkey-colorは関数を定義し名前で色を呼び出せるようにしています。
関数の使い方
map-getメソッドを使うと複数のプロパティ(Key-Value形式のデータ)から指定したキー名をと一致する値を抜き出すことができます。
関数を用意して名前を付け、キー名を引数で渡します。
@function 関数名(引数) {
@return map-get(検索元の変数名, 引数で渡した変数名)
}
_mixin.scss
mixin(どこでも呼び出せるコンポーネント)を定義するファイルです。
メディアクエリを定義するために使用しています。
$breakpoint-down: (
'pc': 'screen and (max-width: #{map-get($breakpoints, 'pc')})',
'tb': 'screen and (max-width: #{map-get($breakpoints, 'tb')})',
'sp': 'screen and (max-width: #{map-get($breakpoints, 'sp')})',
'min': 'screen and (max-width: #{map-get($breakpoints, 'min')})',
) !default;
@mixin mq($breakpoint: tb) {
@media #{map-get($breakpoint-down, $breakpoint)} {
@content;
}
}
$breakpoint-down
という変数の中でメディアクエリのパターンを作成しています。
画面幅はvariable.scssに記述してあります。
// breakpoint
$breakpoints: (
pc: 1280px
tb: 960px,
sp: 560px,
min: 360px,
) !default;
mqという名前のmixinを作成し、pc, tb, sp, minを指定することで対応するメディアクエリを表示できるようになります。
なお、scssでの呼び出しは@include mixin名(引数)
となります。
・#{ }
変数をそのまま表示する記述です。
・@content
mixinを呼び出した時に{ }の中に記述した内容を@contentの部分に表示します。
layout :全体で共通のページのレイアウト
layoutレイヤーには全体で共通となるヘッダーやフッター、メインコンテンツエリア、グリッドシステムに関するファイルを保存しています。
layout 全体で共通のページのレイアウト
├ grid グリッド関連のCSSを保存(googleのマテリアルデザインを使用)
├ _grid.scss
├ _mixin.scss
├ _variable.scss
├ _container.scss
├ _header.scss
├ _footer.scss
なお、グリッドシステムはgoogleのマテリアルデザインをカスタムしたものを使用しています。
グリッドシステムに関するmixinとvariableもこのフォルダの中にファイルを作成しています。
_grid.scss
_grid.scssはグリッドシステムの基本構成を記述したファイルです。変数やmixinは別ファイルから読み込んでいます。
/*
* layout/grid/grid.scss
*/
@import './variables';
@import './mixins';
:root {
@each $size in map-keys($grid-columns) {
--grid-margin-#{$size}: #{map-get($grid-default-margin, $size)};
--grid-gutter-#{$size}: #{map-get($grid-default-gutter, $size)};
--grid-column-width-#{$size}: #{map-get($grid-column-width, $size)};
}
}
// grid
.l-grid {
max-width: 1024px;
transition: all 0.25s;
@each $size in map-keys($grid-columns) {
@include grid-media-query_($size) {
$margin: map-get($grid-default-margin, $size);
$gutter: map-get($grid-default-gutter, $size);
@include grid($size, $margin, $gutter);
}
}
}
.l-grid-cell {
// default style
min-height: 25px;
// select the upper breakpoint
$upper-breakpoint: nth(map-keys($grid-columns), 1);
@each $size in map-keys($grid-columns) {
@include grid-media-query_($size) {
$gutter: map-get($grid-default-gutter, $size);
@include grid-cell($size, $grid-default-column-span, $gutter);
@for $span from 1 through map-get($grid-columns, $upper-breakpoint) {
// Span classes.
// stylelint-disable max-nesting-depth
@at-root .l-grid-cell--span#{$span},
.l-grid-cell--span#{$span}-#{$size} {
@include grid-cell-span_($size, $span, $gutter);
}
}
}
}
}
_variables.scss
グリッドシステム用の変数をまとめたファイルです。
ブレイクポイントやカラム数などはプロジェクトに合わせ変更します。
$grid-breakpoints: (
desktop: 1140px,
tablet: 641px,
phone: 0,
) !default;
$grid-columns: (
desktop: 12,
tablet: 6,
phone: 4,
) !default;
$grid-default-margin: (
desktop: 24px,
tablet: 24px,
phone: 16px,
) !default;
$grid-default-gutter: (
desktop: 24px,
tablet: 24px,
phone: 16px,
) !default;
$grid-column-width: (
desktop: 72px,
tablet: 72px,
phone: 72px,
) !default;
$grid-default-column-span: 4 !default;
$grid-max-width: null !default;
_mixins.scss
グリッドシステム用のmixinをまとめたファイルです。
@import './variables';
// returns the lower grid boundary or null if the smallest grid is selected
@function grid-breakpoint-min($size) {
@if not map-has-key($grid-columns, $size) {
@error "Invalid style specified! Choose one of #{map-keys($grid-columns)}";
}
$min: map-get($grid-breakpoints, $size);
@return if($min > 0, $min, null);
}
// returns the upper grid boundary or null if the largest grid is selected
@function grid-breakpoint-max($size) {
@if not map-has-key($grid-columns, $size) {
@error "Invalid style specified! Choose one of #{map-keys($grid-columns)}";
}
$names: map-keys($grid-columns);
$n: index($names, $size);
$prev: if($n > 1, nth($names, $n - 1), null);
@return if($prev, (grid-breakpoint-min($prev) - 1px), null);
}
// Private mixins, meant for internal use.
@mixin grid-media-query_($size) {
@if not map-has-key($grid-columns, $size) {
@error "Invalid style specified! Choose one of #{map-keys($grid-columns)}";
}
$min: grid-breakpoint-min($size);
$max: grid-breakpoint-max($size);
@if $min == null and $max != null {
// Phone
@media (max-width: $max) {
@content;
}
} @else if $min != null and $max != null {
// Tablet
@media (min-width: $min) and (max-width: $max) {
@content;
}
} @else if $min != null and $max == null {
// Desktop
@media (min-width: $min) {
@content;
}
} @else {
// Fallback - no breakpoints defined
@content;
}
}
@mixin grid-cell-span_($size, $span, $gutter) {
@if not map-has-key($grid-columns, $size) {
@error "Invalid style specified! Choose one of #{map-keys($grid-columns)}";
}
$percent: percentage($span / map-get($grid-columns, $size));
@if $percent > 100% {
$percent: 100%;
}
width: calc(#{$percent} - #{$gutter});
width: calc(#{$percent} - var(--grid-gutter-#{$size}, #{$gutter}));
@supports (display: grid) {
width: auto;
grid-column-end: span min($span, map-get($grid-columns, $size));
}
}
// Public mixins, meant for developer usage.
@mixin grid($size, $margin, $gutter) {
@if not map-has-key($grid-columns, $size) {
@error "Invalid style specified! Choose one of #{map-keys($grid-columns)}";
}
display: flex;
flex-flow: row wrap;
align-items: stretch;
margin: -$gutter / 2;
margin: calc(var(--grid-gutter-#{$size}, #{$gutter}) / 2 * -1);
@supports (display: grid) {
display: grid;
margin: 0;
grid-gap: $gutter;
grid-gap: var(--grid-gutter-#{$size}, $gutter);
grid-template-columns: repeat(map-get($grid-columns, $size), minmax(0, 1fr));
}
}
@mixin grid-cell($size, $default-span, $gutter) {
@if not map-has-key($grid-columns, $size) {
@error "Invalid style specified! Choose one of #{map-keys($grid-columns)}";
}
@include grid-cell-span_($size, $default-span, $gutter);
box-sizing: border-box;
margin: $gutter / 2;
margin: calc(var(--grid-gutter-#{$size}, #{$gutter}) / 2);
@supports (display: grid) {
margin: 0;
}
}
_container.scss
共通のレイアウトとなるメインコンテンツエリアのスタイルをまとめたファイルです。プロジェクト毎に必要に応じて編集します。
.l-container {
max-width: 960px;
margin: 0 auto;
padding: 0 24px;
@include mq(tb) {
width: 100%;
}
}
.l-list {
max-width: 640px;
margin: 0 auto;
padding: 0 0 24px;
@include mq(sp) {
padding: 0 16px 16px;
}
}
.l-error {
max-width: 640px;
margin: 0 auto;
padding: 0 0 24px;
@include mq(tb) {
padding: 0 0 16px;
}
@include mq(sp) {
padding: 0 16px 16px;
}
}
.l-btn-container,
.l-top-btn-container {
padding: 48px 0;
}
.l-btn-container {
@include mq(sp) {
padding: 24px 0;
}
}
.l-top-btn-container {
background-color: #f5f5f5;
@include mq(tb) {
padding: 24px 0;
}
}
_header.scss
共通のヘッダーのスタイルをまとめたファイルです。プロジェクト毎に必要に応じて編集します。
#l-header {
width: 100%;
height: 130px;
background-color: #1a1a1a;
@include mq(sp) {
height: auto;
}
}
.l-header-logo {
height: 130px;
padding-top: 27px;
border-bottom: 4px solid key-color(pink02);
@include mq(sp) {
height: 67px;
padding: 14px 16px;
border-bottom: 2px solid key-color(pink02);
}
&__image {
display: block;
width: 267px;
margin: 0 auto;
@include mq(sp) {
width: 133px;
margin: 0;
}
img {
vertical-align: bottom;
width: 100%;
}
}
}
.l-top-text {
padding-bottom: 16px;
background-color: #f5f5f5;
@include mq(tb) {
padding: 0 16px 16px;
}
@include mq(sp) {
padding: 8px 16px;
}
p {
width: 960px;
margin: 0 auto;
color: text-color(base);
font-size: 1.4rem;
line-height: 1;
@include mq(tb) {
width: 100%;
}
@include mq(sp) {
margin: auto;
font-size: 1.2rem;
line-height: 1.5;
}
}
}
_footer.scss
共通のフッターのスタイルをまとめたファイルです。プロジェクト毎に必要に応じて編集します。
#l-footer {
max-width: 100%;
background-color: #1a1a1a;
}
.l-footer-conainer {
max-width: 1000px;
margin: 0 auto;
padding: 48px 0 24px;
@include mq(tb) {
padding: 48px 16px 24px;
}
@include mq(sp) {
width: 100%;
padding: 68px 0 24px;
border-top: 4px solid key-color(pink02);
}
}
.l-footer-logo {
display: block;
width: 160px;
margin-bottom: 20px;
@include mq(sp) {
width: 120px;
margin: 0 auto;
}
img {
vertical-align: bottom;
width: 100%;
}
}
.l-footer-links {
display: flex;
flex-wrap: wrap;
margin-bottom: 20px;
@include mq(sp) {
margin: 24px 0;
// border: 2px solid #fff;
}
a, span {
color: #fff;
font-size: 13px;
line-height: 24px;
}
a {
@include mq(sp) {
width: 50%;
height: 58px;
padding: calc(33px / 2) 0;
border-color: #fff;
border-style: solid;
font-size: 1.2rem;
text-align: center;
line-height: 24px;
}
}
span {
padding: 0 4px;
@include mq(sp) {
display: none;
}
}
}
.l-footer-copyright {
@include mq(sp) {
text-align: center;
}
a {
color: #fff;
font-size: 1.4rem;
line-height: 1;
@include mq(sp) {
font-size: 1.2rem;
}
}
}
object
プロジェクト毎に固有となるページのスタイルや、再利用可能なモジュールなどを保存するディレクトリです。配下にcomponent, project, utilityの3つのディレクトリをもちます。
- component : 複数のページで使い回しをするモジュールなどのスタイル。
- project : ページやレイアウト毎に固有のモジュールのスタイル。
- utility : 色やマージンの変更などちょっとした変更を加えるためのスタイル。
component
componentは複数のページで使い回しをするモジュールなどのスタイルを保存するためのディレクトリです。
button、link、error、breadcrumbなどのコンポーネント用のファイルを作成します。
なお、クラス名は冒頭に「c-
」を付けて記述します。
├ component いくつかのページで共通するモジュール
├ _button.scss
├ _link.scss
├ …
_btn.scss
.c-btn {
position: relative;
display: block;
width: 376px;
height: 52px;
margin: 0 auto;
background-color: #fff;
border: 2px solid #333;
border-radius: 26px;
color: text-color(base);
font-size: 1.6rem;
font-weight: bold;
text-align: center;
line-height: 48px;
transition: all .25s;
&__arrow {
position: absolute;
top: 50%;
right: 18px;
width: 20px;
height: 20px;
margin-top: -10px;
background-color: key-color(pink01);
border-radius: 50%;
@include mq(sp) {
right: 16px;
}
&::before {
content: '';
display: block;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 3px;
width: 6px;
height: 6px;
margin: auto;
border-top: 1px solid #fff;
border-right: 1px solid #fff;
transform: rotate(45deg);
}
}
&:hover {
background-color: #333;
color: text-color(white);
}
@include mq(sp) {
width: 264px;
height: 40px;
border: 2px solid #333;
border-radius: 20px;
font-size: 1.4rem;
line-height: 36px;
}
}
.c-btn-to-top {
position: fixed;
z-index: 5;
right: 20px;
bottom: 50px;
width: 40px;
height: 40px;
background-color: #373737;
transition: opacity 0.3s cubic-bezier(.13, .78, .38, .98);
border-radius: 50%;
cursor: pointer;
@include mq(sp) {
right: 16px;
bottom: 16px;
}
&:hover { opacity: .5; }
&::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
display: block;
width: 10px;
height: 10px;
margin-top: -3px;
margin-left: -5px;
border-width: 0;
border-top: 2px solid #fff;
border-left: 2px solid #fff;
transform: rotate(45deg);
}
}
_breadcrumb.scss
.c-breadcrumb {
padding: 16px 0;
background-color: #f5f5f5;
@include mq(tb) {
padding: 16px;
}
@include mq(sp) {
overflow: scroll;
white-space: nowrap;
padding: 8px 16px;
background-color: #fff;
}
&--narrow {
background-color: #fff;
.c-breadcrumb-list {
width: 640px;
@include mq(sp) {
width: 100%;
}
}
}
}
.c-breadcrumb-list {
width: 960px;
margin: 0 auto;
@include mq(tb) {
width: 100%;
}
&__item {
position: relative;
display: inline-block;
color: text-color(link02);
font-size: 1.3rem;
@include mq(sp) {
padding-right: 18px;
font-size: 1.2rem;
}
&:not(:last-child) {
padding-right: 18px;
&::after {
content: '';
display: block;
position: absolute;
top: 50%;
right: 6px;
width: 8px;
height: 8px;
margin-top: -5px;
border-top: 1px solid #666;
border-right: 1px solid #666;
transform: rotate(45deg);
@include mq(sp) {
width: 6px;
height: 6px;
margin-top: -4px;
}
}
}
a {
color: text-color(link02);
&:hover {
color: text-color(link02-hover);
text-decoration: underline;
}
}
}
}
_error.scss
.p-error-header {
margin: 32px 0 16px;
font-size: 3rem;
font-weight: bold;
line-height: 1;
@include mq(sp) {
margin: 8px 0 16px;
font-size: 1.7rem;
}
}
.p-error-text {
font-size: 1.7rem;
line-height: 2;
@include mq(sp) {
font-size: 1.5rem;
}
}
_link.scss
.c-link01,
.c-link02 {
&:hover {
text-decoration: underline;
}
}
.c-link02 {
color: link-color(link02);
&:hover {
color: link-color(link02-hover);
}
}
_pagenation.scss
.c-pagination {
margin: 24px 0 48px;
@include mq(sp) {
margin: 8px 0 24px;
}
}
.c-pagination-list {
display: flex;
align-items: center;
justify-content: center;
}
.c-pagination-item {
display: inline-block;
margin: 0 4px;
background-color: #fff;
border: 2px solid #333;
border-radius: 50%;
transition: .25s all;
a {
position: relative;
display: block;
width: 32px;
height: 32px;
color: text-color(base);
font-size: 1.4rem;
font-weight: bold;
text-align: center;
line-height: 32px;
&:disabled {
cursor: default;
}
}
&:hover {
background-color: #ddd;
}
// prev & next
&.is-prev,
&.is-next,
&.is-ellipsis {
a { font-size: 0; }
}
&.is-prev,
&.is-next {
a::before {
content: '';
display: block;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 8px;
height: 8px;
margin: auto;
border-top: 1px solid #222;
border-left: 1px solid #222;
}
}
&.is-prev {
a::before {
left: 4px;
transform: rotate(-45deg);
}
}
&.is-next {
a::before {
right: 4px;
transform: rotate(135deg);
}
}
// ellipsis
&.is-ellipsis {
.point-icon {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
justify-content: space-between;
width: 13px;
height: 3px;
margin: auto;
}
.point {
display: block;
width: 3px;
height: 3px;
background-color: #222;
border-radius: 50%;
}
&:hover {
background-color: #fff;
}
}
// current
&.is-current {
background-color: #333;
a { color: key-color(pink01) !important; }
}
// disabled
&.is-disabled {
&.is-prev,
&.is-next {
background-color: #fff;
}
a { cursor: default; }
}
}
_title.scss
.c-title {
margin-bottom: 24px;
color: text-color(base);
font-size: 3rem;
font-weight: bold;
line-height: 1;
@include mq(sp) {
margin-bottom: 16px;
font-size: 2.2rem;
}
}
.c-title-new-article {
position: relative;
margin-bottom: 24px;
padding-bottom: 14px;
color: text-color(base);
font-size: 2.6rem;
font-weight: bold;
line-height: 1;
@include mq(sp) {
margin-bottom: 16px;
padding-bottom: 12px;
font-size: 2rem;
}
&::before,
&::after {
content: '';
display: block;
position: absolute;
bottom: 0;
left: 0;
height: 6px;
@include mq(sp) {
height: 4px;
}
}
&::before {
width: 150px;
background-color: key-color(pink01);
@include mq(sp) {
width: 100%;
}
}
&::after {
width: 36px;
background-color: #222;
@include mq(sp) {
width: 66px;
}
}
}
など。
project
プロジェクト毎に固有のページやレイアウト毎に固有のモジュールのスタイルを記述したファイルを保存します。
ページやレイアウト毎にディレクトリを作成してその中にファイルを作成していきます。
例えば、ユーザー一覧、ユーザー詳細、趣味のページがある場合以下のようなディレクトリ構造になります。
├ projects
├ user
├ _user-list.scss
├ _user-detail.scss
├ _user-hobby.scss
なお、要素のクラス名は冒頭に「p-」をつけます。
utility
utilityはmarginやpaddingなど微調整をするスタイルを保存するディレクトリです。
強制的に適用のが基本となるため「!important」を設定しています。メディアクエリ毎のスタイルもまとめています。
- _space.scss :marginとpaddingに関するスタイル
- _typography.scss :textに関するスタイル
- _display.scss :displayに関するスタイル
対象となる要素のクラス名には冒頭に「u-
」を付けて記述します。
├ utility 強制的に適用するスタイル(!importantがつくもの)
├ _display.scss
├ _margin.scss
├ _padding.scss
├ _typography.scss
_space.scss
marginとpaddingに関するスタイルです。
一定間隔のmarginとpaddingを用意します。間隔は_variable.scssで定義しています。(例: u-mt8
でmargin-top: 8px !important
となるセレクタを作成する)
.u-mxa {
margin-left: auto !important;
margin-right: auto !important;
}
@each $space in $spaces {
// margin
.u-m#{ $space } {
margin: #{ $space }px !important;
}
.u-mx#{ $space } {
margin-left: #{ $space }px !important;
margin-right: #{ $space }px !important;
}
.u-my#{ $space } {
margin-top: #{ $space }px !important;
margin-bottom: #{ $space }px !important;
}
.u-mt#{ $space } {
margin-top: #{ $space }px !important;
}
.u-mb#{ $space } {
margin-bottom: #{ $space }px !important;
}
.u-ml#{ $space } {
margin-left: #{ $space }px !important;
}
.u-mr#{ $space } {
margin-right: #{ $space }px !important;
}
// padding
.u-p#{ $space } {
padding: #{ $space }px !important;
}
.u-px#{ $space } {
padding-left: #{ $space }px !important;
padding-right: #{ $space }px !important;
}
.u-py#{ $space } {
padding-top: #{ $space }px;
padding-bottom: #{ $space }px !important;
}
.u-pt#{ $space } {
padding-top: #{ $space }px !important;
}
.u-pb#{ $space } {
padding-bottom: #{ $space }px !important;
}
.u-pl#{ $space } {
padding-left: #{ $space }px !important;
}
.u-pr#{ $space } {
padding-right: #{ $space }px !important;
}
}
@each $size in $sizes {
@include mq($size) {
.u-mxa-#{ $size } {
margin-left: auto;
margin-right: auto;
}
@each $space in $spaces {
// margin
.u-m#{ $space }-#{ $size } {
margin: #{ $space }px !important;
}
.u-mx#{ $space }-#{ $size } {
margin-left: #{ $space }px !important;
margin-right: #{ $space }px !important;
}
.u-my#{ $space }-#{ $size } {
margin-top: #{ $space }px !important;
margin-bottom: #{ $space }px !important;
}
.u-mt#{ $space }-#{ $size } {
margin-top: #{ $space }px !important;
}
.u-mb#{ $space }-#{ $size } {
margin-bottom: #{ $space }px !important;
}
.u-ml#{ $space }-#{ $size } {
margin-left: #{ $space }px !important;
}
.u-mr#{ $space }-#{ $size } {
margin-right: #{ $space }px !important;
}
// padding
.u-p#{ $space }-#{ $size } {
padding: #{ $space }px !important;
}
.u-px#{ $space }-#{ $size } {
padding-left: #{ $space }px !important;
padding-right: #{ $space }px !important;
}
.u-py#{ $space }-#{ $size } {
padding-top: #{ $space }px !important;
padding-bottom: #{ $space }px !important;
}
.u-pt#{ $space }-#{ $size } {
padding-top: #{ $space }px !important;
}
.u-pb#{ $space }-#{ $size } {
padding-bottom: #{ $space }px !important;
}
.u-pl#{ $space }-#{ $size } {
padding-left: #{ $space }px !important;
}
.u-pr#{ $space }-#{ $size } {
padding-right: #{ $space }px !important;
}
}
}
}
なお、@eachを使うと指定した変数の要素を一つずつ抜き出す処理になります。
間隔は以下のように_variable.scssで定義しています。(ここでは8の倍数の間隔を使用)
// space
$spaces: 0, 8, 16, 24, 32, 40;
_typography.scss
textに関するスタイルをまとめています。font-weightやtext-alignなどのスタイルを定義しています。
// font weight
.u-fwn {
font-weight: normal !important;
}
.u-fwb {
font-weight: bold !important;
}
// text align
.u-tac {
text-align: center !important;
}
.u-tal {
text-align: left !important;
}
.u-tar {
text-align: right !important;
}
// text decoration
.u-underline {
text-decoration: underline !important;
}
// other
.u-text-truncate,
.u-text-clip {
overflow: hidden !important;
white-space: nowrap !important;
}
.u-text-truncate {
text-overflow: ellipsis !important;
}
.u-text-clip {
text-overflow: clip !important;
}
// line-clamp
.u-clamp1,
.u-clamp2,
.u-clamp3,
.u-clamp4 {
overflow: hidden !important;
text-overflow: ellipsis !important;
display: -webkit-box !important;
-webkit-box-orient: vertical !important;
}
.u-clamp1 { -webkit-line-clamp: 1 !important; }
.u-clamp2 { -webkit-line-clamp: 2 !important; }
.u-clamp3 { -webkit-line-clamp: 3 !important; }
.u-clamp4 { -webkit-line-clamp: 4 !important; }
// font size
@each $fontSize in $fontSizes {
.u-fs#{$fontSize} {
font-size: #{$fontSize / 10}rem !important;
}
}
@each $size in $sizes {
@include mq($size) {
@each $fontSize in $fontSizes {
.u-fs-#{$size}-#{$fontSize} {
font-size: #{$fontSize / 10}rem !important;
}
}
}
}
フォントサイズやメディアクエリ用のサイズは_variable.scssで定義しています。
// size
$sizes: tb, sp;
// font size
$fontSizes: 10, 12, 14, 16, 18, 20, 22, 24, 26, 28;
メディアクエリは_mixin@scssで定義しています。
_display.scss
displayに関するスタイルです。
.u-dn {
display: none !important;
}
.u-db {
display: block !important;
}
.u-dib {
display: inline-block !important;
}
.u-df {
display: flex !important;
}
@each $size in $sizes {
@include mq($size) {
.u-dn-#{$size} {
display: none !important;
}
.u-db-#{$size} {
display: block !important;
}
.u-dib-#{$size} {
display: inline-block !important;
}
.u-df-#{$size} {
display: flex !important;
}
}
}
メディアクエリ用のサイズは_variable.scssで定義しています。
// size
$sizes: tb, sp;
メディアクエリは_mixin@scssで定義しています。
コンパイル用のファイル
実際にコンパイルするファイルはsassディレクトリ直下にある「_
」のないファイルです。
├ common_inview.scss 全体で共通のファーストビュー用のスタイル。
├ common_overview.scss 全体で共通のファーストビュー以外のスタイル。
├ top_inview.scss トップページ固有のファーストビュー用のスタイル。
├ top_overview.scss トップページ固有のファーストビュー以外のスタイル。
├ (ページ名)_inview.scss
├ (ページ名)_overview.scss
...
inviewとoverviewとは何か?
読み込むファイルを1種類で読み込むのではなく、inview
とoverview
の2つに分けています。
これは、クリティカルCSSの考え方で以下のような分類になります。
これをすることでレンダリングブロックを回避し、ページの描画速度を改善することができます。
common_inview.scss
全体で共通となるファーストビューで必要なスタイルをまとめたファイルです。
@charset 'utf-8';
// foundation
@import "foundation/variable";
@import "foundation/mixin";
@import "foundation/normalize";
@import "foundation/base";
// layout
@import 'layout/grid/grid';
@import "layout/header";
@import "layout/container";
// object - component
@import "object/component/breadcrumb";
@import "object/component/title";
@import "object/component/btn";
@import "object/component/link";
@import "object/component/pagenation";
@import "object/component/error";
// object - project
// object - utility
@import "object/utility/display";
@import "object/utility/space";
@import "object/utility/typography";
common_overview.scss
全体で共通となるファーストビュー以外で必要なスタイルをまとめたファイルです。
@charset 'utf-8';
// foundation
@import "foundation/variable";
@import "foundation/mixin";
// layout
@import "layout/footer";
variableとmixinはコンパイル時に必要となるファイルです。
補足
必要に応じて、ページ毎にページ名_inview
、ページ名_overview
を作成します。