【簡単！】リサイズ可能な 2 分割ビューレイアウトを JavaScript で実装

# はじめに
本記事では、以下のような**リサイズ可能な 2 分割ビューレイアウト**を `JavaScript` で実装する方法を紹介します。



<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2801269/fde4159a-6d00-df74-d350-72f47b1c45c1.gif" alt="demo" width="500" >

左側のエリアと右側のエリアの間に配置されたボーダーをドラッグすることで、エリアの幅を動的に調整できるレイアウトを実現しています。

# 実装

## HTML

`HTML` では、コンテナ要素（`container`）の中に3つの子要素を配置しています。

```html
<div class="container" id="container">
    <div id="left-side" class="left-side"></div>
    <div id="border" class="border"></div>
    <div id="right-side" class="right-side"></div>
</div>
```
- `left-side`: 左側のエリア。
- `border`: 左右のカラムを分けるボーダー。ドラッグ操作で使用します。
- `right-side`: 右側のエリア。


# CSS
```html
<style>
    *, *::before, *::after {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
    }
    .container {
        position: relative;
        display: flex;
        width: 100vw;
    }
    .container .left-side {
        position: relative;
        height: 100vh;
        overflow-y: scroll;
        background-color: #fbfbfb;
    }
    .container .right-side {
        flex: 1;
        position: relative;
        height: 100vh;
        overflow-y: scroll;
        background-color: #fff;
    }
    .container .border {
        width: 8px;
        margin: 0px -4px;
        overflow: hidden;
        position: relative;
        z-index: 50;
        cursor: col-resize;
    }
    .container .border::after {
        content: "";
        display: block;
        position: absolute; 
        top: 0px; 
        left: 3px;
        bottom: 0px;
        width: 2px;
        background: #f5f5f5;
    }
</style>
```

**ポイント**
- **`flex` レイアウト**
  - `container` をフレックスボックスとして設定し、左右のエリアが横に並ぶようにしています。
  - `right-side` に `flex: 1;` を設定することで、右側のエリアが残りの幅を柔軟に占有します。
- **`border` のスタイリング**
  - 幅 `8px` のドラッグ可能なエリアを作成します。
  - カーソルを `col-resize` に設定して、ドラッグ可能であることを示します。

# JavaScript

`JavaScript` では、ボーダーのドラッグ操作に応じて `left-side` の幅を動的に変更します。

```html
<script>
    let isScrubbing = false;
    let mousePosition = { x: null };
    const sectionMinWidth = 150 // px
    const leftSide = document.getElementById('left-side');

    leftSide.style.width = '50%';
    document.getElementById('border').addEventListener('pointerdown', () => isScrubbing = true);
    document.addEventListener('pointermove', handlePointerMove);
    document.addEventListener('pointerup', () => isScrubbing = false);
    document.addEventListener('pointerleave', () => isScrubbing = false);

    function handlePointerMove(e) {
        if(!isScrubbing) return;
        
        mousePosition = { x: e.pageX };

        if(
            !mousePosition.x || 
            mousePosition.x > (window.innerWidth - sectionMinWidth) ||
            mousePosition.x < sectionMinWidth
        ) return;

        leftSide.style.width = mousePosition.x + 'px';
    }
</script>
```

**動作の流れ**

1. **初期設定**
  ページ読み込み時に、`left-side` の幅を画面の `50%` に設定します。
  ボーダーにドラッグの開始イベント（`pointerdown`）を設定します。
2. **ドラッグ中の処理**
  マウスカーソルの `x` 座標を取得し、それに応じて `left-side` の幅を更新します。
  左右エリアの最小幅を `150px` に制限することで、エリアが極端に小さくなるのを防ぎます。
3. **ドラッグ終了時の処理**
  マウスボタンを離したときやカーソルが画面外に移動したときに、ドラッグ状態（`isScrubbing`）を終了します。

# おまけ
左右のエリアに文字列がある場合、ドラッグ時に文字を選択してしまうことがあります。`user-select` を `none` にすることで防ぐことができます。

```javascript
const container = document.getElementById('container');

document.getElementById('border').addEventListener('pointerdown', handlePointerDown);
function handlePointerDown(e) {
    isScrubbing = true;
    container.style.userSelect = 'none';
    container.style.MozUserSelect = 'none';
    container.style.WebkitUserSelect = 'none';
    container.style.msUserSelect = 'none';
}; 

document.addEventListener('pointerup', handlePointerEnd);
document.addEventListener('pointerleave', handlePointerEnd);
function handlePointerEnd(e) {
    isScrubbing = false;
    container.style.userSelect = 'unset';
    container.style.MozUserSelect = 'unset';
    container.style.WebkitUserSelect = 'unset';
    container.style.msUserSelect = 'unset';
};
```