LoginSignup
3
0

More than 1 year has passed since last update.

最低限のCSSでフロントエンド開発を乗り切る

Posted at

この記事について

CSS書けないしデザインセンスも皆無な自分が個人開発で画面を作る際、どのようにしてそれなりの見た目のものを作ったかについてまとめた記事
読者は↓のような人を想定

  • CSSはmarginくらいしか知らない
  • デザインなんてやったことない
  • Reactの基本的なこと(コンポーネントの作り方など)はわかる

戦略

最低限のCSSで見栄えのいい画面を作る戦略の紹介
結論から言うとCSSには出来合いのコンポーネントを使えば書かなくていいものと絶対に逃れられないものの2種類あるので、前者はライブラリを使うことで作業量を削減しようという作戦

部品の見た目を決めるCSSと配置を決めるCSS

CSSは大きく分けて2つに分けられる(と思っている)

部品の見た目を決めるCSS

例えばこういうおしゃれなボタン
スクリーンショット 2022-03-17 094059.jpg

ボタンの角を丸くしたり影をつけたり色を変えたり...といったことをして実装されている
このようなCSS(borderとかbackground-colorとか)がこれに該当する

部品の配置を決めるCSS

Googleのログインフォーム
スクリーンショット 2022-03-17 095446.jpg
ロゴやテキストボックス、ボタンなどの各部品をいい感じに並べている
marginやdisplayなどがこれに該当する

ライブラリを使ってCSSの記述量を減らそう

上記のCSSのうち部品の見た目を決めるCSSに関してはこれらのライブラリを用いることで記述する必要がなくなる
部品の配置を決めるCSSに関してはどうあがいても逃れられないので記述する必要はあるが、アーキテクチャを工夫したりライブラリを利用することでCSS初心者でも比較的書きやすくしたり見通しをよくしてメンテナンスしやすくしたりはできる

※(詳細は後述)Material-UIやBootstrapなどのライブラリには部品の配置を決めるためのコンポーネントが用意されているのでさらにCSSの記述量を減らすことが可能

ライブラリ/フレームワーク紹介

今回はReactをベースにし、以下の2種類のデザイン用ライブラリを組み合わせて実装する

  • おしゃれにスタイリングされたコンポーネントを提供するライブラリ(コンポーネントライブラリ)
    • 部品の見た目を決めるCSSはこれに一任する
  • ReactのコンポーネントのCSSを書くためのライブラリ(CSSフレームワーク)
    • 部品の配置を決めるCSSはこれを使って記述する

それぞれいくつか候補をあげていくので適当に見繕ってインストールしてほしい
ちなみに自分が選択したのはMaterial-UIとMaterial-UIのsx props

コンポーネントライブラリ

ざっと触った感じ機能に大きな差があるわけではないので見た目の好みで決めていいと思う
特にこだわりがなければコミュニティが大きく更新も頻繁なMaterial-UIかBootstrapがよさそう

Material-UI

マテリアルデザインに沿って実装されたコンポーネントを提供
スクリーンショット 2022-03-16 213048.jpg
歴史が長くドキュメントが豊富

Bootstrap

フラットデザインのコンポーネントを提供
スクリーンショット 2022-03-16 213758.jpg
こちらも古株でドキュメントには困らない

Semantic-UI

Bootstrapと似た感じのコンポーネント
スクリーンショット 2022-03-16 214342.jpg
上2つと比べるとマイナーでドキュメントも少ない

CSSフレームワーク

styled-components

Reactのコンポーネント単位でのスタイリング機能を提供するフレームワーク

const Button = styled.button`
  background: ${props => props.primary ? "palevioletred" : "white"};
  color: ${props => props.primary ? "white" : "palevioletred"};
  font-size: 1em;
  margin: 1em;
  padding: 0.25em 1em;
  border: 2px solid palevioletred;
  border-radius: 3px;
`;

render(
  <div>
    <Button>Normal</Button>
    <Button primary>Primary</Button>
  </div>
);

CSSを文字列で表現しているためテンプレートリテラルで変数を埋め込むことも可能
コンポーネント単位なのでどうしても↓2つに比べて記述量が多くなってしまうのが玉にキズ
ただしこちらの記事のようにDOM、スタイリング、依存性注入とコンポーネントを細かく分けて実装する方法だとかなり便利

Tailwind CSS

UtilityFirst(汎用的なCSSのクラスを作りそれらを組み合わせてスタイリングする手法)なCSSフレームワーク

shadow-sm shadow-lgのようにスタイルの大きさをenum的に表現してくれているのがgood
ちょっと凝ったデザインにしようものならクラス名が肥大化してしまうのが難点

MUI.sx props

単体のフレームワークではなくMaterial-UIのコンポーネントのsxというpropsに直接CSSを渡す方法
CSSをjavascriptのObjectで表現するので何かと融通が利く

若干話は逸れるがMaterial-UIなどにはこちらのように部品の配置を決めるためのコンポーネントが用意されているため、それらを活用すればsx propsで記述するCSSの量もグッと減らせる

コード例

import * as React from 'react';
import Box from '@mui/material/Box';

export default function BoxSx() {
  return (
    <Box
      sx={{
        width: 300,
        height: 300,
        backgroundColor: 'primary.dark',
        '&:hover': {
          backgroundColor: 'primary.main',
          opacity: [0.9, 0.8, 0.7],
        },
      }}
    />
  );
}

実装方針

ReactのプロジェクトにMaterial-UIをインストールしてコンポーネントを実装する
コンポーネントをどういった粒度で分割していくかについてはAtomic Designに従って決定する

Atomic Design

UI設計の方法論の1つ
詳細についてはわかりやすい記事がたくさんあるので各自参照していただきたい

ここではAtomic Designのざっくりとした解説とCSSのコーティング規約(っぽいもの)を各階層ごとに書き連ねていく

Atoms

Atomic Designの最小単位となる階層
スクリーンショット 2022-03-19 203944.jpg
↑のLABEL、INPUT、BUTTONのようにこれ以上細かく分解できないような粒度のコンポーネントをこの階層に実装する

CSSは自身の見た目を決めるもののみ記述するべきで、自身の配置や大きさについては関心を持たないようにするべき

具体的には↓

// OK
.btn {
  text-align: center;
  background-color: blue;
  padding: 8px;
  border-width: 2px;
}

// NG
.btn {
  margin-top: 8px;
  width: 200px;
}

Molecules

Atomsを組み合わせて実装されるコンポーネント
スクリーンショット 2022-03-19 205039.jpg
これも自身の見た目に関するCSSのみ記述可能

Organisms

AtomsMoleculesを組み合わせて実装されるコンポーネント
スクリーンショット 2022-03-19 205516.jpg
これも自身の見た目(ロゴと検索フォームを横並びにするなど、自身の子コンポーネントの配置が主)に関するCSSのみ記述可能

Templates

Organismsを組み合わせてページ全体を構成するコンポーネント
スクリーンショット 2022-03-19 205730.jpg
ただし実際にTemplatesコンポーネント内でOrganismsコンポーネントを呼び出すわけではなく、ヘッダーはここで動画があそこで...という風にレイアウトを決めているだけ

CSSは↑にある通りOrganismsコンポーネントをどのように配置するか、サイズはどれくらいかなどを記述していく

Pages

Templatesコンポーネントに対して必要なOrganismsコンポーネントを渡すことでページを表現するためのコンポーネント
スクリーンショット 2022-03-19 210133.jpg
CSSはこのコンポーネントでは書かない

実践

以下のコマンドでプロジェクト作成

npx create-react-app {プロジェクト名} --template typescript
cd {プロジェクト名}
npm install @mui/material @emotion/react @emotion/styled

ディレクトリ構成

src/
  ├ components/ 
  │        ├ atoms/
  │        ├ molecules/
  │        ├ organisms/
  │        ├ templates/
  │        └ pages/
  └ styles/theme.ts

components以下にAtomicDesignに従ってコンポーネントを実装していく
また、グローバルなスタイル定義はstyles/theme.tsに書く

ThemeProvider

Material-UIのコンポーネントのCSSをグローバルに管理できるプロバイダ
以下のようにテーマオブジェクトを作成して<ThemeProvider />に渡してやることで、全子コンポーネントのCSSを変更できる

const theme = createTheme({
  status: {
    danger: orange[500],
  },
});

<ThemeProvider theme={theme}>
  ...
</ThemeProvider>

以下ではThemeProviderでカスタムできる項目のうちよく使いそうなものだけ抜粋して紹介

Color

コンポーネントの色を変更できる

const theme = createTheme({
  palette: {
    primary: {
      light: '#0066ff',
      main: '#0044ff',
      dark: '#0022ff',
    },
    secondary: {
      main: '#ff4400',
      // light, darkを省略した場合mainの値に応じて自動で決定される
    },
  },
});

カラーコードを自分で作るのが面倒な場合は↓を使うと便利

Spacing

marginやpaddingなどの大きさをカスタマイズできる

コンポーネント実装

今回はよくあるログインページを実装してみる

Atoms

基本的にMaterial-UIのコンポーネントを少しカスタマイズするだけ

src/components/atoms/Button.tsx
import * as mui from "@mui/material";

type ButtonProps = {
  label: string;
  type?: "submit" | "button";
  color?: "primary" | "secondary";
  onClick?: () => void;
};

export function Button(props: ButtonProps) {
  return (
    <mui.Button
      variant="contained"
      color={props.color}
      onClick={() => props.onClick && props.onClick()}
      type={props.type}
      fullWidth={true}
    >
      {props.label}
    </mui.Button>
  );
}

その他TypographyTextFieldのコンポーネントも作っておく

Molecules

今回は対象となるコンポーネントがないので省略

Organisms

※今回の主題はデザインなので関数などは適当

この階層ではAtomsのコンポーネントを呼び出し、それらの配置を決めるコンポーネントを実装する
以下はログインフォームのコンポーネント

src/components/organisms/LoginForm.tsx
import { Paper, Stack } from "@mui/material";
import { Button } from "../atoms/Button";
import { Textbox } from "../atoms/Textbox";
import { Typography } from "../atoms/Typography";

export function LoginForm() {
  return (
    <Paper
      sx={{
        padding: 4,
      }}
    >
      <Stack spacing={4}>
        <Typography value="Login" variant="h3" />
        <Textbox
          value="test@example.com"
          onChange={() => null}
          label="Email"
          type="email"
        />
        <Textbox
          value="password"
          onChange={() => null}
          label="Password"
          type="password"
        />
        <Button label="LOGIN" color="primary" />
      </Stack>
    </Paper>
  );
}

新たに登場した<Paper /><Stack />について簡単に補足

Paper

画用紙っぽく浮き出た感じに装飾された<div>
スクリーンショット 2022-03-23 213535.jpg

Stack

コンポーネントを1次元に並べるコンポーネント
directionをcolumn(デフォルト)にすると垂直方向に、rowにすると水平方向に子コンポーネントを1列に並べる
スクリーンショット 2022-03-23 214056.jpg
他にも2次元に並べるための<Grid>や中央揃えするための<Container>などが用意されている

ついでにヘッダーのコンポーネントも実装する

src/components/organisms/Header.tsx
import { AppBar } from "@mui/material";
import { Typography } from "../atoms/Typography";

export function Header() {
  return (
    <AppBar position="relative">
      <Typography value="My App" variant="h1" />
    </AppBar>
  );
}

Templates

Organismsのコンポーネントの配置を決めるためのコンポーネント

src/components/templates/AuthenticationTemplate.tsx
import { Box, Container } from "@mui/material";

type AuthenticationTemplateProps = {
  header: JSX.Element;
  form: JSX.Element;
};

export function AuthenticationTemplate(props: AuthenticationTemplateProps) {
  return (
    <Box
      sx={{
        width: "100vw",
        height: "100vh",
      }}
    >
      <Box>{props.header}</Box>
      <Container
        sx={{
          marginTop: 10,
        }}
      >
        {props.form}
      </Container>
    </Box>
  );
}

Pages

Templatesに対してOrganismsのコンポーネントを流し込むためのコンポーネント
(今回は省略したものの)各コンポーネントで使用するコールバック関数や引数などもこのコンポーネントで注入する形になる

src/components/pages/LoginPage.tsx
import { Header } from "../organisms/Header";
import { LoginForm } from "../organisms/LoginForm";
import { AuthenticationTemplate } from "../templates/AuthenticationTemplate";

export function LoginPage() {
  return <AuthenticationTemplate header={<Header />} form={<LoginForm />} />;
}

実装完了したらnpm startで実行

スクリーンショット 2022-03-23 215104.jpg

3
0
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
3
0