---
title: '2020/02/29 Sass/Scss 教學筆記: 建立flexbox排版為主的共用模組'
disqus: hackmd
---
2020/02/29 Sass/Scss 教學筆記: 建立flexbox排版為主的共用模組
===
綱要
[TOC]
為什麼每次更新Git時最好 npm install 一次?會出現什麼問題嗎?
---
同學們應該發現了最近課堂上出現了`gulp watch`時,node-sass要你更新depth 2的套件訊息,我們雖然可以照著錯誤訊息上的指示更新 node 相依套件或者跑一次`npm audit fix`。
接著你會看見 Git 突然顯示了`packge-lock.json`和`packge.json`兩隻檔案的變更(有時只會更新lock那隻),而你在修正了相依套件更新的問題後必須要將這個變更Push到Git上。
假如跟你同個專案的其他工程師更新了這些檔案,卻忘了或不知道更新的內容包含`packge-lock.json`和`packge.json`,那麼啟動環境時就會顯示缺漏特定模組的錯誤訊息、環境也跑不起來。或者更慘的狀況是環境能跑、功能卻無法顯示或不正常,而你們對外Release了產品新版本後才發現...XD
正確的觀念是,只要Git上的專案有任何新的變更,在你`Fast-forward-merge`或者直接將目標分支`merge`到自己的分支上時,一定要記得跑一次`npm install`來避免這樣的問題,千萬不要因為沒更新 Package 就略過這個步驟。
npm install 會幫我們做些什麼事
---
還記得剛開始這個專案時我們做過的`npm init`初始化得到的`package.json`嗎?
後頭為了gulp任務管理工具我們又安裝了其他相依套件:

通常我們不會把`node_modules`這麼肥大的檔案直接丟上Git,而是依賴`package.json`和`package-lock.json`中已安裝套件的記錄,透過`npm install`這個指令重新安裝回來,同專案的同事也可以利用這份json來讓開發環境同步,也可以將各套件的版號刪除上箭號「^」來鎖定版本。
Media Queries
---
以前我們都學過Media Query媒體查詢來做 RWD Breakpoint 判別特定寬度下 UI 要怎麼變化,而在Sass/Scss中,定義的方式會稍稍有些不同:
```sass=
$ipad_landscape: "screen and (max-width: 1024px) and (orientation: landscape) and (min-device-pixel-ratio: 2)";
@mixin ipad_landscape {
@media #{$ipad_landscape} {
@content;
}
}
.block {
background-color: #f90;
@include ipad_landscape {
background-color: #333;
}
}
```
```css=
/* 編譯後的CSS */
.block {
background-color: #f90;
}
@media screen and (max-width: 1024px) and (orientation: landscape) and (min-device-pixel-ratio: 2) {
.block {
background-color: #333;
}
}
```
建立flexBox排版邏輯共用模組
---
還記得2/8上課時我們提到:[你不應該把時間花在命名容器上,那是下等人做的事情](https://hackmd.io/@FortesHuang/SyKirdx78#%E4%BD%A0%E4%B8%8D%E6%87%89%E8%A9%B2%E6%8A%8A%E6%99%82%E9%96%93%E8%8A%B1%E5%9C%A8%E5%91%BD%E5%90%8D%E5%AE%B9%E5%99%A8%E4%B8%8A%EF%BC%8C%E9%82%A3%E6%98%AF%E4%B8%8B%E7%AD%89%E4%BA%BA%E5%81%9A%E7%9A%84%E4%BA%8B%E6%83%85)
而且將html的結構以組件的概念依照需求來做排列,為了達到這個理想的目的,今天起大約兩週時間就需要建構我們自己的 Code Library,結訓作業會派得上用場,之後也能整併到自己的公司專案裡馬上使用喔!
可以參考老師的Github範例來配置:
**style.scss**
[完整範例](https://github.com/fortes1219/sass_0208/tree/0222_homework/src/scss)
```sass=
/* 注意引用的順序喔 */
@import "_reset.scss";
@import "_variable.scss";
// @import "_commonStyle.scss"; 這隻將會抽離,各位用不到
@import "_flexbox.scss";
@import "_dataAttribute.scss";
@import "_mediaQueries.scss";
html {
touch-action: manipulation;
height: 100%;
body {
font-family: "Microsoft JhengHei", "Helvetica Neue", Helvetica, Arial, sans-serif;
touch-action: manipulation;
height: 100%;
}
}
```
**_reset.scss**
重設瀏覽器規則,跨裝置跨瀏覽器的必備作業。
```sass=
html, body, div, section, aside, figure, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, font, img, ins, kbd, q, s, samp,
small, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td {
margin: 0;
padding: 0;
border: 0;
outline: 0;
font-size: 100%;
vertical-align: baseline;
background: transparent;
text-decoration: none;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
/* remember to define focus styles! */
:focus {
outline: 0;
}
/* remember to highlight inserts somehow! */
ins {
text-decoration: none;
}
del {
text-decoration: line-through;
}
/* tables still need 'cellspacing="0"' in the markup */
table {
border-collapse: collapse;
border-spacing: 0;
}
.clearfix {
*zoom: 1;
}
.clearfix:before, .clearfix:after {
display: block;
content: "";
line-height: 0;
}
.clearfix:after {
clear: both;
}
```
**_variable.scss**
我們通常會將Sketch或者Figma專案上的色票設定、狀態樣式存放在這支檔案。
命名好的變數就可以直接在其他scss檔案上調用,當你有多樣板的需求(聖誕節、春節主題等)就可以非常彈性的擴充你的專案外觀。
```sass=
$h1FontSize: 1.85rem;
$white: #fff;
$darkGray: #37424C;
$dark: #37424C;
$red: #dc222a;
$blue: #2287DC;
$green: #1fc66b;
$lightGreen: #36f88d;
$defaultGray: #999;
$landGray: #C9CCCF;
$lightGray: #EEEFEF;
$riceWhite: #f9f9f9;
$gray: #d9d9d9;
$carrotOrange: #eb7428;
$manatee: #92989E;
$bgColor_lv1: linear-gradient(to right, #EB7428, #DC222A);
$bgColor_lv2: #1B2733;
//screen
$wideScreen: "only screen and(min-width: 1920px) and (min-height: 1024px) and (orientation: landscape)";
$largeScreen: "only screen and(max-width: 1920px) and(min-width: 1680px) and (max-height: 1080px) and (min-height: 1050px) and (orientation: landscape)";
```
**flexBox.scss**
本次課程的重點就是這隻排版邏輯,我們會定義出`flexbox`在**row**或**column**不同邏輯下的排列規則外,還會設置**指定每行顯示N個元素為**的規則,真正幫助你節省切版時間之外也保持更好的維護性。
```sass=
@mixin boxSizing {
box-sizing: border-box;
}
@mixin flexBox($direction, $alignItems, $justifyContent) {
display: flex;
flex-direction: $direction;
align-items: $alignItems;
justify-content: $justifyContent;
}
/* 指定一行顯示幾個元素,element UI採24分、Bootstrap則是12分,這裡用element UI */
@mixin dataRowCount {
@for $i from 1 through 24 {
[data-row-count="#{$i}"] {
.data_row {
width: calc(100% / #{$i});
}
}
}
}
@for $i from 1 through 24 {
[data-row-count="#{$i}"] {
.data_row {
// 使用計算屬性來切割寬度
width: calc(100% / #{$i});
}
}
}
/* 注意,假如你的專案有套用Boostrap的話,這裡的row請替換成別的名稱,會撞名 */
.row {
@include boxSizing;
position: relative;
width: 100%;
margin: 0;
@include dataRowCount;
&.full {
width: 100%;
height: 100%;
}
&.horizontal {
@include flexBox(row);
&.v_center {
@include flexAlign(center, flex-start);
}
&.h_center {
@include flexAlign(flex-start, center);
}
&.end {
@include flexAlign(center, flex-end);
}
&.bottom {
@include flexAlign(flex-end, center);
}
&.bottom_left {
@include flexAlign(flex-end, flex-start);
}
&.bottom_right {
@include flexAlign(flex-end, flex-end);
}
&.space {
justify-content: space-between;
}
}
&.vertical {
@include flexBox(column);
&.v_center {
@include flexAlign(flex-start, center);
}
&.h_center {
@include flexAlign(center, flex-start);
}
&.end_right {
@include flexAlign(flex-end, flex-end);
}
&.end_left {
@include flexAlign(flex-end, flex-start);
}
&.end {
@include flexAlign(flex-end, center);
}
}
&.normal {
@include flexAlign(flex-start, flex-start);
}
&.center {
@include flexAlign(center, center);
}
&.half {
width: 50%;
}
&.wrap {
flex-wrap: wrap;
}
&.space {
justify-content: space-between;
}
&.inset {
@include boxSizing;
padding: 1rem;
}
&.static {
/* 如果你不想被row中的width: 100%影響,加上這個屬性 */
width: auto;
}
}
```
**_dataAttribute.scss**
將它放置在`_flexBox.scss`後載入的理由就是為了要覆寫原先的樣式,我們不喜歡直接去改已經寫好的`mixin`,所以這個方式雖然只是在html上增加些參數,卻能很彈性的處理像是margin、border、border-radius之類需要因應狀況來給值的外觀。
```sass=
[data-space="space-next"] {
margin-right: 1rem;
}
@for $i from 1 through 10 {
[data-space-next='#{$i * 0.1 + "rem"}'] {
margin-right: #{$i * 0.1 + "rem"};
}
}
// html ex: data-space-next='0.5rem'
@each $direction in "next", "before", "vertical", "horizontal", "bottom" {
@for $i from 0 through 10 {
[data-space-#{$direction}="#{$i * 0.1 + "rem"}"] {
@if $direction == "next" {
margin-right: #{$i * 0.1 + "rem"};
}
@if $direction == "before" {
margin-left: #{$i * 0.1 + "rem"};
}
@if $direction == "vertical" {
margin: #{$i * 0.1 + "rem"} 0;
}
@if $direction == "horizontal" {
margin: 0 #{$i * 0.1 + "rem"};
}
@if $direction == "bottom" {
margin-bottom: #{$i * 0.1 + "rem"};
}
}
}
}
// 百分比的寫法
@each $direction in "next", "before", "vertical", "horizontal", "bottom" {
// 20最後會是2%,所以同學們可以自己依照需要來改寫
@for $i from 0 through 20 {
[data-space-#{$direction}-percent="#{$i * 0.1 + "%"}"] {
@if $direction == "next" {
margin-right: #{$i * 0.1 + "%"};
}
@if $direction == "before" {
margin-left: #{$i * 0.1 + "%"};
}
@if $direction == "vertical" {
margin: #{$i * 0.1 + "%"} 0;
}
@if $direction == "horizontal" {
margin: 0 #{$i * 0.1 + "%"};
}
@if $direction == "bottom" {
margin-bottom: #{$i * 0.1 + "%"};
}
}
}
}
//space direction
[data-space="space-before"] {
margin-left: 1rem;
}
[data-space="space-vertical"] {
margin: 1rem 0;
}
[data-space="space-horizontal"] {
margin: 0 1rem;
}
[data-space="space-top"] {
margin-top: 1rem;
}
[data-space="space-bottom"] {
margin-bottom: 1rem;
}
[data-space="padding-xs"] {
padding: 0.5rem;
}
[data-width="column-name"] {
width: 10rem;
line-height: 1.3;
}
@for $i from 1 through 50 {
[data-v-space="#{$i * 0.1 + "rem"}"] {
padding: #{$i * 0.1 + "rem"} 0;
}
}
@for $i from 1 through 50 {
@each $direction in "top", "left", "right", "bottom" {
[data-space-#{$direction}="#{$i * 0.1 + "rem"}"] {
padding#{"-" + $direction}: #{$i * 0.1 + "rem"};
}
}
}
//border
[data-border="left"] {
border-left: 1px solid $lightGray;
}
@each $direction in "top", "left", "right", "bottom" {
[data-border="#{$direction}"] {
#{"border-" + $direction}: 1px solid $lightGray;
}
}
[data-border="left"]:first-child {
border-left: none;
}
[data-border="bottom"] {
border-bottom: 1px solid $lightGray;
}
[data-border="bottom"]:last-child {
border-bottom: none;
}
[data-border="horizontal"] {
border-left: 1px solid $lightGray;
border-right: 1px solid $lightGray;
}
//
@each $clearList in "top", "left", "right", "bottom" {
[data-border-clear="#{$clearList}"] {
#{"border-" + $clearList}: none;
}
}
[data-border="none"] {
border: none;
}
// text-align
@each $fontAlign in left, right, center {
[data-font-align="#{$fontAlign}"] {
text-align: $fontAlign;
}
}
@each $fontSize in "default", "small", "xs", "large", "xl" {
[data-font-size="#{$fontSize}"] {
@if $fontSize == "default" {
font-size: 1rem;
}
@if $fontSize == "small" {
font-size: 0.85rem;
}
@if $fontSize == "xs" {
font-size: 0.6rem;
}
@if $fontSize == "large" {
font-size: 1.2rem;
}
@if $fontSize == "xl" {
font-size: 2rem;
}
}
}
[data-font-size="default"] {
font-size: 1rem;
}
//font color
@each $color in "red", "blue", "carrot-orange" {
[data-color="#{$color}"] {
color: #{$color};
@if $color == "carrot-orange" {
color: #{$carrotOrange};
}
}
}
//max and min-width
@each $maxWidth in "xs", "small", "default", "medium", "large", "xl" {
[data-max-width="#{$maxWidth}"] {
@if $maxWidth == "xs" {
max-width: 80px;
}
@if $maxWidth == "small" {
max-width: 120px;
}
@if $maxWidth == "default" {
max-width: 160px;
}
@if $maxWidth == "medium" {
max-width: 200px;
}
@if $maxWidth == "large" {
max-width: 300px;
}
@if $maxWidth == "xl" {
max-width: 500px;
}
}
}
//width percentage
@for $i from 0 through 100 {
[data-width='#{$i + "%"}'] {
width: #{$i + "%"};
}
}
//width rem
@for $i from 1 through 100 {
[data-width='#{$i + "rem"}'] {
width: #{$i + "rem"};
}
}
//width size prefix
@each $size in "small", "default", "large", "xl" {
[data-width='#{$size}'] {
@if $size == "small" {
width: 320px;
}
@if $size == "default" {
width: 768px;
}
@if $size == "large" {
width: 800px;
}
@if $size == "xl" {
width: 1000px;
}
}
}
//height
@for $i from 1 through 100 {
[data-height="#{$i}vh"] {
height: #{$i}vh;
}
}
//data-inset float
@for $i from 0 through 20 {
[data-inset="#{$i * 0.1}rem"] {
padding: #{$i * 0.1}rem;
}
}
//data-flex
@for $i from 0 through 24 {
[data-flex="#{$i}"] {
flex: #{$i};
}
}
//input adjust
@each $dataAttr in "default", "full", "short", "common-count", "keywords", "long-string", "date", "caption", "search", "xs" {
[data-style="#{$dataAttr}"] {
@if $dataAttr == "default" {
width: 8rem;
}
@if $dataAttr == "common-count" {
width: 10rem;
margin-right: 10px;
}
@if $dataAttr == "long-string" {
width: 20rem;
}
@if $dataAttr == "short" {
width: 6.5rem;
}
@if $dataAttr == "full" {
width: 100%;
}
@if $dataAttr == "date" {
width: 100%;
margin: 0;
padding-right: 26px;
}
@if $dataAttr == "caption" {
width: 100%;
margin: 0;
border-right: none;
border-top-left-radius: 6px;
border-top-right-radius: 0;
border-bottom-left-radius: 6px;
border-bottom-right-radius: 0;
}
@if $dataAttr == "search" {
width: 100%;
margin: 0;
padding-right: 26px;
}
@if $dataAttr == "xs" {
width: 3rem;
}
}
}
```
假如你都配置好了這些樣式,以後對於基本的flexbox排列規則就能更容易上手!
```htmlmixed=
<div class="row horizontal wrap" data-row-count="3" data-width="50%">
<!--另外再用其他樣式設定data_row的高度和外觀就行了-->
<div class="data_row"></div>
<div class="data_row"></div>
<div class="data_row"></div>
<div class="data_row"></div>
<div class="data_row"></div>
<div class="data_row"></div>
</div>
```
本日後記
---
別忘了這週開始會有回家作業,每一次上課雖然課堂會有一些練習時間,但其實真正要把東西學進骨子裡,還是需要有一個標的來臨摹會比較好。
這次的回家作業是RWD手機版面,題目可以看這裡:[回家作業題目](https://docs.google.com/document/d/1szOUUgDaRjo0Hjw_EDFlwEv8u0-fNdEFlGrvg7nVyUw/edit?usp=sharing)
---
###### tags: `Scss` `flexbox` `data-attribute`