Panda Noir

JavaScript の限界を究めるブログでした。最近はいろんな分野を幅広めに書いてます。

rowspanを考慮してtable要素を2次元配列に変換する(第一正規化する)

rowspan、colspanがあるとtd:nth-of-type などがズレてしまう。

td:nth-of-type(2) に背景色をつけた図

html

<table>
  <tr>
    <td>col1,1</td>
    <td rowspan="3">col1,2</td>
    <td>col1,3</td>
  </tr>
  <tr>
    <td>col2,1</td>
    <td>col2,2</td>
  </tr>
  <tr>
    <td>col3,1</td>
    <td>col3,2</td>
  </tr>
  <tr>
    <td>col4,1</td>
    <td>col4,2</td>
    <td>col4,3</td>
  </tr>
</table>

2列目に色をつけたいのに、col2,2col3,2にも色がついてしまっている。本来はこうなってほしい↓

2列目だけに色がついている図

これをするには第一正規化してやればよい。正規化する関数 normalizeTable の実装はこちら。

const normalizeTable = (table: HTMLTableElement) => {
  const rows = table.querySelectorAll<HTMLTableRowElement>('tr');
  const tableCells = Array.from(rows).map((row) =>
    Array.from(
      row.querySelectorAll<
        HTMLTableDataCellElement | HTMLTableHeaderCellElement
      >('td, th'),
    ),
  );

  for (let r = tableCells.length - 1; r >= 0; r--) {
    const row = tableCells[r];
    if (!row) continue;
    for (let c = row.length - 1; c >= 0; c--) {
      const cell = row[c];
      if (!cell) continue;
      if (cell.rowSpan === 1 && cell.colSpan === 1) continue;
      for (let i = 0; i < cell.rowSpan; i++) {
        for (let j = 0; j < cell.colSpan; j++) {
          if (i === 0 && j === 0) continue;
          tableCells[r + i]?.splice(c + j, 0, cell);
        }
      }
    }
  }
  return tableCells;
};

tableCells[row][col] には見た目の位置どおりのtd要素が入っている(rowspanが指定されたtdは複数箇所に入ってる)。あとは、normalizeTable を使って適当にクラスを振ってやればよい。

const cells = normalizeTable(document.querySelector('table'))
for (const row of cells) {
  for (let i = 0; i < row.length; i++) {
    row[i].classList.add(`col${i}`);
  }
}

これで td.col1 に背景色をつけてやれば意図した通りにうごく