Vue.jsで昔懐かしのframeタグを実現する方法

#前置き
HTML5以前の時代には、frameタグなるものが存在していました。
１つのWEBページの中に複数のWEBページを埋め込んで上下や左右に並べて表示させるタグです。
2000年代前半あたりにはframeタグを活用した個人サイトなどが数多く存在したような気がします。
(index.htmlの中にframesetを書いてフレームの左側にmenu.html、右側にtop.htmlとかやってたり)

HTML5が登場してからはframeタグはframesetタグと共に非推奨となり、今ではもうこのタグを使っているサイトはほぼ無いと思います。

ですがもしかするとframeタグが今でも好きな方がいらっしゃるのではと思い、代替案としてVue.jsを用いてframeタグらしきものを再現してみました。
(実際に需要があるかどうかは特に調査していませんので気にしないでください。)

#デモ
[vue-frame](https://de-teiu.github.io/vue-frame/)
(frameの境目をドラッグしてコンテンツの幅を変えられます)


#動作イメージ
![vue-frame.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/173272/289bed3d-483d-643d-0224-040c96840d03.gif)


#ざっくり解説
##構成
<img width="181" alt="キャプチャ.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/173272/2a292d8b-3d1f-35e6-9bfd-ebfa917b5807.jpeg">

### 左側

```LeftFrame.vue
<template>
  <div class="left-frame" v-bind:style="{width:width + 'px'}">
    <div class="left-frame-content">ヒダリー</div>
    <div class="frame-border" @mousedown="$emit('startResize')"></div>
  </div>
</template>

<script>
export default {
  props: {
    width: Number
  }
};
</script>

<style scoped>
.left-frame {
  background-color: rgb(240, 240, 255);
  display: flex;
}

.left-frame-content {
  flex-grow: 1;
  padding-left: 10px;
}

.frame-border {
  width: 3px;
  background-color: rgb(208, 208, 208);
  border-left: solid 0.5px rgb(170, 170, 170);
  border-right: solid 0.5px black;
}

.frame-border:hover {
  cursor: col-resize;
}
</style>
```
- コンテンツの横幅は可変にするため、変数で保持
- フレームの境界線にはドラッグイベントを持たせるため、borderっぽいdiv要素(frame-border)を配置

### 右側

```jRightFrame.vue
<template>
  <div class="right-frame">ミギー</div>
</template>

<style scoped>
.right-frame {
  background-color: white;
  flex-grow: 1;
  padding-left: 10px;
}
</style>
```
- 画面右側に表示させたいコンテンツを配置するだけ

###本体

```Frame.vue
<template>
  <div
    class="frame"
    v-bind:class="{dragged: isDragged}"
    @mousemove="resizeFrame"
    @mouseup="endResizeFrame">
    <left-frame v-bind:width="leftWidth" @startResize="startResize"></left-frame>
    <right-frame></right-frame>
  </div>
</template>

<script>
import LeftFrame from "./LeftFrame.vue";
import RightFrame from "./RightFrame.vue";

const LEFT_FRAME_MIN_WIDTH = 45;
const FRAME_ADJUSTED_SETTING = 2;

export default {
  components: {
    LeftFrame,
    RightFrame
  },
  data() {
    return {
      isDragged: false,
      leftWidth: 200
    };
  },
  methods: {
    startResize() {
      this.isDragged = true;
    },
    resizeFrame(event) {
      if (event.buttons === 0) {
        this.endResizeFrame();
        return;
      }
      if (this.isDragged) {
        if (event.clientX + FRAME_ADJUSTED_SETTING < LEFT_FRAME_MIN_WIDTH) {
          this.leftWidth = LEFT_FRAME_MIN_WIDTH;
          return;
        }
        this.leftWidth = event.clientX + FRAME_ADJUSTED_SETTING;
      }
    },
    endResizeFrame() {
      this.isDragged = false;
    }
  }
};
</script>

<style scoped>
.frame {
  display: flex;
  flex-direction: row;
  height: 100vh;
}

.dragged * {
  cursor: col-resize;
}
</style>

```
- ドラッグ開始(startResize())はLeftFrameから発火させる
- mousemoveイベントでマウスの位置を監視する
- event.buttonsの値でマウスの押下状態を判断(===0ならマウス押下無し)
- マウスの位置(event.clientX)でleftFrameの幅を変更させる(フレームのstyleに合わせて適当に微調整)


#ソースコード全体
[vue-frame(GitHub)](https://github.com/de-teiu/vue-frame)

#参考資料

[&lt;frame&gt; - HTML: HyperText Markup Language | MDN](https://developer.mozilla.org/ja/docs/Web/HTML/Element/frame)

[jQueryを使った擬似フレーム（高機能版）](http://labo.ek-pro.com/jQuery%25A4%25F2%25BB%25C8%25A4%25C3%25A4%25BF%25B5%25BC%25BB%25F7%25A5%25D5%25A5%25EC%25A1%25BC%25A5%25E0%25A1%25CA%25B9%25E2%25B5%25A1%25C7%25BD%25C8%25C7%25A1%25CB.html)