CSS大混亂

CSS → Sass → CSS Module → JSS。寫於2020/11/12。之後的事之後再說。

三位一體:html、css、JavaScript

於現在網頁的開發於,html、css、JavaScript 已是三位一體。各項可分離但又密不可分。在技術發展上各自獨立;在應用上缺一不可。其中 CSS 是最混亂的剛好有機會順便整理。

關鍵名稱清單

CSS

Cascading Style Sheets。參考:階層式樣式表(CSS)。 為全靜態的組態於 Modern Web 是完全不敷使用的。

SCSS(Sass)/Less

Sass: Syntactically Awesome Style Sheets less css: Leaner Style Sheets

皆為CSS預處理器。提供變數與一些彈性,但關鍵的 CSS conflict/collision selectors 問題仍存在。 Sass 與 SCSS 現已互為別名,泛指相同的標的於版本發展中命名不同。 SCSS, LESS 是不同的技術但解決同樣的問題,也用近乎相似的方法(因為互相學習)所以指令也大同小異。

CSS Modules

參考:CSS Modules 用法教程 (此篇文章技術內容已過時,但說明仍有參考性) 終於解決了 CSS conflict/collision selectors 問題,方法就是為 css class name 取一個獨一無二的ID名稱。如:foo-clsname →(pre-compile)→ foo-clsname-xzqse98j邏輯名稱 + 亂數字串

CSS-in-JS

把 CSS 寫在 JavaScript 中。由於 modern web 中的css套用太複雜,所以有了各自負責並互不干擾的需求。 也就是說,在某個組件內所撰寫的樣式,即使有相同 class 的命名,但在最後編譯後這些樣式都只會作用在該組件內,不會干擾到外層或其他組件的樣式。參考: 把 CSS 寫在 JavaScript 中

不同的技術會有不同的指令實作,但指令形式一樣。 styled-component 是一個 CSS-In-JS 的函式庫,使你可以在 JSX 中撰寫 CSS code,更方便的是他可以接到 component 的 props 值來動態改變 css 樣式。參考:styled-component 是什麼

JSS

JSS 是一個 CSS in JS 的函式庫。不同的前端技術會有各自的JSS方案。比如:React-JSSMaterial-UI.v4。大概都會有幾個共同的指令,如:、makeStyles, ThemeProvider, withStyles等等的。

TSS (tss-react)

簡單的說就是 TypeScript 版本的 JSS,之中也有提昇/調整部份規格並與 Material UI.v5 有緊密整合。

'tss-react' is intended to advantageously replace the now deprecated @material-ui v4 makeStyles and react-jss by providing much better TypeScript support.

A tool for transforming CSS with JavaScript

一套 CSS 後處理工具模組,有各種 CSS 後處理的插件。

為 CSS 後處理器(post processor),處理位置上與 SCSS/Less 相反。它可以做的事還滿多的最有名的產品之一就是 Tailwind CSSostCSS 最基本的能力就是把『標準的CSS』再 postfix 時期再加入各家瀏覽器的前綴詞,如:

// 轉換前...
.example {
    display: grid;
    transition: all .5s;
}
// 用Autoprefixer轉換後...
.example {     
    display: -ms-grid;     
    display: grid;     
    -webkit-transition: all .5s;     
    -o-transition: all .5s;     
    transition: all .5s; 
}

優點:就是“跨瀏覽器” 還可以除錯等等。上述案例是安裝了 Autoprefixer 插件。

缺點:PostCSS 有各種的插件不是統一的標準。PostCSS 的能力來自安裝了多少插件。PostCSS 基本功能不包含CSS分隔,這項能力要另外安裝搭配的相關插件。

補充 on 2022/6/16

一套適用於過場動畫的模組。有獨特的設計理念如同其命名 spring (彈簧)一樣的自然而然。

範例一:將會定時翻動文字

FilpText.js
import type { CSSProperties, FC } from 'react'
import { useState } from 'react'
import { useSpring, animated, config } from 'react-spring'

// 範例一:將會定時翻動文字 
const SpringBasic2_FlipText: FC = (props) => {
    const [flip, setFlip] = useState(false)
    
    // 設定得會動的 spring style 物件
    const aniStyle = useSpring({
        opacity: 0,
        from: { opacity: 1 },
        reset: true,
        reverse: flip,
        delay: 500,
        config: config.molasses,
        onRest: () => setFlip(f => !f),
    })

    return (
        <div>
            {/* 將會動的 anyStyle 物件帶入 animated 元素。 */} 
            <animated.div style={aniStyle}>
                {props.children}
            </animated.div>
        </div>
    )
}

範例二:依訊息翻動文字(使用 api)

import type { CSSProperties, FC } from 'react'
import { useState } from 'react'
import { useSpring, animated, config } from 'react-spring'

const SpringBasic4_SpringApi: FC = (props) => {
    const [aniStyle, api] = useSpring(() => ({ opacity: 1 }))
    //設定 CSS 初始狀態:顯現。並拿到 spring api。

    return (
        <div>
            {/* 依訊息觸發 api.start() 函式指定目標CSS並開始變化。 */}
            <AButton mutant='primary' label="隱藏 "
                onClick={() => api.start({ opacity: 0 })} // CSS 變化目標:隱藏
            />
            <AButton mutant='primary' label="顯現"
                onClick={() => api.start({ opacity: 1 })} // CSS 變化目標:顯現
            />

            {/* 將會動的 anyStyle 物件帶入 animated 元素。 */}
            <animated.div style={aniStyle}>
                {props.children}
            </animated.div>
        </div>
    )
}

如何應用

較新的 CSS 解法方案太概都會疊加之前的技術,不過不能保證會完全疊加,這也是CSS混亂的原因之一。總之我個人把 Sass + CSS Module + JSS 疊加混用才解決css套用的綜合問題。

Sass 讓CSS有彈性。 CSS Module 解決 conflict/collision 問題。 JSS 提高 React 前端元件 closure 屬性。 styled-components 單純的元件就用這招。

整合 Sass、CSSModule 與 JSS

在scss 定義全域基本共通參數與通用的 layout 與元件呈現設計,比如:色系等。 用CSS Module 之 :export 指令匯出給 React/JavaScript 使用。 最後用 JSS 指令 makeStyles 等等套用 css 組態到元件。

範例 --- 依然很混亂啊

global.module.scss
/* variables.scss */
$white-color: #fcf5ed;
$dark-color: #402f2b;
$light-color: #e6d5c3;
$medium-color: #977978;
$alert-color: #cb492a;
$light-black-color: #706e72;
$black-color: #414042;

/*** 將 SCSS 變數匯出成JsvaScript物件 ***/
:export {
  colors: {
    white: $white-color;
    dark: $dark-color;
    light: $light-color;
    medium: $medium-color;
    alert: $alert-color;
    lightblack: $light-black-color;
    black: $black-color;
  }
}

/*** SCSS class 將會自動匯出成JsvaScript物件之 className ***/
.container /* 轉譯成 .container */ {
  background: red;
  color: white;

  .hello /* 轉譯成 .container.hello */ {
    padding-left: 50px;
  }
}
App.tsx
import React from 'react'
import { CssBaseline } from '@material-ui/core'
import { ThemeProvider, createMuiTheme } from '@material-ui/core/styles'
import './App.scss' // 匯入一般的 Sass/SCSS
import globalStyles from 'global.module.scss'; // 匯入 CSS Module

const theme = createMuiTheme({
   /* 可在此引入 globalStyles 客製化 Theme */
})

export default function App() {
  return (
    <ThemeProvider theme={theme}>
      <CssBaseline />

      <div style={{
        color: globalStyles["colors-light"],
        backgroundColor: globalStyles["colors-dark"]
      }}>
        By globalStyle color
      </div>

      <div className={globalStyles.container}>
        <div className={globalStyles.hello} >
          Hello SCSS container.hello
        </div>
      </div>

    </ThemeProvider>
  )
}
tsconfig.json
{
  "compilerOptions": {
    "plugins": [ { "name": "typescript-plugin-css-modules" } ],
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react",
    "baseUrl": "src"
  },
  "include": [
    "src"
  ]
}

Last updated