Emotion Wave Tech Blog

福岡にあるエモーションウェーブ株式会社のエンジニアが書いています。

htmlテーブルをレスポンシブに

目次

画面幅に合わせて配置が変わるテーブル

表を、画面サイズに合わせて狭めたり広げてみせたり、ということを試してみました。 画面幅に合わせて、3列、2列、1列と変化します。

f:id:devew:20200619192111g:plain

これ、IEでは正常に動きませんでした。 Chromeでは動きました。あしからず。

ソースはこんな感じです。

<!doctype html>
    <head>
       <meta charset="utf-8">
       <meta http-equiv="X-UA-Compatible" content="IE=edge">
       <title>ファイルアップロード</title>
       <style type="text/css">
table{
    max-width: 1139px;
    width:100%;
}

table,th,td{
    border-collapse: collapse;
    border:1px solid #333;
    vertical-align: baseline;
    margin:0;
    padding:0;
}


table tr{
    display: inline;
    float: left;
}


table th{
    background-color: #CCCCCC;
}

.department{
    width:80px;
}

.num{
    width:80px;
}

.name{
    width:80px;
}

.position{
    width:120px;
}

.detail.department{
    background-color: #CCCCCC;
}

        </style>
       <script
          src="https://code.jquery.com/jquery-1.12.4.min.js"
          integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ="
          crossorigin="anonymous"></script>

       <script>
$(document).ready(function(){

   // 初期表示処理
   tableResize();

   //リサイズされたときの処理
   $(window).resize(function() {

       // ダミー行を消し、隠し列を表示に戻す
       $('#datatable tr[data-department="dummy"]').remove();
       $('#datatable td.department').css('display','table-cell');

       tableResize();
   });


   // リサイズ時イベント
   function tableResize(){
       // テーブル幅をtr幅で割る
       switch (Math.floor($("#datatable").width() / 370)) {
         case 0:
         case 1:
           console.log('1行に1人表示');
           break;
         case 2:
           console.log('1行に2人表示');
           addDummyTr(2);
           break;
         case 3:
         default:
           console.log('1行に3人表示');
           addDummyTr(3);
           break;
       }
   }

   function addDummyTr(humanCnt){

       // ヘッダ部調整用の仮行
       var DUMMY_DETAILHEADER_TMPL ='<tr data-department="dummy"><th class="header department">所属</th><th class="header num">社員番号</th><th class="header name">氏名</th><th class="header position">役職</th></tr>';

       var dummy_detailheader = "";

       // ヘッダに調整用の行を追加する。
       for (var i = 1; i < humanCnt; i++) {
           dummy_detailheader = dummy_detailheader + DUMMY_DETAILHEADER_TMPL;
       }

       $('#datatable thead tr:last').after(dummy_detailheader);

       // ヘッダ2番目以降の所属列を消す
       $('#datatable thead th.department:gt(0)').css('display','none');

       // 明細部調整
       // まず、所属を全て取得し一意にする。
       var departmentList = [];
       $('#datatable tbody tr').each(function(index, element){
           departmentList.push($(element).data('department'));
           
       });
       departmentList = departmentList.filter(function (x, i, self) {
         return self.indexOf(x) === i;
       });

       console.log('departmentList:' + departmentList);


       var DUMMY_DETAILROW_TMPL ='<tr data-department="dummy"><td class="department"></td><td class="num" style="color: transparent;">dummy</td><td class="name"></td><td class="position"></td></tr>';

       // 例として、1行に2人の場合は、所属1人だとスキマができるため、スキマ埋めのためのdummy行を追加する。
       for(let v of departmentList) {

           // スキマ埋めすべきdummy行数を求める。
           let addDummyRowCnt = 0;
           let departmentLength = $('#datatable tbody tr[data-department="' + v + '"]').length
           if(departmentLength <= humanCnt) {
               // 例:所属2人で、1行に3人表示の場合は、1人分のスキマ埋めが必要。
               addDummyRowCnt = humanCnt-departmentLength
           } else {
               // 例:所属4人で、1行に3人表示の場合は、2行になるため、この所属の必要な領域は3×2=6となる。2人分のスキマ埋めが必要。
               addDummyRowCnt = (Math.ceil(departmentLength / humanCnt) * humanCnt) % departmentLength;
           }

           // スキマ分、ダミー行を追加する。
           for(let i = 0; i < addDummyRowCnt; i++) {
               $('#datatable tbody tr[data-department="' + v + '"]:last').after(DUMMY_DETAILROW_TMPL);
           }
       }
           

       // 1行に2人以上の場合、表示上の左から1番目の所属列は残し、後の所属列は消す。
       if(humanCnt > 1){

           // 1行あたりの表示人数の倍数ではない行(見た目上、右側に移った行)は所属列を消す
           $('#datatable tbody tr').filter(function(index) {
               return index % humanCnt >= 1;
           }).find('td.department').css('display','none');

       }
   }
});

       </script>
   </head>
    <body>
        <form action="" method="post">
            <table id="datatable">
                <thead>
                    <tr>
                        <th class="header department">所属</th>
                        <th class="header num">社員番号</th>
                        <th class="header name">氏名</th>
                        <th class="header position">役職</th>
                    </tr>
                </thead>
                <tbody>
                    <tr data-department="tokyo">
                        <td class="detail department">東京</td>
                        <td class="detail num">3</td>
                        <td class="detail name">田中</td>
                        <td class="detail position">課長</td>
                    </tr>
                    <tr data-department="tokyo">
                        <td class="detail department">東京</td>
                        <td class="detail num">11</td>
                        <td class="detail name">鈴木</td>
                        <td class="detail position">主任</td>
                    </tr>
                    <tr data-department="tokyo">
                        <td class="detail department">東京</td>
                        <td class="detail num">16</td>
                        <td class="detail name">内海</td>
                        <td class="detail position">パート</td>
                    </tr>
                    <tr data-department="tokyo">
                        <td class="detail department">東京</td>
                        <td class="detail num">5</td>
                        <td class="detail name">田口</td>
                        <td class="detail position">パート</td>
                    </tr>
                    <tr data-department="nagoya">
                        <td class="detail department">名古屋</td>
                        <td class="detail num">14</td>
                        <td class="detail name">富田</td>
                        <td class="detail position">主任</td>
                    </tr>
                    <tr data-department="osaka">
                        <td class="detail department">大阪</td>
                        <td class="detail num">7</td>
                        <td class="detail name">松永</td>
                        <td class="detail position">主任</td>
                    </tr>
                    <tr data-department="osaka">
                        <td class="detail department">大阪</td>
                        <td class="detail num">14</td>
                        <td class="detail name">井ノ瀬</td>
                        <td class="detail position">パート</td>
                    </tr>
                    <tr data-department="fukuoka">
                        <td class="detail department">福岡</td>
                        <td class="detail num">21</td>
                        <td class="detail name">牧田</td>
                        <td class="detail position">主任</td>
                    </tr>
                    <tr data-department="fukuoka">
                        <td class="detail department">福岡</td>
                        <td class="detail num">6</td>
                        <td class="detail name">水島</td>
                        <td class="detail position">リーダー</td>
                    </tr>
                    <tr data-department="fukuoka">
                        <td class="detail department">福岡</td>
                        <td class="detail num">16</td>
                        <td class="detail name">堀川</td>
                        <td class="detail position">パート</td>
                    </tr>
                    <tr data-department="kagosshima">
                        <td class="detail department">鹿児島</td>
                        <td class="detail num">27</td>
                        <td class="detail name">大迫</td>
                        <td class="detail position">主任</td>
                    </tr>
                </tbody>
            </table>
        </form>
    </body>
</html>

考え方

  • テーブルのtrタグをinlineにし、行を横並びに出来るようにする。
  • 所属ごとに人数が違うので、javascriptに動的に空白行を追加したりして、ちょうどよい見た目にする。

やり方

ポイントは、cssでtrタグをdisplay: inline;にしていること。

table tr{
    display: inline;
    float: left;
}

これでtrが横に並びます。 画面横幅に合わせて勝手に折り返してくれます。

とはいえ、所属によって人数が異なるので、名古屋所属の人が東京の行に表示されたりしておかしくなります。 そこで空白行を埋めることで、調整します。

テーブルの幅を検知して、何列表示になるのか判断します。

   // リサイズ時イベント
    function tableResize(){
        // テーブル幅をtr幅で割る
        switch (Math.floor($("#datatable").width() / 370)) {
          case 0:
          case 1:
            console.log('1行に1人表示');
            break;
          case 2:
            console.log('1行に2人表示');
            addDummyTr(2);
            break;
          case 3:
          default:
            console.log('1行に3人表示');
            addDummyTr(3);
            break;
        }
    }

trにはデータ属性departmentを定義しており、どこの所属かわかるようにしています。 javascriptのaddDummyTr関数内で、所属ごとの人数から、必要な空行を追加します。

f:id:devew:20200619191833p:plain

f:id:devew:20200619191937p:plain

横幅に合わせて、ちょうどよくなるように、空白行が追加されました。

次に、ヘッダ行も表示します。 ヘッダのtrタグの後ろに、ヘッダをさらに追加してしまいます。 1行に2人表示する画面幅なら、ヘッダを1行追加し、 3人表示するなら、ヘッダを2行追加します。

   // ヘッダ部調整用の仮行
    var DUMMY_DETAILHEADER_TMPL ='<tr data-department="dummy"><th class="header department">所属</th><th class="header num">社員番号</th><th class="header name">氏名</th><th class="header position">役職</th></tr>';

    var dummy_detailheader = "";

    // ヘッダに調整用の行を追加する。
    for (var i = 1; i < humanCnt; i++) {
        dummy_detailheader = dummy_detailheader + DUMMY_DETAILHEADER_TMPL;
    }

    $('#datatable thead tr:last').after(dummy_detailheader);

f:id:devew:20200619191958p:plain

f:id:devew:20200619192014p:plain

これでヘッダも付きました。

所属列は要らないので消します。

   // ヘッダ2番目以降の所属列を消す
    $('#datatable thead th.department:gt(0)').css('display','none');

departmentはヘッダの所属列についたクラスです。 gt(0)で、2番目以降が選択対象になります。 それをdisplay:noneにして見えなくします。

これで完成です。

あとがき

同じことを実現するにも、様々なやり方があるでしょうし、外部のモジュールを取り入れたら、あっさり実現できたと思います。

今回の書き方が、きれいな正当なやり方だとは思っていませんが、お勉強兼ねてやってみました。