--- 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任務管理工具我們又安裝了其他相依套件: ![](https://i.imgur.com/XCA6bDf.png) 通常我們不會把`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`