Try   HackMD

2020/02/29 Sass/Scss 教學筆記: 建立flexbox排版為主的共用模組

綱要

為什麼每次更新Git時最好 npm install 一次?會出現什麼問題嗎?

同學們應該發現了最近課堂上出現了gulp watch時,node-sass要你更新depth 2的套件訊息,我們雖然可以照著錯誤訊息上的指示更新 node 相依套件或者跑一次npm audit fix

接著你會看見 Git 突然顯示了packge-lock.jsonpackge.json兩隻檔案的變更(有時只會更新lock那隻),而你在修正了相依套件更新的問題後必須要將這個變更Push到Git上。

假如跟你同個專案的其他工程師更新了這些檔案,卻忘了或不知道更新的內容包含packge-lock.jsonpackge.json,那麼啟動環境時就會顯示缺漏特定模組的錯誤訊息、環境也跑不起來。或者更慘的狀況是環境能跑、功能卻無法顯示或不正常,而你們對外Release了產品新版本後才發現XD

正確的觀念是,只要Git上的專案有任何新的變更,在你Fast-forward-merge或者直接將目標分支merge到自己的分支上時,一定要記得跑一次npm install來避免這樣的問題,千萬不要因為沒更新 Package 就略過這個步驟。

npm install 會幫我們做些什麼事

還記得剛開始這個專案時我們做過的npm init初始化得到的package.json嗎?
後頭為了gulp任務管理工具我們又安裝了其他相依套件:

通常我們不會把node_modules這麼肥大的檔案直接丟上Git,而是依賴package.jsonpackage-lock.json中已安裝套件的記錄,透過npm install這個指令重新安裝回來,同專案的同事也可以利用這份json來讓開發環境同步,也可以將各套件的版號刪除上箭號「^」來鎖定版本。

Media Queries

以前我們都學過Media Query媒體查詢來做 RWD Breakpoint 判別特定寬度下 UI 要怎麼變化,而在Sass/Scss中,定義的方式會稍稍有些不同:

$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 */ .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上課時我們提到:你不應該把時間花在命名容器上,那是下等人做的事情
而且將html的結構以組件的概念依照需求來做排列,為了達到這個理想的目的,今天起大約兩週時間就需要建構我們自己的 Code Library,結訓作業會派得上用場,之後也能整併到自己的公司專案裡馬上使用喔!

可以參考老師的Github範例來配置:

style.scss

完整範例

/* 注意引用的順序喔 */ @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

重設瀏覽器規則,跨裝置跨瀏覽器的必備作業。

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檔案上調用,當你有多樣板的需求(聖誕節、春節主題等)就可以非常彈性的擴充你的專案外觀。

$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

本次課程的重點就是這隻排版邏輯,我們會定義出flexboxrowcolumn不同邏輯下的排列規則外,還會設置指定每行顯示N個元素為的規則,真正幫助你節省切版時間之外也保持更好的維護性。

@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之類需要因應狀況來給值的外觀。

[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排列規則就能更容易上手!

<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手機版面,題目可以看這裡:回家作業題目


tags: Scss flexbox data-attribute