LoginSignup
14
4

More than 3 years have passed since last update.

SvelteでCSSinJS(emotion)使ってみた

Last updated at Posted at 2020-03-16

時間がない人は一番下の「使った感想」までジャンプ!

これはなに

Svelteでemotion(CSSinJS)を試したので備忘録

CSSinJSってなに

CSSをJSで書きますよ〜という概念。
詳しいメリットは後述しますが、ざっくりいえば下記な点より支持されています。

  • スコープのないCSSにスコープを付与できる
  • 変数・関数がJSとCSSで同じものが使える

主にスタイルにスコープを持てないReactで使われています。
emotion,styled-compornentなど、ライブラリも豊富です。

書いたコードはインラインやCSSファイルとしては吐き出されず、DOMのトップに直接追加されます。

スクリーンショット 2020-03-13 12.38.52.png

メリットざっくり

  • インラインではなく、実際のCSSを生成する
  • SCSSより軽いらしい ライブラリ容量:emotion:8.9kb
  • JSの変数・関数がそのままCSSに使える(後述)
  • CSSにスコープを付与できる
    • ただし今回の場合、Svelte自体がスコープを持っているため、特別なメリットではない

デメリットざっくり

  • SASSと書き方の異なる部分があり、デザイナーがスタイルを書く場合は学習コストあり
  • mixinなど、SASS独自の機能は当然使えない
  • mixinできた

検証したFWについて

emotionを検証しました。
emotionはFWに依存しないCSSinJSのため、公式のブログでもサンプルコードが掲載されています。
(CSSinJSといえばstyled-components一択感はあるが、React依存)
記事


検証

ここからは実際の使い方の説明です。

スタイルの指定

  • htmlタグ内ではclassを{}で囲むと適用される
  • <script>タグ内にconst hoge =で指定
  • importするときはexport const hoge =にしてね
  • タグ付きテンプレートリテラルで記述(バッククオーテーションで囲む)

どうやってCSS書くの

CSSinJSだと、文字通りJSでCSSを書くため、
margin-bottommarginBottomになったり、
ハイフンではなくキャメルケースで書きます。

それがJSXの中でインライン記法のように書けるCSS propといいます。

   const style = css({
    marginBottom: '16px',
    borderRadius: '4px',
  });

いつものCSSと書き方が異なり書きづらいよ〜という人のために、
タグ付きテンプレートリテラルを使えばいつも通りのCSSの書き方になります。

  const list = css `
    background: ${Color.BaseColor};
    margin-bottom: 16px;
    border-radius: 4px;
  `

ただし、SvelteではCSS propは書けないっぽい:no_mouth: (JSXだし?)

コード サンプル

変数はそれだけでまとめるとよさそう

Color.js
export default {
 BaseColor: '#FFF',
 KeyColor: '#444',
 SubColor: '#ececec'
}

こうやって呼び出す

sample.svelte
<script>
  import { css } from "emotion";
  import Color from "../../apps/style/Color.js";

  const list = css `
    background: ${Color.BaseColor};
    margin-bottom: 16px;
    border-radius: 4px;
  `
</script>

<li class={list}>
  <span class={icon}>
    <Label {type} />
  </span>
</li>

const list = css ~でスタイルを定義すると、
templateの方でclass={list}で適用されます。

この時classはcss-#ハッシュ値で出力されます。
rollupで消せないかな? できないらしい><

スクリーンショット 2020-03-10 9.39.28.png

styled-compornent風に、styled.h1 =のように、
const list = css.liでタグの指定はできない

NGな書き方1

<script>
  import { css } from "emotion";

  export const List = css.li `//タグをこっちで指定
    background: ${Color.BaseColor};
    margin-bottom: 16px;
    border-radius: 4px;
  `
</script>

<List>
  <Icon>
    <Label {type} />
  </Icon>
</List>

タグを指定できるメリットとしては、

  • 純粋にコンポーネント名だけでかけるのでスッキリする
  • h1、h2、spanとpなどがスタイル側で指定できる

があるけど、
前者はtemplate部分だけでhtml構成が判別できないデメリットもあるし、
後者はスタイル側で指定しなくてもいいと思うので、
かけなくても問題はない:rolling_eyes:

Media Queries

Base.js
//メディアクエリ
export const breakpoints = [480, 768, 980, 1200]//ブレイクポイント
export const mq = breakpoints.map(
  bp => `@media (min-width: ${bp}px)`
)
呼び出すとき
  import { mq } from "../../apps/style/Base.js";

  //コンテンツ幅の980pxで切り替える
  ${mq[2]}{
    display: block;
  }
`

keyframe Animation

//keyframesを指定する
import { css,keyframes } from "emotion";

//アニメーション
//keyframesを先に書かないとエラー
const heightAnimation = keyframes `
 from {
   opacity:0;
  }
  to {
   opacity:1;
  }
`;

const searchBlock  = css `
  display: none;
  //ちゃんとハッシュ値でセットされる
  animation: ${heightAnimation} ease-in .3s;

  //レスポンシブ
  ${mq[2]}{
    display: block;
  }
`

mixin


export function square(val , key) {
  return {
    width: val,
    height: key,
  };
}

const regularStyle = css`
  ${square('24px','12px')}
 background: hotpink;
`;

propsを直接スタイルで指定して出し分ける方法

やりたかったこと

  • propsでわざわざclassNameを指定しなくてもいいので、スタイルのためにコンポーネントを増やさずに済む
    • jQueryでは、onClickでclass名を付与->そのclass名の分だけコンポーネントを追加で作るイメージだった
    • propsでprimaryという値を渡せば、1つのコンポーネントの中でスタイルの出しわけができる
    • 参考

できたこと

jsの配列をCSSに直接指定


  let attr = {
    none: {
      caption: "",
      fontColor: "red"
    },
    place: {
      caption: "ほにゃほにゃ",
      fontColor: "orange"
    },
    image: {
      caption: "ふがふが",
      fontColor: "blue"
    }
  };

export const title = css `
  color: ${attr[type].fontColor};
  font-size: 2rem;
`;
</script>

<ICon {type} />
<label class={title}>{attr[type].caption}</label>

今までは、JSとSASSで変数を二重管理する部分があったので、管理が一元化できてよい
(slickのブレイクポイントとかね)

できないこと

propsでCSSを直接出し分けること。これが本当にやりたかった><
これができると、状態ごとに毎回classを1つ追加する手間が省けて、コード量が減らせる。

例えば、<a class="btn">というDOMにprimaryという状態を付与する場合、
btnクラスの他にis-primary{foo: bar;};のclassを新しく付与していた。

CSSに直接propsを渡せる場合、下記のコードのように、
btnクラスの中でprimaryが渡った場合のみ、必要なコードを動的に付与できる。
'@emotion/core'を入れると使えるけど、React依存のようだ。


<script>
  const btn = css `
    //backgroundがpropsで渡ってきた色に変更、初期値はgreen
    background: ${props => props.background || "green"};

  //primaryのpropsが渡ったら、btnにプロパティを追加
    ${props => props.primary && css`
      padding: 1em;
      margin: 1em;
      color: purple;
    `}
</script>

<p>
 <a class={btn} {primary}>
  test
 </a>
</p>

単純に書き方が違うだけかもしれないので、
良い書き方があればアドバイスほしいです!

使った感想

意外と使えそう:relaxed:

公式で言っている通り、SvelteのCSSと併用する前提になってはいるものの
mixin的使い方も可能なので、十分使えそうです。
デザイナーが使う場合、学習期間は設けるとよさそう。

GOOD

  • 親コンポーネントと子コンポーネントで同じclass名を指定してもスタイルはあたらない
  • mixinもいける
  • JS側と変数を共有できるのは嬉しい
    • slickのブレイクポイント、カラー変数など、2重管理が防げる

BAD

  • emotion公式のGlobal Stylesは使えない
    • installの必要な'@emotion/core'がreactでしか動かないのが諸悪の根源
    • Globalなclassを使う場合、CSSの外部ファイルとしてimportする
    • またはSvelteのGrobalとして指定する Using CSS-in-JS with Svelte
  • VSCodeにEmmetがない?
  • Svelteがそもそもスコープを持っているので、擬似スコープのうまみはない

デザイナーができそう?できない?

良いか悪いか、SvelteのCSSinJSは特有の複雑さが出てこないので、デザイナーでも慣れればOK
(「できないこと」であげたpropsを直接渡す、はグラフィック寄りのデザイナーはしんどいかも)

検証ポイント早見表

検証項目 結果 一言
親子コンポーネントで継承できるか importするかpropsでclassの付け替え
.svelteファイル、外部ファイルとしても使えるか コンポーネントまたいだスタイルは外部ファイルimport
グローバルで当てられるか jsファイルをimport
レスポンシブ(SP,PC) MediaQuery
keyframe animation
mixin
スタイルに直接propsが渡せるか 直接は渡せないので、変数・配列としてclass名を渡す、とかになりそう

参考

emotionを使ったCSS-in-JS
emotion - フレームワークに依存しない洗練された CSS-in-JS
CSS-in-JSのライブラリとして「emotion」を選択している理由

14
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
4