Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

一個方便大家快速上手使用的Scss庫。
如果你偏好Scss開發,且需要和UI/UX設計師按照Guideline協作,這份Library很適合你使用!
當然,你也能很輕鬆的擴充和維護這個架構!

特別注意!從 Sass 2.0 版本開始會有一些Breaking Change

  1. map-get 日後請改用 map.get()
  2. 除法不再使用 slash 符號 '/',請改用math.div()

源碼位置

可自行取用,並且直接放在你的專案中即可,Vue、React、nuxt、next皆可使用
如果有任何可以改進的地方,歡迎私信後推個PR過來
https://github.com/fortes1219/scss-base-library

npm install npm run dev

由於 SassMeister 作者在推特上宣布已經關站,假如網站無法進入的話,推薦以 https://sass.js.org/ 做預覽

在閱讀這份文件以前,希望你了解幾件事情

一、考慮團隊內的技術選型

如果你的專案不需要考慮關於更換Skin的問題
那麼你可以放心繼續使用TailwindCSS、Stylus、bootstrap等你早已熟悉或者認為更快的做法

二、內容會持續更新

隨著瀏覽器版本更迭,內容會不定期跟著修正,因為這包東西我平常也在用,或許我會加上一些新的玩具,比如最近好不容易能用的:has()

三、善待團隊中的UIUX設計師

這個Library本質上需要跟著Figma、XD等UI協作工具來定義主題
如果你遇到可以將稿子上每份Theme color、排版typograph、間距Spacing、Easing指定都做到位的設計師,請好好善待他們,這對你的換Skin作業、改版都有很大幫助

綱要

適用版本: Sass 1.50.1 +
測試工具: 推薦使用 Sass.js

架構圖

建議另開視窗觀看圖片: https://i.imgur.com/J3hNFJT.png

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Theme Color

Live Code: https://codepen.io/fortes-huang/pen/gOdJOWG

路徑:src/style/config/_theme.scss

Figma定義的主題色票取用方法,分為兩種取用方式

Class Name

map透過遍歷自動抓取色碼產生CSS的關係,直接套用即可

$theme-prefix: 'nt'; // 自定義的prefix
<div class="my-text nt-primary-900"></div>

產生出來的CSS會長這樣:

.nt-primary-default {
  color: #6a675d;
}

.nt-primary-300 {
  color: #a09d92;
}

.nt-primary-200 {
  color: #bdb9ae;
}

.nt-primary-100 {
  color: #d9d5ca;
}

.nt-primary-gradient {
  background: linear-gradient(#baa671, #d4be81);
}

.nt-secondary-default {
  color: #85754a;
}

useColor

套用在任何的color或background上即可

.my-typography {
  // 對應 primary-900: #15130E
  color: useColor(primary, 900);
}

.sample-gradient {
  // 對應 semantic-dramatic: linear-gradient(#BAA671, #D4BE81)
  background: useColor(semantic, dramatic);
}

建構源碼

// 主題名稱前綴,可以依照專案不同版型替換名稱
$theme-prefix: 'nt';

// 所有色票的主題map
$themeColor: (
  primary: (
    default: #6a675d,
    900: #15130e,
    800: #2a2823,
    700: #39362d,
		...
  ),(...),(...)
);

// 遍歷取值和判別是單色還是漸層
@each $category, $color in $themeColor {
  @each $variant, $value in $color {
    @if type-of($value) == map {
      @each $subvariant, $subvalue in $value {
        .#{$theme-prefix}-#{'' + $category}-#{$subvariant} {
          @if str-index(inspect($subvalue), 'linear-gradient') {
            background: $subvalue;
          } @else {
            color: $subvalue;
          }
        }
      }
    } @else {
      .#{$theme-prefix}-#{'' + $category}-#{$variant} {
        @if str-index(inspect($value), 'linear-gradient') {
          background: $value;
        } @else {
          color: $value;
        }
      }
    }
  }
}

// 取色的方法
@function useColor($category, $variant: 'default') {
  $color: map-get(map-get($themeColor, $category), $variant);
  @return $color;
}

Typography

標題、文字排版、指定文字斷點溢位用途

Live Code: https://codepen.io/fortes-huang/pen/XWPwbXE

heading

標題文字 h1~h6 等

/*== h1~h6的基本設定,以後可以自行擴充 ==*/

$headings: (
  h1: (
    font-size: 40px,
    line-height: 48px
  ),
  h2: (
    font-size: 32px,
    line-height: 40px
  ),
  h3: (
    font-size: 26px,
    line-height: 28px
  ),
  h4: (
    font-size: 21px,
    line-height: 21px
  ),
  h5: (
    font-size: 18px,
    line-height: 18px
  ),
  h6: (
    font-size: 16px,
    line-height: 16px
  )
);

/*== 這個each會自動產生h1~h6原本的設定,底下不再需要重新定義 ==*/

@each $heading, $properties in $headings {
  #{$heading} {
    @each $property, $value in $properties {
      #{$property}: $value;
    }
  }
}

/*== 渲染出來的結果會像這樣 ==*/

h1 {
  font-size: 40px;
  line-height: 48px;
}

h2 {
  font-size: 32px;
  line-height: 40px;
}

h3 {
  font-size: 26px;
  line-height: 28px;
}

font-size & clamp

字級設定、顏色搭配、指定斷點套用 text-overflow: ellipsis

路徑:src/style/config/_typography.scss

字級大小用法:

<div class="my-article nt-text-sm">這是一段Small Size的文字</div>
<div class="my-article nt-text-base">這是一段預設大小的文字</div>
<div class="my-article nt-text-lg right">這是一段稍大的文字,並且靠右對齊</div>

搭配字體顏色的用法:

<div class="my-article nt-text-sm">
  這是一段文字,<span class="nt-accent-600">這裡會被上色</span>
</div>

指定第N行文字溢出:

<div class="my-article nt-text-sm clamp-3">
  這段文字會在第三行溢出,現在第一行<br />
  我是第二行<br />
  我是第三行<br />
  我是可能被隱藏的第四行
</div>

預覽效果

原理和源碼

這裡使用了文字溢位的mixins

路徑:src/style/config/_mixins.scss

// src/style/config/_mixins.scss

/*== 文本溢位可指定行數啟用 ==*/
// width 用來設定容器固定寬,可接受 fit-content
// -webkit-line-clamp: $line-count = 指定顯示「...」的行數

%extendEllipsis {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  height: auto;
  line-height: inherit;
  overflow: hidden;
}

@mixin useEllipsis($width, $line-count) {
  width: $width;
  -webkit-line-clamp: $line-count;
  overflow: hidden;
  @extend %extendEllipsis;
}

useEllipsis 在 typography.scss下使用

// 字級對應map表
$textConfig: (
  max-clamp: 10,
  size-prefix: (
    'xs',
    'sm',
    'base',
    'lg',
    'xl',
    '2xl'
  ),
  size: (
    12px,
    14px,
    16px,
    18px,
    21px,
    24px
  ),
  default-color: #ffffff
);

// 定義變數取得各自的prefix和size
$textPrefixList: map-get($textConfig, size-prefix);
$textSizeList: map-get($textConfig, size);

@each $size in $textSizeList {
  $index: index($textSizeList, $size);
	// theme-prefix就是在base.scss開頭定義的主題名稱,預設目前為 'nt'
  .#{$theme-prefix}-text-#{nth($textPrefixList, $index)} {
    font-size: px-to-rem($size);
    color: map-get($textConfig, default-color);
    @for $i from 1 through map-get($textConfig, max-clamp) {
      &.clamp-#{$i} {
        @include useEllipsis(null, $i);
      }
    }
    @each $align in 'left', 'right', 'center' {
      &.#{$align} {
        text-align: #{$align};
      }
    }
  }
}
// 編譯出來的結果

.nt-text-xs {
  font-size: 0.75rem;
  color: #ffffff;
}

.nt-text-xs.clamp-2 {
  -webkit-line-clamp: 2;
  overflow: hidden;
}

.nt-text-xs.clamp-3 {
  -webkit-line-clamp: 3;
  overflow: hidden;
}

Adaptor: pixel & rem

一個可以將px轉rem、或者rem轉px的方法

路徑:src/style/config/_remAdaptor.scss

適用場景

如果你的專案中和設計師溝通確認採用等比縮放來顯示手機版站台外觀的話。
那麼很大機會你必須替專案設置這樣的東西:

https://github.com/fortes1219/scss-base-library/blob/master/rem.js

這段程式在預設390x884(iPhone 12/13)下font-size是100px
用意是動態改變body font-size,讓UI寬高都以rem作單位計算,方便等比縮放。

用法:

// rem.js

function setRootFontSize(doc, win) {
  const clientWidth = win.innerWidth || doc.documentElement.clientWidth;
  if (!clientWidth) return;

  // Check if the device is a PC
  const isPC = win.matchMedia("(min-width: 1024px)").matches;
  if (isPC) {
    doc.documentElement.style.fontSize = "72px";
    return;
  }

  // Set font size based on screen width
  const baseFontSize = 100 * (clientWidth / 390);
  if (clientWidth >= 540) { // 依照實際需要修改裝置寬度上限
    doc.documentElement.style.fontSize = "100px";
  } else {
    doc.documentElement.style.fontSize = baseFontSize + "px";
  }
}

export default setRootFontSize;

// main.js
import debounce from "lodash/debounce";
import setRootFontSize from "./rem.js";

// 動態rem處理,並借用lodash debounce防止resize發生時會抖動
document.addEventListener("DOMContentLoaded", () =>
  setRootFontSize(document, window)
);

const debouncedSetRootFontSize = debounce(
  () => setRootFontSize(document, window),
  10
);
window.addEventListener("resize", debouncedSetRootFontSize);
window.addEventListener("orientationchange", debouncedSetRootFontSize);
// 實際上轉換出來的值會根據rem.js的body font-size 大小變化
.my-class {
  width: px-to-rem(100px); // 1rem
  font-size: px-to-rem(2rem); // 200px
}

源碼:

@use 'sass:math';

// 等比縮放佈局會以 baseRemRate = 100 * (當前裝置寬度/750) 為設定值
// 這裡base rate 100px的意思可以看成比例是100%

$baseRemRate: 100px;

@function strip-unit($number) {
  @return math.div($number, ($number * 0 + 1));
}

@function px-to-rem($value) {
  // unit 可找出$value中是否包括單位
  @if unit($value) == 'px' {
    @return math.div(strip-unit($value), strip-unit($baseRemRate)) * 1rem;
  } @else if unit($value) == 'rem' {
    @return strip-unit($value) * $baseRemRate;
  } @else {
    @error "Invalid unit. Only 'px' and 'rem' units are supported.";
  }
}


// Dart Sass 在未來的版本 2.0.0 將不再支援使用 / 進行除法操作,因為 / 會被保留用於分隔列表。
// 建議使用新的 math.div() 函數或 calc() 來替代。


// @function strip-unit($number) {
//   @return $number / ($number * 0 + 1);
// }

// @function px-to-rem($value) {
	
//   @if unit($value) == 'px' {
//     @return (strip-unit($value) / strip-unit($baseRemRate)) * 1rem;
//   } @else if unit($value) == 'rem' {
//     @return strip-unit($value) * $baseRemRate;
//   } @else {
//     @error "Invalid unit. Only 'px' and 'rem' units are supported.";
//   }
// }

flexbox

路徑:src/style/config/_flexbox.scss

用法請看 Live Code (codepen): https://codepen.io/fortes-huang/pen/OJVyVxw

/* src/style/config/_flexbox.scss */
@mixin flexBox($direction, $alignItems, $justifyContent) {
  display: flex;
  flex-direction: $direction;
  @if ($alignItems != null) {
      align-items: $alignItems;
  }
  @if ($justifyContent != null) {
      justify-content: $justifyContent;
  }
}

/* usage */

.my-element1 {
    @include flexBox(row, null, null);
}

.my-element2 {
    @include flexBox(row, center, space-between);
}

.my-element3 {
    @include flexBox(column, flex-end, center);
}

例子中的 my-element 實際上會渲染成:

.my-element1 {
  display: flex;
  flex-direction: row;
}

.my-element2 {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
}

.my-element3 {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  justify-content: center;
}

原則上用不到設定 flex-shrink(收縮比)、flex-basis(初始寬)

如果有需要做成換行後貼齊父元素容器的左右邊,那其實使用Grid比較理想。

因為flexbox是被設計用來解決「單行」為前題下的排版,只用flexbox要維護的Code就相對複雜。

可以參照下面的 flexList 來看看需要哪些條件才能進行。

flexList

使用flexbox為主的清單項目,需指定子元素內的每列幾欄、排列方式、寬高

row-type

Live Code: https://codepen.io/fortes-huang/pen/jOvKZOr

<h2>row:</h2>
<div class="flx is-row is-list sp-between wrap w-50vw" data-border="all">
  <span>1</span>
  <span>2</span>
  <span>3</span>
  ...
	...
</div>
@mixin flexListItem($rows, $margin, $unit) {
  // width算法上要抵銷margin-right,因為是100/$rows,所以最後需要加上$margin的20%
  $mod: (100 / $rows) / 100;
  width: calc((100% / #{$rows}) - #{$margin + $unit} + (#{$margin} * #{$mod + $unit}));
  margin-right: #{$margin + $unit};
  margin-bottom: #{$margin + $unit};
  &:nth-child(#{$rows}n) {
    margin-right: 0;
  }
}

span {
  @include flexBox(row, center, center);
  @include flexListItem(10, 10, 'px');
  height: 120px;
  background-color: #333;
  color: #fff;
}

.flx {
  display: flex;
  width: 100%;
  box-sizing: border-box;
  
  &.is-list::after {
    content: '';
    display: flex;
    flex: 1;
  }
// ...
// ...
}

關於實做原理,我們來看個例子

假設每個子元素的card寬度是calc(100%/5),使用space-between想用來貼齊容器左右:

這裡在.wrap使用了align-content: start,不會產生換行後兩行間的高度被拉高的狀況。

每排最後一個(5n)的元素margin去掉的設定:

.child-element {
  @include flexBox(row, center, center);
  // width算法上要抵銷margin-right的10px,因為是100/5,所以需要加上10px的20% = 5px
  width: calc((100%/5) - 10px + 2px);
  height: 120px;
  margin-right: 10px; // 原則上margin多少,calc就得扣掉多少
  margin-bottom: 10px;
  background-color: #333;
  color: #fff;
  // 每5個元素都會是該列最後一個
  &:nth-child(5n) { 
    margin-right: 0;
  }
}

如果第二行只有一個元素倒還好,但越來越多的話…


space-between會在第二行把兩個元素分開,只有一個元素不會生效的關係。

多餘的空間越多,間距就會越大。

於是這裏利用了Pseudo Element: after來填滿未被處理的空間:

.flx {
  display: flex;
  width: 100%;
  box-sizing: border-box;
  // 設置::after為一個 grow = 1、shrink = 1、basis = auto的修飾塊
  &::after {
    content: '';
    display: flex;
    flex: 1;
  }
}

並且添加了子元素清單項目的mixin:

@mixin flexListItem($rows, $margin, $unit) {
  // $rows: 一排幾列,width: calc(100%/$rows)
  // $margin: 邊界寬度,用$mod表示單位
  // $unit: margin用的單位
  $mod: (100 / $rows) / 100;
  width: calc((100% / #{$rows}) - #{$margin + $unit} + (#{$margin} * #{$mod + $unit}));
  margin-right: #{$margin + $mod};
  margin-bottom: #{$margin + $mod};
  &:nth-child(#{$rows}n) { //nth-child(number + n)的意思,指定每n個才生效
    margin-right: 0;
  }
}

span {
  @include flexBox(row, center, center);
  @include cards(5, 5, 'px');
  height: 120px;
  background-color: #333;
  color: #fff;
}

如此無論清單有幾行子元素都可以順利自訂

column-type

Live Code: https://codepen.io/fortes-huang/pen/VwGELJq

flex-direction: column為基準的話則會是另一種方式,justify-content 此時必定為 flex-start

<h2>column:</h2>
<div class="flx is-col is-list wrap w-50vw h-100vh" data-border="all">
  <span>1</span>
  <span>2</span>
  <span>3</span>
	...
	...
</div>
@mixin flexColumnListItem($rows, $margin, $unit) {
  // height算法上要抵銷margin-right,因為是100/$rows,所以最後需要加上$margin的20%
  $mod: (100 / $rows) / 100;
  height: calc((100% / #{$rows}) - #{$margin + $unit} + (#{$margin} * #{$mod + $unit}));
  margin-right: #{$margin + $unit};
  margin-bottom: #{$margin + $unit};
  &:nth-child(#{$rows}n) {
      margin-bottom: 0;
  }
}

span {
  @include flexBox(row, center, center);
  @include flexColumnListItem(4, 10, 'px');
  width: 120px;  
  background-color: #333;
  color: #fff;
}

如果需要將子元素們置中,容器父元素加上 align-content: center 即可,像這樣:

Grid List

路徑:src/style/config/_grid.scss

Live Code (codepen): https://codepen.io/fortes-huang/pen/wvEYOPG

用法:

.sample-list {
  @include gridList(6, 10px, 100px);
  width: 600px;
}

源碼:

grid清單的寫法:
$rows => 一行幾個元素
$gap => 元素之間的間距
$autoSize => 用來指定每個元素最小高度,接受 'auto' / null / 任何單位的高度

當某一列的特定元素內容高度高過其他元素,會自動拉伸該列所有兄弟元素高度


@mixin gridList($rows, $gap, $autoSize) {
  display: grid;
  grid-template-columns: repeat($rows, 1fr);
  grid-gap: $gap;
  @if $autoSize == 'auto' or $autoSize == null {
    grid-auto-rows: minmax(min-content, max-content);
  } @else {
    grid-auto-rows: minmax($autoSize, max-content);
  }
}
tags: Scss Scss Library Scss Optimization, SCSS best practices