🍃

Nuxt.jsにTailwindを初めて使ってみるときのTips

2021/07/31に公開

Tailwind CSSとは

ユーティリティが用意されているCSSフレームワーク。
CSSでいうユーティリティとは、あらかじめ用意されたクラスを利用してStyleを当てていくことで、CSSを書く事なくスタイルを当てていく手法です。

具体的には、クラスを定義した別ファイル(ユーティリティファイル)を用意しておいて、使いたい時にクラス名を書くイメージです。

ユーティリティファイル
.p-10 { padding:10px }

使うときは、

<h2 class="p-10">padding10pxになる</h2>

こんな感じでTailwindのユーティリティファイルにも大量のクラスとStyleが用意されていて、このユーティリティたちを組み合わせてマークアップを行います。
余談ですが、この手法自体はBEMやFLOCSSなどのいわゆるCSS命名規則によるマークアップが流行る前からあった様です。

ただ当時は

  • Webページがどんどん複雑になっていく段階であった
  • CSSはグローバルスコープな仕様のため、思わぬところでCSSが当たるのが厄介だった

というような経緯があり、これらの問題を解決するための手法の一つとしてCSS命名規則が生まれ、対処されてきました。
実際にBEMやCSSを便利に書くSCSSなどの技術は今でも使われています。

Tailwindはユーティリティを使うフレームワークでありながらも、nuxt/tailwindの公式には

Includes CSS Nesting with postcss-nesting

と記述されています。postcssを使用してCSSをネスティングすることができる為、クラス名が別のコンポーネントと衝突しない(=思わぬところでCSSが当たらない)ような仕組みが使われています。

CSSの課題に対する対処法が増えた事で、ユーティリティを使ったWebサイト構築も比較的容易に対処できるようになったという事です。
Tailwindでコーディングをした際に、意識した事や知っておいて為になった事をまとめました。

nuxt/tailwindでtailwindを始める

nuxt/tailwindの公式を参考にします。

nuxtのバージョンをpackage.jsonで確認してインストール

yarn add --dev @nuxtjs/tailwindcss postcss@latest

nuxt.config.jsのbuildModulesに追記

nuxt.config.js
export default {
  buildModules: ['@nuxtjs/tailwindcss']
}

ルートディレクトリで、tailwind.config.jsファイルを作る

npx tailwindcss init

オプションの設定

tailwind.config.js
module.exports = {
  mode: "jit",
};

反映をすぐに確認できるように(保存時に自動リロード)したかったのでmode:jitを設定しました。

あとは、各タグにクラス名を付与してStyleを当てていきます。

コーディングしてみる

前提

  • Tailwindのクラス名は略称が使われています。
  • 最初はpaddingやmargin、width、heightで慣れるのがおすすめです。
  • paddingは「p」 marginは「m」 widthは「w」 heightは「h」 など一文字で表現しています。
  • padding-topは「pt」 と書きます。「pr(padding-right)」「pb(padding-button)」「pl(padding-left)」も同じ感じです。
  • 公式のドキュメントでは両端や両上下などのクラス名の掲載もあります。

https://tailwindcss.com/docs/margin

慣れてきたら

  • 当てたいCSSに対してTailwindのクラス名を思い浮かべる→分からない場合は公式のドキュメントを見てStyleを当てていきます。
  • 例えば、display:noneを適用したいけどtailwindでどう書くかわからない時は、公式のドキュメント内でCommand + Kを押すと検索窓がでてくるのでそれで調べるのが楽です。
  • googleで「tailwind display none」と調べると検索結果の一番上に出てくるので、慣れないうちは検索でもいいかもしれません。

タイトルカードを作ってみる

CMSでよくみるタイトルカードにTailwindを使ってみました。

↑こんな感じになりました。

コード

TitleCard.vue
<template>
  <div
    class="p-5 flex-none lg:w-2/5 md:w-2/4 sm:w-1/2 xs:w-full transition duration-300 z-10"
  >
    <nuxt-link :to="linkTo(post)">
      <div
        class="max-w-lg rounded overflow-hidden shadow-lg mb-4"
        :class="{ activeCard: activityCard }"
      >
        <div class="overflow-hidden w-full h-64">
          <img
            class="w-full h-64 object-cover"
            :src="setHeaderImg(post).url"
            :alt="setHeaderImg(post).title"
          />
        </div>
        <div class="px-6 py-4 pb-2 bg-white">
          <div class="font-bold text-xl mb-2">
            {{ post.fields.title }}
          </div>
          <div>
            <small>{{ $getFormattedDate(post.fields.publishedAt) }}</small>
          </div>
          <template v-if="post.fields.tags">
            <div class="py-3">
              <span
                class="badge mr-2"
                v-for="tag in post.fields.tags"
                :key="tag.sys.id"
              >
                <nuxt-link :to="linkToTag(tag)">
                  {{ tag.fields.name }}
                </nuxt-link>
              </span>
            </div>
          </template>
        </div>
      </div>
    </nuxt-link>
  </div>
</template>
<script>
import { mapState, mapGetters } from "vuex";

export default {
  props: {
    post: {
      type: Object,
      reqire: true,
      default: () => {
        return {
          fields: {
            title: "sample",
            publishhehAt: new Date(),
            headerImage: null
          }
        };
      }
    }
  },
  data: function() {
    return {
      activityCard: false
    };
  },
  computed: {
    ...mapState(["filterposts"]),
    ...mapGetters(["setHeaderImg"])
  },
  methods: {
    linkTo(post) {
      return { name: "posts-slug", params: { slug: post.fields.slug } };
    },
    linkToTag(tag) {
      return {
        name: "tags-slug",
        params: { slug: tag.fields.slug }
      };
    }
  },
  mounted: function() {
    this.$store.commit("filterposts", []);
  }
};
</script>

<style lang="postcss" scoped>
.badge {
  @apply inline-block bg-yellow-100 rounded-full px-3 py-1 text-sm font-semibold text-yellow-500;
  &:hover {
    @apply bg-yellow-200;
  }
}
.activeCard {
  @apply transform shadow-xl transition duration-300 ease-in-out;
}
</style>

class名が長くなる分、styleの記述量が少ないのが特徴です。

よく使うもの

@apply

  • @applyを使うとtemplate部分に書かず、style部分にまとめて書くことができます。
  • コードだと、template部分にclass="badge"と指定して、style部分に .badge{ @apply......}と書いて設定してます。
  • template部分のクラス名が長くなりすぎて見にくくなるのを避けたり、別の場所で再利用するために使います。

レスポンシブ対応

  • PCの時と、スマホのときの表示を変えたい時に使う時は、
lg:w-2/5 md:w-2/4 sm:w-1/2 xs:w-full

という感じで、先頭にそれぞれの画面幅を表すlgやmdなどの接頭語をつけます。

https://tailwindcss.jp/docs/responsive-design

スマホの時は半分(2分の1)など、ブラウザ画面で確認しながらやると直感的にできます。

自分で設定する

  • 自分の設定したい値がない時は、tailwind.config.jsにて設定することができます。
tailwind.config.js
module.exports = {
  theme: {
    screens: {
      xs: "375px",
      ...defaultTheme.screens
    },
};

使う時はクラス名を指定(上記の場合は「xs」)を指定すれば、Styleを当てることができます。

マークダウンにtailwindCSSを当てはめる

  • contentful等のCSMからの記事情報などを取得したときに、tailwindのクラスを当てたい事があります。
  • 情報を取得してDOMを形成するタイプだと、マークアップ時にクラスを当てるのが難しいケースがあります。
  • また、Tailwindはh3タグなどのデフォルトのCSSを要素リセットしているので、ただ取得しただけですとCSSが当たりません。
  • 対処方法は、nuxt.jsのassetsフォルダにtailwind.cssを作成し以下を記述します。
assets/css/tailwind.css
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";

.markdown h3 {
  @apply border-yellow-300 border-l-4 text-2xl;
}

上の場合は、markdowのクラス内のh3タグに対してグローバルにCSSが当たる設定になります。
あとは、classに指定してあげます。

<div class="markdown"></div>

容量を減らして無駄を省く

  • Tailwindは大量のユーティリティを用意している為、ファイル容量がそれなりにあります。
  • なので、使っていないユーティリティをビルドした時に取り除くpurge機能を設定する事ができます。
tailwind.config.js
module.exports = {
  purge: [
    "./components/**/*.{vue,js}",
    "./layouts/**/*.vue",
    "./pages/**/*.vue",
    "./plugins/**/*.{js,ts}",
    "./nuxt.config.{js,ts}"
  ],
};
  • ファイル容量の少ない方が、ブラウザがサーバからファイルを取得するのも早くなるのでpurge機能は設定しておくのがおすすめです。

感想

  • クラスを考える必要がない反面、tailwindのユーティリィクラス名を知る必要がある。
  • ただ、知っていれば実装のスピードは上がりそうだし、container・wrapperなど人によって書き方変わるのがなくなるので、分かりやすく・見やすくなるのはいいかなと思った。
  • styleにCSSを当てて書く事が殆どなくなる為、結果として全体のコード量が減る。
  • purgeすることで使っていないCSSを削除してくる機能はいい。
  • 組み合わせて使うので、Bootstrapなど似通ったデザインにもなりにくい。
  • headless UIとかと組み合わせまではやってないので、やってきたい。
  • Vuetifyみたいにコンポーネントが用意されている訳じゃないので、例えば、input項目のバリデーションチェックもやろうと思うと結構大変になるかもしれないです。
  • 一長一短ありCSSマークアップ手段のひとつなので、サイトの規模や複雑具合でBEM✖️SCSSなどと使い分けしていきたいと思います。

Discussion