0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Reactで行列を固定した表をgridレイアウトで作ろう

Posted at

はじめに

Reactでフロントエンドを開発していると、カレンダーなどテーブル表記を行いたい場面はたくさんあるかなと思います。
Excelのように、行列の一部を固定して、中身だけスクロールさせたいなんていうデザイン要望にぶち当たることはままあるのではないでしょうか?
今回、コード例を載せながら、scssでどんなstyleを当てれば、それが実装できるのか?を備忘録として残しておきたいと思います。

完成形はこんな感じです。

Resultの表示が見にくいと思うので、
0.5xのボタンをクリックして小さくして動かしてみてください

See the Pen gridLayout-sample5 by Yoshi2885 (@Yoshi2885) on CodePen.

スタイルなしの状態を作成

今回のサンプルは適当にボタンやテキスト、チェックボックスなどが存在する表を作成してみようと思います。
まずは、単純に要素を並べたものがこちらです。
(便宜上一部のクラス名だけつけちゃってます。)

See the Pen gridLayout-sample by Yoshi2885 (@Yoshi2885) on CodePen.

ヘッダー部分を作る

まずは、ヘッダー部分にスタイルを当てていこうと思います。

See the Pen gridLayout-sample2 by Yoshi2885 (@Yoshi2885) on CodePen.

表示領域内でx, y方向にスクロールさせたいので、親要素にoverflow-x, overflow-yautoを入れています。
また、ヘッダーをstickyにして固定したいのでposition: relative;position: sticky;を使用しています。ヘッダーの背景は、widthを指定しないとスクロールの範囲外には色がつかなくなってしまうので計算した値を入れています。
(コード詳細は、codepenの中身を参照してください)

コンテンツ内のレイアウトを調整する

ヘッダーの配置は決まったので次はコンテンツのそれぞれの配置を調整します。

Resultの表示が見にくいと思うので、
0.5xのボタンをクリックして小さくして動かしてみてください

See the Pen gridLayout-sample3 by Yoshi2885 (@Yoshi2885) on CodePen.

以下のように、contentRowクラスに対してスタイルを当てていきます。

.modal {
  display: flex;
  flex-direction: column;
  background-color: lightgray;
  width: 800px;
  height: 600px;
  margin: auto;
  overflow-x: auto;
  overflow-y: auto;
  position: relative;

  .header {
    display: grid;
    grid-template-columns: 120px 200px repeat(5, 160px);
    text-align: center;
    position: sticky;
    top: 0;
    left: 0;
    width: 1120px;
    background-color: #d1d7e0;
  }

+ .contentRow {
+   display: grid;
+   grid-template-columns: 120px 200px repeat(5, 160px);
+   align-items: center;
+   border-bottom: 1px solid black;
+   width: 1120px;
+ }
}

checkboxのところにスクロールしてみてみると、縦の位置がしっかり揃っていることがわかると思います。

左の2列も固定化する

ヘッダー部分はstickyの状態にすることができていますが、ボタンと名前の列も固定化してチェックボックスがある部分だけをスクロールするように変更したいと思います。

これを実装するには、実装側のclassNameを追加する必要があります。

const DEFAULT_USER_NAME = [...Array(30)].map(
  (_, index) => `sample name${index + 1}`
);

const ChildComponent = () => {
  return (
    <div className="modal">
      <div className="gridLayoutWrapper">
        <div className="header">
+         <div className={`stickyColumn deleteRow`}></div>
+         <div className={`stickyColumn name`}>名前</div>
          {[...Array(5)].map((_, index) => (
            <div>{`someColumn${index + 1}`}</div>
          ))}
        </div>

        {DEFAULT_USER_NAME.map((name, index) => (
          <div className="contentRow" key={`row-${index + 1}`}>
            <button
+             className={`stickyColumn deleteRow`}
              key={`deleteRow-${index + 1}`}
            >
              deleteRow
            </button>
+           <div className={`stickyColumn name`} key={`tableContents-${index}`}>
              {name}
            </div>
            {[...Array(5)].map((_, index) => (
              <input type="checkbox" key={`column${index + 1}`} />
            ))}
          </div>
        ))}
      </div>
    </div>
  );
};

stickyColumn, deleteRow, nameのクラスを追加しました。

scssの方は、

.modal {
  display: flex;
  flex-direction: column;
  background-color: lightgray;
  width: 800px;
  height: 600px;
  margin: auto;
  overflow-x: auto;
  overflow-y: auto;
  position: relative;

  .header {
    display: grid;
    grid-template-columns: 120px 200px repeat(5, 160px);
    text-align: center;
    position: sticky;
    top: 0;
    left: 0;
    width: 1120px;
+   background-color: #d1d7e0;
+   z-index: 30;

+   .stickyColumn {
+     position: sticky;
+     z-index: 40;
+     background-color: #d1d7e0;
+   }

+   .deleteRow {
+     left: 0;
+     width: 120px;
+   }

+   .name {
+     left: 120px;
+     width: 200px;
+   }
  }

  .contentRow {
    display: grid;
    grid-template-columns: 120px 200px repeat(5, 160px);
    align-items: center;
    border-bottom: 1px solid black;
    width: 1120px;

+   .stickyColumn {
+     position: sticky;
+     background-color: #d1d7e0;
+     z-index: 20;
+   }

+   .deleteRow {
+     left: 0;
+     width: 120px;
+   }

+   .name {
+     left: 120px;
+     width: 200px;
+   }
  }
}

これらを追加し、左のに列を固定化するのと同時に、
z-indexbackground-colorを追加することで裏に入り込むような挙動になるように調整しました。

ここまでのコードの動きはこんな感じです。

Resultの表示が見にくいと思うので、
0.5xのボタンをクリックして小さくして動かしてみてください

See the Pen gridLayout-sample4 by Yoshi2885 (@Yoshi2885) on CodePen.

動きは完成しましたが、もう少しだけ調整をしたいと思います。

見た目微調整

全体的に行がピッチピチなので、大人の余裕の如く、隙間を設けてあげようと思います。
あとは好みの色にしたり、遊び心で線を足してみたりしてます。
この辺りはお遊びなのでCodePenのStyle参照でお願いします。

完成形は以下の通りです(再掲)。

Resultの表示が見にくいと思うので、
0.5xのボタンをクリックして小さくして動かしてみてください

See the Pen gridLayout-sample5 by Yoshi2885 (@Yoshi2885) on CodePen.

最後に

display: flex;はよく使いますが、
display: grid;をちゃんと使ってみたのは初めてでした。
CodePenの環境ではありますが、いろいろ試してみてこんなレイアウトを結構簡単に整えられるんだな〜というのを知れていい学びでした。
Headerを固定化するのはよくやったことがありますが、
列を固定化するのにclassNameをそれぞれに当てて…とやることで実現できるんですね💡

もっと賢い記述方法もきっとあると思いますが、
それはおいおい勉強することにしようと思います。

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?