Quantcast
Channel: CSSタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8664

Styled-componentsでシンプルにグリッドレイアウトを実装する

$
0
0

はじめに

ReactとStyled-compomentsを使って、シンプルに12カラムのグリッドレイアウトを実装してみました。

カラムレイアウトにはFlexboxを使っています。
Reactの環境構築はcreate-react-appで行います。

なお、Styled-compomentsの概要については省略します。

グリッドレイアウトの概要

  • 12カラムのグリッドに沿って、横並びやレスポンシブのアイテムを配置していきます。
  • 各カラムの左右にはガター(溝)のpaddingがあります。12カラム全体では左右に(ガター/2)分のpaddingとなります。

グリッドレイアウトについてあまり聞きなれない場合は下記リンク等が参考になると思います。

Bootstrapのグリッドシステムの使い方を初心者に向けておさらいする
Grid system - Bootstrap 4.5 - 日本語リファレンス

1. Reactプロジェクトの準備

CRAをTypeScript付きでプロジェクト作成し、Styled-compoments関連のパッケージもインストールします。

$ npx create-react-app grid-layout-styled-components --typescript$ cd grid-layout-styled-components
$ npm install--save styled-components
$ npm install @types/styled-components
$ npm install--save-dev babel-plugin-styled-components

次にbabel-plugin-styled-componentsを使うための準備をします。

$ touch .babelrc

.babelrc

{"plugins":[["babel-plugin-styled-components"]]}

最後にindex.cssとApp.cssの中身を一旦空にしておきます。

2. グローバルスタイルの作成

グローバルに適用するスタイルを作成します。

$ touch src/GlobalStyle.ts

はじめにリセットCSSを追加します。好みによりNormalizeCSSでも可能だと思います。
リセットCSSの中では個人的にEric Meyer氏のものがシンプルで好きです。

GlobalStyle.ts
import{createGlobalStyle}from'styled-components/macro';exportconstGlobalStyle=createGlobalStyle`
  /* Reset CSS */
  /* ===================================== */

  /* http://meyerweb.com/eric/tools/css/reset/ 
    v2.0 | 20110126
    License: none (public domain)
  */

  html, body, div, span, applet, object, iframe,
  h1, h2, h3, h4, h5, h6, p, blockquote, pre,
  a, abbr, acronym, address, big, cite, code,
  del, dfn, em, img, ins, kbd, q, s, samp,
  small, strike, strong, sub, sup, tt, var,
  b, u, i, center,
  dl, dt, dd, ol, ul, li,
  fieldset, form, label, legend,
  table, caption, tbody, tfoot, thead, tr, th, td,
  article, aside, canvas, details, embed, 
  figure, figcaption, footer, header, hgroup, 
  menu, nav, output, ruby, section, summary,
  time, mark, audio, video {
    margin: 0;
    padding: 0;
    border: 0;
    font-size: 100%;
    font: inherit;
    vertical-align: baseline;
  }
  /* HTML5 display-role reset for older browsers */
  article, aside, details, figcaption, figure, 
  footer, header, hgroup, menu, nav, section {
    display: block;
  }
  body {
    line-height: 1;
  }
  ol, ul {
    list-style: none;
  }
  blockquote, q {
    quotes: none;
  }
  blockquote:before, blockquote:after,
  q:before, q:after {
    content: '';
    content: none;
  }
  table {
    border-collapse: collapse;
    border-spacing: 0;
  }
}
`;

次にタイプセレクタへのCSSを追加します。
ここで必須なのは*, *::before, *::after{box-sizing: border-box;}になります。

ついでに必要ないとは思いますが、フォントはいつでも綺麗にしておきたい性分なのでbodyへのfont-family設定も癖で追加しました。

GlobalStyle.ts
import{createGlobalStyle}from'styled-components/macro';exportconstGlobalStyle=createGlobalStyle`
  /* Reset CSS */
  /* ===================================== */

  /* http://meyerweb.com/eric/tools/css/reset/ 
    v2.0 | 20110126
    License: none (public domain)
  */

  html, body, div, span, applet, object, iframe,
  h1, h2, h3, h4, h5, h6, p, blockquote, pre,
  a, abbr, acronym, address, big, cite, code,
  del, dfn, em, img, ins, kbd, q, s, samp,
  small, strike, strong, sub, sup, tt, var,
  b, u, i, center,
  dl, dt, dd, ol, ul, li,
  fieldset, form, label, legend,
  table, caption, tbody, tfoot, thead, tr, th, td,
  article, aside, canvas, details, embed, 
  figure, figcaption, footer, header, hgroup, 
  menu, nav, output, ruby, section, summary,
  time, mark, audio, video {
    margin: 0;
    padding: 0;
    border: 0;
    font-size: 100%;
    font: inherit;
    vertical-align: baseline;
  }
  /* HTML5 display-role reset for older browsers */
  article, aside, details, figcaption, figure, 
  footer, header, hgroup, menu, nav, section {
    display: block;
  }
  body {
    line-height: 1;
  }
  ol, ul {
    list-style: none;
  }
  blockquote, q {
    quotes: none;
  }
  blockquote:before, blockquote:after,
  q:before, q:after {
    content: '';
    content: none;
  }
  table {
    border-collapse: collapse;
    border-spacing: 0;
  }

  /* Add Global CSS */
  /* ===================================== */

  *, *::before, *::after {
    box-sizing: border-box;
  }

  body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
  }
`;

3. 定数の作成

App.tsxに書いていきます。以下を用意します。

  • ブレークポイント
  • 各ブレークポイントに対応するコンテナの最大幅
  • グリッドの溝
  • カラム数
App.tsx
importReactfrom'react';importstyledfrom'styled-components/macro';import{GlobalStyle}from'./GlobalStyle';import'./App.css';// configsconstbreakpoints:{sm:number;md:number;lg:number;xl:number}={sm:576,md:768,lg:992,xl:1280,};constcontainerMaxWidths:{sm:number;md:number;lg:number;xl:number}={sm:540,md:720,lg:960,xl:1250,};constgridColumns:number=12;constgridGutterWidth:number=32;// componentsfunctionApp(){return<div><GlobalStyle/></div>;
}exportdefaultApp;

4. 汎用コンポーネント作成

汎用的に使うコンテナのコンポーネントと、グリッド行・グリッド列のコンポーネントを用意します。

実装にあたりBootstrapのscssやcssのソースを参考にしました。
全て理解するのは難解ですが、一つ一つは(Bootstrapを使うかに関係なく)CSSの設計に勉強になることが多いと感じます。

App.tsx
importReactfrom'react';importstyledfrom'styled-components/macro';import{GlobalStyle}from'./GlobalStyle';import'./App.css';// configsconstbreakpoints:{sm:number;md:number;lg:number;xl:number}={sm:576,md:768,lg:992,xl:1280,};constcontainerMaxWidths:{sm:number;md:number;lg:number;xl:number}={sm:540,md:720,lg:960,xl:1250,};constgridColumns:number=12;constgridGutterWidth:number=32;// componentsconstContainer=styled.div`
  max-width: 100%;
  @media (min-width: ${breakpoints.sm}px) {
    max-width: ${containerMaxWidths.sm}px;
  }
  @media (min-width: ${breakpoints.md}px) {
    max-width: ${containerMaxWidths.md}px;
  }
  @media (min-width: ${breakpoints.lg}px) {
    max-width: ${containerMaxWidths.lg}px;
  }
  @media (min-width: ${breakpoints.xl}px) {
    max-width: ${containerMaxWidths.xl}px;
  }
  padding-right: ${gridGutterWidth/2}px;
  padding-left: ${gridGutterWidth/2}px;
  margin-right: auto;
  margin-left: auto;
`;constRow=styled.div`
  display: flex;
  flex-wrap: wrap;
  margin-right: ${-gridGutterWidth/2}px;
  margin-left: ${-gridGutterWidth/2}px;
`;typeColProps={sizeDefault:number;sizeSm?:number;sizeMd?:number;sizeLg?:number;sizeXl?:number;};constCol=styled.div<ColProps>`
  flex: 0 0 ${(props)=>(props.sizeDefault/gridColumns)*100}%;
  max-width: ${(props)=>(props.sizeDefault/gridColumns)*100}%;
  @media (min-width: ${breakpoints.sm}px) {
    flex: 0 0 ${(props)=>((props.sizeSm||props.sizeDefault)/gridColumns)*100}%;
    max-width: ${(props)=>((props.sizeSm||props.sizeDefault)/gridColumns)*100}%;
  }
  @media (min-width: ${breakpoints.md}px) {
    flex: 0 0 ${(props)=>((props.sizeMd||props.sizeDefault)/gridColumns)*100}%;
    max-width: ${(props)=>((props.sizeMd||props.sizeDefault)/gridColumns)*100}%;
  }
  @media (min-width: ${breakpoints.lg}px) {
    flex: 0 0 ${(props)=>((props.sizeLg||props.sizeDefault)/gridColumns)*100}%;
    max-width: ${(props)=>((props.sizeLg||props.sizeDefault)/gridColumns)*100}%;
  }
  @media (min-width: ${breakpoints.xl}px) {
    flex: 0 0 ${(props)=>((props.sizeXl||props.sizeDefault)/gridColumns)*100}%;
    max-width: ${(props)=>((props.sizeXl||props.sizeDefault)/gridColumns)*100}%;
  }
  padding-right: ${gridGutterWidth/2}px;
  padding-left: ${gridGutterWidth/2}px;
`;functionApp(){return<div><GlobalStyle/></div>;
}exportdefaultApp;

ここで各コンポーネントの解説をします。
なお前提として、モバイルファーストで作っています。

Containerコンポーネント

幅はブレークポイントに対応する最大幅を設定します。
中央寄せした上で、グリッド溝/2を左右のpaddingに与えます。

Rowコンポーネント

はじめにdisplayflexにセットして、
グリッド溝/2のネガティブマージンを左右のmarginに与えます。

Colコンポーネント

幅は12グリッドのいくつ分を占めるかをpropsとして受け取れるようにします。
デフォルトの幅は必須で、sm/md/lg/xl用の幅は任意とします。

※sizeDefaultの名前は最初sizeとする予定だったのですが、元々HTMLにsizeという属性があるようなので止めました。

flexの他にmax-widthにおいても幅を設定していますが、前者だけだと特定ブラウザで動かないようなので後者も設定する必要があるらしいです。(Bootstrapのソースコメントによると)
この辺りは申し訳無いのですが調べずにスルーしています。

余談

なるべくシンプルにという方針で、多少1つのブロックにつき繰り返しは多くなりそうですが
css propを使わない(Sassでいうmixinのような用法)方針で実装しました。

5. 実際に使ってみる

前章のコンポーネントを使ってみます。
グリッドを適用する場合はRowを記述し、その子にColをサイズのpropsと共に指定します。

App.css
.side-border{border-right:1pxsolid#000;border-left:1pxsolid#000;}
App.tsx
importReactfrom'react';importstyledfrom'styled-components/macro';import{GlobalStyle}from'./GlobalStyle';import'./App.css';// configsconstbreakpoints:{sm:number;md:number;lg:number;xl:number}={sm:576,md:768,lg:992,xl:1280,};constcontainerMaxWidths:{sm:number;md:number;lg:number;xl:number}={sm:540,md:720,lg:960,xl:1250,};constgridColumns:number=12;constgridGutterWidth:number=32;// componentsconstContainer=styled.div`
  max-width: 100%;
  @media (min-width: ${breakpoints.sm}px) {
    max-width: ${containerMaxWidths.sm}px;
  }
  @media (min-width: ${breakpoints.md}px) {
    max-width: ${containerMaxWidths.md}px;
  }
  @media (min-width: ${breakpoints.lg}px) {
    max-width: ${containerMaxWidths.lg}px;
  }
  @media (min-width: ${breakpoints.xl}px) {
    max-width: ${containerMaxWidths.xl}px;
  }
  padding-right: ${gridGutterWidth/2}px;
  padding-left: ${gridGutterWidth/2}px;
  margin-right: auto;
  margin-left: auto;
`;constRow=styled.div`
  display: flex;
  flex-wrap: wrap;
  margin-right: ${-gridGutterWidth/2}px;
  margin-left: ${-gridGutterWidth/2}px;
`;typeColProps={sizeDefault:number;sizeSm?:number;sizeMd?:number;sizeLg?:number;sizeXl?:number;};constCol=styled.div<ColProps>`
  flex: 0 0 ${(props)=>(props.sizeDefault/gridColumns)*100}%;
  max-width: ${(props)=>(props.sizeDefault/gridColumns)*100}%;
  @media (min-width: ${breakpoints.sm}px) {
    flex: 0 0 ${(props)=>((props.sizeSm||props.sizeDefault)/gridColumns)*100}%;
    max-width: ${(props)=>((props.sizeSm||props.sizeDefault)/gridColumns)*100}%;
  }
  @media (min-width: ${breakpoints.md}px) {
    flex: 0 0 ${(props)=>((props.sizeMd||props.sizeDefault)/gridColumns)*100}%;
    max-width: ${(props)=>((props.sizeMd||props.sizeDefault)/gridColumns)*100}%;
  }
  @media (min-width: ${breakpoints.lg}px) {
    flex: 0 0 ${(props)=>((props.sizeLg||props.sizeDefault)/gridColumns)*100}%;
    max-width: ${(props)=>((props.sizeLg||props.sizeDefault)/gridColumns)*100}%;
  }
  @media (min-width: ${breakpoints.xl}px) {
    flex: 0 0 ${(props)=>((props.sizeXl||props.sizeDefault)/gridColumns)*100}%;
    max-width: ${(props)=>((props.sizeXl||props.sizeDefault)/gridColumns)*100}%;
  }
  padding-right: ${gridGutterWidth/2}px;
  padding-left: ${gridGutterWidth/2}px;
`;// UsageconstHeading=styled.h1`
  font-size: 32px;
  font-weight: bold;
  padding: 24px 0;
`;typeInnerContentProps={height:number;backgroundColor:string};constInnerContent=styled.div<InnerContentProps>`
  height: ${(props)=>props.height}px;
  background-color: ${(props)=>props.backgroundColor};
`;functionApp(){return(<div><GlobalStyle/><ContainerclassName="side-border"><Heading>12Gridsystem</Heading>
<Row>{[0,1,2,3,4,5,6,7,8,9,10,11].map((v,index)=>(<Colkey={index}sizeDefault={1}><InnerContentheight={600}backgroundColor="deepskyblue"></InnerContent>
</Col>
))}</Row>
<Heading>Responsive1</Heading>
<Row><ColsizeDefault={12}sizeMd={4}sizeLg={4}sizeXl={4}><InnerContentheight={200}backgroundColor="lightgray"></InnerContent>
</Col>
<ColsizeDefault={12}sizeMd={4}sizeLg={4}sizeXl={4}><InnerContentheight={200}backgroundColor="darkgray"></InnerContent>
</Col>
<ColsizeDefault={12}sizeMd={4}sizeLg={4}sizeXl={4}><InnerContentheight={200}backgroundColor="gray"></InnerContent>
</Col>
</Row>
<Heading>Responsive2</Heading>
<Row><ColsizeDefault={12}sizeMd={12}sizeLg={4}sizeXl={4}><InnerContentheight={300}backgroundColor="gold"></InnerContent>
</Col>
<ColsizeDefault={12}sizeMd={12}sizeLg={8}sizeXl={8}><InnerContentheight={300}backgroundColor="goldenrod"></InnerContent>
</Col>
</Row>
<Heading>Responsive3</Heading>
<Row><ColsizeDefault={12}sizeLg={8}sizeXl={8}><Row><ColsizeDefault={6}><InnerContentheight={150}backgroundColor="blue"></InnerContent>
</Col>
<ColsizeDefault={6}><InnerContentheight={150}backgroundColor="darkblue"></InnerContent>
</Col>
<ColsizeDefault={6}><InnerContentheight={150}backgroundColor="dodgerblue"></InnerContent>
</Col>
<ColsizeDefault={6}><InnerContentheight={150}backgroundColor="royalblue"></InnerContent>
</Col>
</Row>
</Col>
<ColsizeDefault={12}sizeLg={4}sizeXl={4}><InnerContentheight={300}backgroundColor="crimson"></InnerContent>
</Col>
</Row>
</Container>
</div>
);}exportdefaultApp;

上記を出力するとこのようになります。

スクリーンショット 2020-09-25 7.20.16.png

実際の動きは下記GitHubからリンクを参照してください。

https://github.com/yha-1228/grid-layout-styled-components


Viewing all articles
Browse latest Browse all 8664

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>