さて、すっかりスマートフォンやタブレットが定着して、ユーザーもさまざまな画面幅でWebサイトを見ることが当たり前になりました。
レスポンシブ対応する中でも苦戦するのが「一覧表」、つまり「テーブル」です。
例えば、こんなテーブルがあります。

デスクトップパソコンやiPad(横向き)のようにディスプレイの画面幅が広ければ何も問題ありません。
しかし、スマートフォンのようにディスプレイの画面幅が狭い場合には問題が出ます。
例えば、iPhone SEの画面幅(375px)で表示する場合を想定すると、以下のようになります。

行見出しの「午前」「午後」が縦並びになり、窮屈なレイアウトになってしまいます。
この例では、セルの値が「○」「×」の1文字だけですが、「開催中」「終了しました」のような言葉が入るとかなり見づらくなります。
そこで、Webデザイナーがスマートフォン向けに見やすいデザインを考案してくれました。
こんな感じです。

見出しの「曜日」の部分を左側に配置して、縦方向に伸びるデザインになっています。これなら、土曜や日曜が追加されても縦方向に増えていくので列が圧迫されることもありません。
スマートフォンは画面は縦長であることが多いので、ユーザーに優しいデザインですね。
しかし、Webコーダーには厳しいです。
察しのよいWebコーダーなら見当が付くと思いますが、table要素でマークアップされた表の配置をCSSだけで変える難しいです。特に、この例のように見出しのセルの場所が変わってしまう場合は、HTMLの構造を変える必要があります。
こういったときによく使う対処法は、それぞれのデザインに合わせた構造のテーブルを2つ用意して、CSSのメディアクエリを指定して、画面幅にあったテーブルを表示・非表示にすることです。
この場合、2つの表を管理することになるので、手間が余分にかかります。
そこで、今回は「JavaScriptでテーブルを変化させる」というある意味力技で対応してみましょう。
処理の流れ
まず、JavaScriptでどう処理するか考えてみます。
変化したいtable要素を取得して、さらにその中にあるth, td要素を取得します。
取得した th, td要素 から内容(テキスト)を取得して、見出しとデータに分けて別々の配列にします。
作成しておいた配列から、横方向と縦方向のテーブルになるようにHTMLを出力します。
今回は画面幅が600px以下の場合は縦方向のテーブルにして、それより画面幅が大きい場合は横方向のテーブルに変化させます。
また、画面が可変した場合にもテーブルが変化できるように、メディアクエリーの状態が変化したことを検知するようにします。
テーブルのHTMLについて
今回、テーブルのHTMLは以下のようにしました。
※長いので、アコーディオンで折り畳んでいます。
処理の説明
STEP1 table要素と内包しているth, td要素を取得する
まずは、テーブルを変化させる関数をHTMLの読み込みと解析が完了した後で実行するように、イベントリスナーを設定します。
ここでは関数をinit()とします。
document.addEventListener('DOMContentLoaded', init);次に、関数init()を定義して、テーブル要素を取得します。
取得した要素は変数targetTableに格納します。
function init() {
// テーブル要素を取得する
const targetTable = document.getElementById('targetTable');
}今度は、取得したテーブル要素の中にある、th, td要素を取得します。
曜日見出しと午前・午後の内容は分けて取得して、weekElementsとdayElementsに格納します。
なお、 午前・午後の内容は少し処理が異なるため、ここでは親要素のtr要素を取得して格納します。
function init() {
const targetTable = document.getElementById('targetTable');
// テーブル要素の中にある曜日見出しを格納する
const weekElements = targetTable.querySelectorAll('.weeks th');
// テーブル要素の中に午前・午後の内容を格納する
const dayElements = targetTable.querySelectorAll('tbody tr');
}ここではdocumentではなく、すでに取得した変数targetTableに対してquerySelectorAllメソッドを実行しています。
これは、HTML文書全体から探すのではなく、取得済みの要素から検索することにより、ブラウザの負荷を減らす・うっかり他の関係のない要素を取得することを防ぐ狙いがあります。
ちなみに、特定のテーブル要素に限定してセルの内容を取得するなら、以下のように書くこともできます。
const weekElements = document.querySelectorAll('#targetTable .weeks th');
const dayElements = document.querySelectorAll('#targetTable tbody tr');今回は、テーブル要素に対する操作が必要になるため、まずテーブル要素を取得しておいて、そこからセルの内容を取得する方が効率がよいので、こちらは使いませんでした。
STEP2 セルの内容を取得して配列にする
先ほど取得したのはHTMLの要素なので、この要素からテキストを取得する必要があります。
要素からテキストを取得するには、要素のtextContentプロパティを使います。
曜日見出しのテキストを出力する
まずは動きを確認するために、曜日見出しのテキストを出力してみましょう。
曜日見出しの要素を格納しているweekElementsをforEach()メソッドですべての要素を反復処理します。
weekElements.forEach(item => {
console.log(item.textContent);
});このコードを実行すると、開発ツールのコンソールへ以下のようなログが出力されます。

では、このテキストを配列にしましょう。
曜日見出しの配列を作る
曜日見出しのテキストを格納する配列weeksListを定義します。
// 曜日見出しの配列を定義する
let weeksList = [];weekElementsに格納されているすべての要素をforEach()メソッドで反復処理します。
forEach()メソッドで処理されている要素(ここではitemとして設定)のtextContentプロパティをpush()メソッドで、配列weeksListに追加していきます。
let weeksList = [];
// 取得した要素のテキストを、曜日見出しの配列に追加する反復処理する
weekElements.forEach(item => {
weeksList.push(item.textContent);
});処理された後、配列weeksListをconsole.log()で出力すると以下のように、曜日見出しのテキストが追加されていることが分かります。

午前・午後の内容の配列を作る
曜日見出しは1行だけなので、直接th要素を取得して処理すれば良かったのですが、午前・午後の内容は2行、つまり複数行になります。
将来、別のテーブルに応用できるように、取得した行数分の処理を行えるような設計にしてみましょう。
午前・午後の内容のテキストを格納する配列weeksListを定義します。
// 午前・午後の内容の配列を定義する
let daysList = [];dayElementsに格納されているすべての要素をforEach()メソッドで反復処理します。
// 午前・午後の内容を反復処理する
dayElements.forEach(elements => {
}); dayElements にはtr要素が格納されていますので、 forEach()メソッド を実行すると午前・午後の行ごとに反復処理できます。

dayElements.forEachの処理対象dayElements.forEach()は行単位の反復処理なので、次にその行の中のセル単位で反復処理を行います。
forEach()メソッドで処理されている要素(ここではelementsとして設定)に対して、querySelectorAll()メソッドでth, td要素を取得して、新しく定義したitemsに格納します。
dayElements.forEach(elements => {
// tr要素に内包されているth, td要素を取得する
let items = elements.querySelectorAll('th, td');
});そして、取得したth, td要素が格納されているitemsをforEach()メソッドで 反復処理します。これでセル単位で反復処理されることになります。
dayElements.forEach(elements => {
let items = elements.querySelectorAll('th, td');
// 取得したth, td要素を反復処理する
items.forEach(item => {
});
});
items.forEach()の処理対象あとは、曜日見出しと同じように、forEach()メソッドで渡されるitem のtextContentプロパティを配列に追加すればよいのですが、そのままdaysListに追加すると行の区別なく追加されるので、テーブルを変化させるためのデータとするには都合が悪くなります。
// これはダメな例です
dayElements.forEach(elements => {
let items = elements.querySelectorAll('th, td');
items.forEach(item => {
daysList.push(item.textContent);
});
});上記のコードを実行して、console.log()でdaysListを出力するとこうなります。

この状態でも、行見出しになる「午前」や「午後」の値から行の始まりを判定することもできますが、判定が行見出しのテキストに依存するため、応用が難しくなります。
そこで、dayElements.forEach()の反復処理の中で、処理ごとに新しく配列arrayを定義して、items.forEach()の反復処理の後で dayElements に追加するようにします。
dayElements.forEach(elements => {
// 行単位の内容を格納する配列を定義する
let array = [];
let items = elements.querySelectorAll('th, td');
items.forEach(item => {
// セルのテキストを配列に追加する
array.push(item.textContent);
});
// 行単位の内容を格納した配列を、午前・午後の内容の配列に追加する
daysList.push(array);
}); 記のコードを実行して、console.log()でdaysListを出力するとこうなります。

これでテーブルの内容を、すべて配列にすることができました。
STEP1からSTEP2までのコード
STEP1からSTEP2までのJavaScriptは以下のようになります。
STEP3 取得したテーブルの中身を書き換える
横方向のテーブルのHTMLを生成する
まずは、 横方向のテーブルのHTMLを生成する関数を作成します。
HTMLを生成する関数createHorizontalTable()を定義します。
この関数の引数にweeksListとdaysListを設定して、取得しておいた配列を関数に渡すようにします。
// 横方向のテーブルのHTMLを返す関数
function createHorizontalTable(weeksList, daysList) {
} 横方向のテーブルのHTMLを格納する変数htmlを定義します。このhtmlに配列からテーブルのHTMLを組み立てて格納していきます。
そしてreturn文で、関数を終了してhtmlの内容を関数の呼び出し元に返すようにします。
function createHorizontalTable(weeksList, daysList) {
// HTMLを格納する変数を定義する
let html = '';
// 関数の実行を終了して、関数の呼び出し元に返す
return html;
}曜日見出しを内包する要素の<thead>と<tr>を変数に追加します。
そして、曜日見出しの配列を反復処理して、<th>と</th>を合わせて変数に追加していきます。
反復処理のあとに、<tr>と</thead>を変数に追加します。
// theadとtrの開始タグを追加する
html += '<thead>';
html += '<tr>';
// 曜日見出しの配列を反復処理する
weeksList.forEach(item => {
// 配列の値をthタグを挟んで追加する
html += '<th>' + item + '</th>';
});
// theadとtrの閉じタグを変数に追加する
html += '</tr>';
html += '</thead>';ここまでのコードで、関数の返り値をconsole.log()で出力するとこうなります。

この関数ではHTMLをテキストで返しますが、後にテーブルを書き換えるときにtable要素のinnerHTMLプロパティに上書きするので、問題ありません。
innerHTMLプロパティにHTMLのテキストで上書きすると、HTMLとして解釈して構築されます。
続いて、午前・午後の内容を内包する要素の<tbody>と</tbody>を変数に追加します。
// tbodyの開始タグを追加する
html += '<tbody>';
// tbodyの閉じタグを変数に追加する
html += '</tbody>'; <tbody>と</tbody>を変数に追加する処理の間で、午前・午後の内容の配列daysListをforEach()メソッドで反復処理していきます。
html += '<tbody>';
// 午前・午後の内容の配列を反復処理する
daysList.forEach(items => {
});
html += '</tbody>';この反復処理は下図のように、テーブルの午前と午後の行単位で処理します。

daysList.forEach()の処理対象そのため、メソッドの最初と最後に<tr>と</tr>を変数に追加する処理を書いておきます。
daysList.forEach(items => {
// 行の始まりとして、trの開始タグを追加する
html += '<tr>';
// 行の終わりとして、trの閉じタグを追加する
html += '</tr>';
});daysList.forEach()で渡されるitemsには、午前と午後の内容がそれぞれ配列として格納されているので、これをさらに反復処理します。
daysList.forEach(items => {
html += '<tr>';
// 午前と午後の配列を反復処理する
items.forEach((item, index) => {
});
html += '</tr>';
});今回、行の先頭の午前と午後のセルは見出しのth要素でマークアップするようにしています。
そのため、配列の最初の要素を<th>と</th>で合わせて追加して、その他は<td>と</td>で合わせて追加する必要があります。
そこで、forEach()メソッドの引数のindex(添字)を取得して、indexが0のときは最初の要素を処理していることになりますので、<th>にして、それ以外は<td>にするようにif文を書きます。
// 反復処理で、要素(item)と添字(index)を引数にする
items.forEach((item, index) => {
// indexが「0」(配列の最初の要素)か、判定する
if (index === 0) {
// 配列の最初の要素ならば、thタグで囲む
html += '<th>' + item + '</th>';
} else {
// 配列の最初の要素でなければ、tdタグで囲む
html += '<td>' + item + '</td>';
}
});以上で、 関数createHorizontalTable() ができあがりました。
実際に関数createHorizontalTable() をconsole.log()で出力するとこうなります。

関数createHorizontalTable() 全体のJavaScriptは以下のようになります。
createHorizontalTable() コメントなしcreateHorizontalTable() コメントあり縦方向のテーブルのHTMLを生成する
次に、 縦方向のテーブルのHTMLを生成する関数を作成します。
HTMLを生成する関数createVerticalTable()を定義します。
この関数も引数にweeksListとdaysListを設定して、取得しておいた配列を関数に渡すようにします。
// 縦方向のテーブルのHTMLを返す関数
function createVerticalTable(weeksList, daysList) {
}横方向のテーブルのHTMLを格納する変数htmlを定義しておき、return文でhtmlの内容を関数の呼び出し元に返すようにします。
function createVerticalTable(weeksList, daysList) {
// HTMLを格納する変数を定義する
let html = '';
// 関数の実行を終了して、関数の呼び出し元に返す
return html;
}縦方向のテーブルではthead要素は省略するため、<tbody>と</tbody>を変数に追加します。
function createVerticalTable(weeksList, daysList) {
let html = '';
// tbodyの開始タグを追加する
html += '<tbody>';
// tbodyの閉じタグを変数に追加する
html += '</tbody>';
return html;
}ここからは横方向のテーブルと違った考え方が必要になります。
取得済みのデータは曜日見出し・午前の内容・午後の内容の行単位の配列になっています。
しかし縦方向のテーブルでは、それぞれ列方向にセルが積み上がっているため、一工夫必要になります。

いろいろな手法があるとは思いますが、ここではforEach()メソッドと配列のインデックス(添字)を駆使してHTMLを生成しましょう。
まずは、曜日見出しの配列を forEach()メソッド で反復処理します。
曜日見出しを1つ処理するたびに、「行」として区切る必要があるのでtrタグの追加処理をメソッドの先頭と末尾に書いておきます。
function createVerticalTable(weeksList, daysList) {
let html = '';
html += '<tbody>';
// 曜日見出しの配列を反復処理する
weeksList.forEach((item, index) => {
// trの開始タグを追加する
html += '<tr>';
// trの閉じタグを追加する
html += '</tr>';
});
html += '</tbody>';
return html;
}次の曜日見出しになるth要素のHTMLを追加します。
スタイルの事情で、最初の行とそれ以外の行のth要素には専用のクラスが必要になります。
forEach()メソッドの引数のindex(添字)を取得して、indexが0のときはclass属性にcell-type01を、それ以外にはcell-type03最初を設定するようにif文を書きます。
// ※forEachメソッドの部分を抜粋
weeksList.forEach((item, index) => {
html += '<tr>';
// indexが「0」(配列の最初の要素)か、判定する
if (index === 0 ) {
// 配列の最初の要素ならば、class属性に「cell-type01」を設定する
html += '<th class="cell-type01">' + item + '</th>';
} else {
// 配列の最初の要素でなければ、class属性に「cell-type03」を設定する
html += '<th class="cell-type03">' + item + '</th>';
}
html += '</tr>';
});ここまでのコードで、htmlをconsole.log()を出力するとこうなります。
※ログそのままだと分かりづらいので、改行とインデントをつけています。
<tbody>
<tr>
<th class="cell-type01"></th>
</tr>
<tr>
<th class="cell-type03">月</th>
</tr>
<tr>
<th class="cell-type03">火</th>
</tr>
<tr>
<th class="cell-type03">水</th>
</tr>
<tr>
<th class="cell-type03">木</th>
</tr>
<tr>
<th class="cell-type03">金</th>
</tr>
</tbody>曜日見出しが行ごとに分けて出力できています。
あとは、午前と午後の内容を同じように行ごとに出力すれば、テーブルのHTMLができあがります。
ここで使うのが、配列のインデックスです。
曜日見出しと午前・午後の内容は同数の配列になっており、例えば曜日見出しの月曜日はそれぞれの配列のインデックスは「1」になります。


曜日見出しを反復処理しているときに、処理を行っている要素のインデックスを午前・午後の内容の配列のインデックスを指定すれば、必要なテキストを取得できます。
配列daysListに格納されている午前の内容の配列はdaysList[0]で、午後の内容の配列はdaysList[1]で指定できます。。
午前・午後の月曜日の値を取得する場合は、それぞれdaysList[0][1] と daysList[1][1] と指定すれば取得できます。この[1]をforEach()メソッドのindexで指定すれば、見出しと紐付けられた内容を取得できます。
実際に動作を見てみましょう。以下のように確認用のコードを用意しました。
let html = '';
weeksList.forEach((item, index) => {
html += '<tr>';
html += '<th">' + item + '</th>';
html += '<td>' + daysList[0][index] + '</td>';
html += '<td>' + daysList[1][index] + '</td>';
html += '</tr>';
});ここまでのコードで、htmlをconsole.log()を出力するとこうなります。
※見やすくするために、ログに改行とインデントをつけています。
htmlに 格納されたHTMLちゃんと行ごとに出力できていますね。あとは、実際のテーブルのHTMLを生成できるようにコードを仕上げましょう。
複数箇所でdaysList[0][index]とdaysList[1][index]が出てきますので、見やすくするためにも専用の変数cellAMとcellPMを定義して、そちらに格納しておきます。
あとは、動作確認用のコードにclass属性を設定するようにすれば、できあがりです。
// ※forEachメソッドの部分を抜粋
weeksList.forEach((item, index) => {
// 午前と午後の内容を変数に格納する
let cellAM = daysList[0][index];
let cellPM = daysList[1][index];
html += '<tr>';
// indexが「0」(配列の最初の要素)か、判定する
if (index === 0 ) {
// 配列の最初の要素ならば、class属性「cell-type01」を設定する
html += '<th class="cell-type01">' + item + '</th>';
// 配列の最初の要素ならば、class属性「cell-type02」を設定する
html += '<th class="cell-type02">' + cellAM + '</th>';
html += '<th class="cell-type02">' + cellPM + '</th>';
} else {
// 配列の最初の要素でなければ、class属性に「cell-type03」を設定する
html += '<th class="cell-type03">' + item + '</th>';
html += '<td>' + cellAM + '</td>';
html += '<td>' + cellPM + '</td>';
}
html += '</tr>';
}); このコードで、htmlをconsole.log()を出力するとこうなります。
※見やすくするために、ログに改行とインデントをつけています。
htmlに格納されたHTML 関数createVerticalTable()全体のJavaScriptは以下のようになります。
createVerticalTable() コメントなしcreateVerticalTable() コメントありテーブルの中身を書き換える
テーブルのHTMLを返す関数ができたので、実際のこれらの関数を使ってテーブルの中身を書き換えてみましょう。
まずは、横方向のテーブルを縦方向のテーブルに書き換えます。
これはtable要素のinnerHTMLプロパティを、関数から返ってくるHTMLのテキストで上書きすればよいので、さっそくやってみましょう。
STEP1とSTEP2で説明したコードに、createHorizontalTable()とcreateVerticalTable()を追加します。
// 横方向のテーブルのHTMLを返す関数
function createHorizontalTable(weeksList, daysList) {
// 省略
}
// 縦方向のテーブルのHTMLを返す関数
function createVerticalTable(weeksList, daysList) {
// 省略
}
function init() {
// 省略
}
// HTMLの読み込みと解析が完了した後で関数init()を実行するように、イベントリスナーを設定する
document.addEventListener('DOMContentLoaded', init);関数init()にテーブルを書き換える処理を書いていきます。
書き換えるtable要素は関数init()でtargetTableに格納されていますので、これを使います。
試しに、空白で上書きしてみましょう。
// init()を抜粋
function init() {
const targetTable = document.getElementById('targetTable');
// 中略
// table要素のinnerHTMLを空白で上書きする
targetTable.innerHTML = '';
}このコードを実行すると、table要素の中身(thead, tbody, tr, th, tdなど)が削除され、ブラウザの表示からテーブルが見えなくなります。
table要素は残っていますが、その中身がないのでテーブルがすべて消えたように見えます。
開発ツールで確認すると、tableタグが残っているのが分かります。

それでは、今度は createVerticalTable()を呼び出して、縦方向のテーブルのHTMLに書き換えましょう。
function init() {
const targetTable = document.getElementById('targetTable');
// 中略
// table要素のinnerHTMLを縦方向のテーブルのHTMLで上書きする
targetTable.innerHTML = createVerticalTable(weeksList, daysList);
}このコードを実行すると、横方向のテーブルが縦方向のテーブルに書き換えられます。

テーブルの中身は書き換えられましたが、スタイルが横方向のテーブルのままになっていますが、これはtable要素に指定しているclass属性が横方向のもので指定されていることが原因です。
classList.add()とclassList.remove()で 縦方向のクラスに変更しましょう。
function init() {
const targetTable = document.getElementById('targetTable');
// 中略
targetTable.innerHTML = createVerticalTable(weeksList, daysList);
// 横方向のクラスを削除する
targetTable.classList.remove('horizontal-table');
// 縦方向のクラスを追加する
targetTable.classList.add('vertical-table');
}これで、縦方向のテーブルに書き換えたあと、クラスも変更されるので、スタイルも含めてテーブルを変えることができました。

次に、createHorizontalTable()を呼び出して、縦方向のテーブルのHTMLに書き換えましょう。
function init() {
const targetTable = document.getElementById('targetTable');
// 中略
// 縦方向のテーブルのHTMLで上書きする
targetTable.innerHTML = createVerticalTable(weeksList, daysList);
targetTable.classList.remove('horizontal-table');
targetTable.classList.add('vertical-table');
// 横方向のテーブルのHTMLで上書きする
targetTable.innerHTML = createHorizontalTable(weeksList, daysList);
targetTable.classList.remove('vertical-table');
targetTable.classList.add('horizontal-table');
}このコードを実行すると、横方向のテーブルが縦方向のテーブルに書き換えられたあと、すぐに横方向のテーブルに書き換えます。
そのため、実行しても変化しないように見えますが、実際はHTMLが書き換わっています。
STEP1からSTEP3までのコード
ここで一度JavaScriptをまとめてみましょう。
SETP1からSTEP3までのJavaScriptは以下のようになります。
STEP4 画面幅を判定して、どちらのテーブルに変形するか決める
横方向と縦方向、どちらのテーブルに変化させるかは、デバイスの画面幅によって決める必要があります。
今回は、画面幅が600px以下の場合は縦方向のテーブルにして、それより画面幅が大きい場合は横方向のテーブルに変化させるという条件で作成します。
画面幅の判定については以前記事を書いていますので、そちらも参照してください。

それではコードを書いていきましょう。
画面幅に合わせてテーブルを書き換える
まずは、 関数init()の中でメディアクエリの値を格納する変数mediaQueryを定義して、MediaQueryListオブジェクトのmqlを生成します。
function init() {
// 中略
// 縦方向のテーブルのHTMLで上書きする
targetTable.innerHTML = createVerticalTable(weeksList, daysList);
targetTable.classList.remove('horizontal-table');
targetTable.classList.add('vertical-table');
// 横方向のテーブルのHTMLで上書きする
targetTable.innerHTML = createHorizontalTable(weeksList, daysList);
targetTable.classList.remove('vertical-table');
targetTable.classList.add('horizontal-table');
// メディアクエリの値を定義する
const mediaQuery = '(max-width: 600px)';
// MediaQueryListオブジェクトを作成する
const mql = window.matchMedia(mediaQuery);
}次に、関数init()の中に、新しい関数transformTable()を定義します。
function init() {
// 中略
// 縦方向のテーブルのHTMLで上書きする
targetTable.innerHTML = createVerticalTable(weeksList, daysList);
targetTable.classList.remove('horizontal-table');
targetTable.classList.add('vertical-table');
// 横方向のテーブルのHTMLで上書きする
targetTable.innerHTML = createHorizontalTable(weeksList, daysList);
targetTable.classList.remove('vertical-table');
targetTable.classList.add('horizontal-table');
// 画面幅を判定して、テーブルのHTMLを書き換える関数
function transformTable() {
}
// メディアクエリの値を定義する
const mediaQuery = '(max-width: 600px)';
// MediaQueryListオブジェクトを作成する
const mql = window.matchMedia(mediaQuery);
} transformTable() にテーブルを書き換える処理を移動します。
function init() {
// 中略
// 画面幅を判定して、テーブルのHTMLを書き換える関数
function transformTable() {
// 縦方向のテーブルのHTMLで上書きする
targetTable.innerHTML = createVerticalTable(weeksList, daysList);
targetTable.classList.remove('horizontal-table');
targetTable.classList.add('vertical-table');
// 横方向のテーブルのHTMLで上書きする
targetTable.innerHTML = createHorizontalTable(weeksList, daysList);
targetTable.classList.remove('vertical-table');
targetTable.classList.add('horizontal-table');
}
// メディアクエリの値を定義する
const mediaQuery = '(max-width: 600px)';
// MediaQueryListオブジェクトを作成する
const mql = window.matchMedia(mediaQuery);
}transformTable()に画面幅を判定して、処理を分岐するif文を追加します。
MediaQueryListオブジェクトのmqlのmatchesプロパティにはメディアクエリーに一致していればtrueを、そうでなければfalseが設定されます。
// 画面幅を判定して、テーブルのHTMLを書き換える関数
function transformTable() {
if (mql.matches) {
// メディアクエリー(max-width: 600px)に一致しているので、縦方向のテーブルに書き換える
targetTable.innerHTML = createVerticalTable(weeksList, daysList);
targetTable.classList.remove('horizontal-table');
targetTable.classList.add('vertical-table');
} else {
// メディアクエリー(max-width: 600px)に一致していないので、横方向のテーブルに書き換える
targetTable.innerHTML = createHorizontalTable(weeksList, daysList);
targetTable.classList.remove('vertical-table');
targetTable.classList.add('horizontal-table');
}
}あとは、init()でtransformTable()を呼び出せば、ページを表示した時点の画面幅によって、横方向・縦方向のテーブルに書き換えられた状態でテーブルが表示されます。
function init() {
// 中略
// 画面幅を判定して、テーブルのHTMLを書き換える関数
function transformTable() {
if (mql.matches) {
// メディアクエリー(max-width: 600px)に一致しているので、縦方向のテーブルに書き換える
targetTable.innerHTML = createVerticalTable(weeksList, daysList);
targetTable.classList.remove('horizontal-table');
targetTable.classList.add('vertical-table');
} else {
// メディアクエリー(max-width: 600px)に一致していないので、横方向のテーブルに書き換える
targetTable.innerHTML = createHorizontalTable(weeksList, daysList);
targetTable.classList.remove('vertical-table');
targetTable.classList.add('horizontal-table');
}
}
// メディアクエリの値を定義する
const mediaQuery = '(max-width: 600px)';
// MediaQueryListオブジェクトを作成する
const mql = window.matchMedia(mediaQuery);
// テーブルを書き換える関数を実行する
transformTable();
}

これで、画面幅に合わせてテーブルを変化させることができました。
ウィンドウのリサイズに対応する
この状態でも完成と言えますが、もう一手間加えてページ読み込み後に画面幅が変わってもテーブルが変化するようにしましょう。
MediaQueryListオブジェクトにイベントリスナーを設定して、changeイベントを監視します。
こうすると、メディアクエリーの状態が変わったときに指定した関数を実行できるので、transformTable() を指定します。
function init() {
// 中略
// メディアクエリの値を定義する
const mediaQuery = '(max-width: 600px)';
// MediaQueryListオブジェクトを作成する
const mql = window.matchMedia(mediaQuery);
// テーブルを書き換える関数を実行する
transformTable();
// MediaQueryListオブジェクトでchangeイベントが発生したら、関数を実行する
mql.addEventListener('change', transformTable);
}これでウィンドウの幅が600px以下になればテーブルが縦方向に、600pxより大きくなればテーブルが横方向に変化するようになります。
完成したコード
説明が長くなりました。
完成したコードは以下のようになります。
まとめ
今回は、JavaScriptでHTMLを書き換えてテーブルを変化させる方法を説明しました。
このやり方が正解とは限りません。もっと効率良かったり、スマートな手法があるかと思います。
ただ、この方法が何かの案件に合うこともあると思いますので、手札のひとつになれば幸いです。
テーブルのレスポンシブ対応は難しいですね…。
