iframeの中のaタグをクリックした際に、iframeの高さを遷移したページの高さ分に変更させる

1. iframe内のaタグをクリック
2. iframe内のページ遷移
3. ページの高さによって、元ページでの高さを変更

のような挙動を実装した時にiframeについて理解することができたので、その時に行ったことを記載します。

**注意！！**
クロスドメイン（親ページと子ページのドメインが異なる）の場合、この方法は適用できない可能性があります。

**参考**

https://tm-progapp.hatenablog.com/entry/2021/05/10/220210

# iframeタグとは

> `<IFRAME>`はInline Frameの略です。`<IFRAME>`を使用すると、 `<FRAMESET>`のようにウィンドウを水平・垂直に分割する形式だけではなく、 ウィンドウの中に独立して表示される形式のインラインのフレームが作成できます。
`<IFRAME>`タグは`<BODY>`～`</BODY>`内で使用します。 src属性でフレーム内に表示する内容を指定します。name属性でフレームに名前を付けることで、 そのインラインフレームをリンクの表示先として指定することができます。

http://www.htmq.com/html/iframe.shtml

# 制作したもの

https://iframe-sample.netlify.app/

# 検証したこと

- iframe内のスタイルとtop内のスタイルの`!important`のどちらが優先されるのか検証
- iframe内のリンクを押すと、iframe内のページ遷移をしiframeのbodyの高さ分要素の高さを変更

# 制作したコードについて

## HTML

- iframe外のhtml

```html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="./css/top.css">
  <script src="./js/top.js" defer></script>
  <title>iframe - sample</title>
</head>
<body>
  <p>トップ</p>
  <div>
    <iframe class="js-iframe" src="./move/page1.html" frameborder="0"></iframe>
  </div>
  <p>テキスト</p>
</body>
</html>
```

- iframe内のhtml(page1.html)

```html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="../css/page1.css">
</head>
<body>
  <p>page1</p>
</body>
</html>
```

- iframe内のhtml(page2.html)

```html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="../css/page2.css">
</head>
<body>
  <p>page2</p>
</body>
</html>
```

## CSS

- iframe内のcss

```css
p {
  color: #333 !important;
}
```

- iframe外のcss(page1.css)

```css
* {
  margin: 0;
  box-sizing: border-box;
}

.border {
  border: 1px solid #f0f
}

a {
  color: inherit;
}

p {
  color: #f00;
}
```

- iframe外のcss(page2.css)

```css
* {
  margin: 0;
  box-sizing: border-box;
}

.border {
  height: 100px;
  border: 1px solid #f00
}

a {
  color: inherit;
}

p {
  color: #00f;
}
```

## JavaScript

```js
// iframeのコンテンツの要素取得
function getIframeElem() {
  const elemIframe = document.querySelector('.js-iframe')
  return { elemIframe }
}

// iframeの要素の中にあるbodyの高さを取得
function getIframeHeight(elemIframe) {
  const iframeBodyHeight = elemIframe.contentWindow.document.body.clientHeight
  return { iframeBodyHeight }
}

// iframeの中にあるリンクタグの要素一覧を取得
function getElemIframeLink(elemIframe) {
  const elemIframeLinks = elemIframe.contentWindow.document.querySelectorAll('.js-link')
  return { elemIframeLinks }
}

// iframe内の要素をクリックした時の処理
function clickEvent(elemIframe) {
  elemIframe.addEventListener('load', loadEvent)
}

// ページが読み込まれた時の処理
function loadEvent() {
  const { elemIframe } = getIframeElem()
  const { iframeBodyHeight } = getIframeHeight(elemIframe)
  const { elemIframeLinks } = getElemIframeLink(elemIframe)
  elemIframe.height = iframeBodyHeight + 2 + 'px'
  elemIframeLinks.forEach(function(link) {
    link.addEventListener('click', clickEvent(elemIframe))
  })
}

window.addEventListener('load', function() {
  loadEvent()
}, false)
```

# 行ったこと

## 1. iframe内のスタイルとtop内のスタイルの`!important`のどちらが優先されるのか検証

```css
/* ページ全体 */
p {
  color: #333 !important;
}
```

```css
/* iframe内 */
p {
  color: #f00;
}
```

### 結果

結果として、iframe内のスタイルはページ全体のスタイルと競合しない

- iframe外のpタグ

![スクリーンショット 2021-12-24 0.40.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214546/b897123d-f94a-0b36-6fda-ac32725c6abc.png)

- iframe内のpタグ

![スクリーンショット 2021-12-24 0.39.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214546/1dc412b2-707c-c1e2-7c4f-5e40cb967861.png)

## 2. iframe内のリンクを押すと、iframe内のページ遷移をしiframeのbodyの高さ分要素の高さを変更

行いたいことは表題の通りだが、jsでの実行の手順については以下になります。

### 2-1. iframeの要素を取得

後々管理しやすいようにhtml側に`js-iframe`のclass名を付与、js側で要素の取得

```html
<iframe class="js-iframe" src="./move/page1.html" frameborder="0"></iframe>
```

```js
// iframeのコンテンツの要素取得
function getIframeElem() {
  const elemIframe = document.querySelector('.js-iframe')
  return { elemIframe }
  /** 
   * 以下の結果になる
   *  <iframe class="js-iframe" src="./move/page1.html" frameborder="0"></iframe>
   */
}
```

ここまでで以下のような要素が取得されます。

### 2-2. iframe内にある`body`タグ・`a`タグの一覧の要素を取得

```js
/** 
 * elemIframeについては`2-1`で取得したiframeの要素
 */

// iframeの要素の中にあるbodyの高さを取得
function getIframeHeight(elemIframe) {
  const iframeBodyHeight = elemIframe.contentWindow.document.body.clientHeight
  return { iframeBodyHeight }
  /** 
   * 以下の結果になる
   *  24
   */
}

// iframeの中にあるリンクタグの要素一覧を取得
function getElemIframeLink(elemIframe) {
  const elemIframeLinks = elemIframe.contentWindow.document.querySelectorAll('.js-link')
  return { elemIframeLinks }
  /** 
   * 以下の結果になる
   *  NodeList [a.js-link]
   */
}
```

`HTMLIFrameElement.contentWindow`を使用することで、iframe内のwindowオブジェクトにアクセスすることができます。

https://developer.mozilla.org/ja/docs/Web/API/HTMLIFrameElement/contentWindow

### 2-3. iframeの高さを変更

`2-2`で取得した`iframeBodyHeight`をiframeの高さに設定

```js
// 今回の場合はiframe内のページ内のborder分もプラスしたいため、2pxの追加も行っている。
elemIframe.height = iframeBodyHeight + 2 + 'px'
```

### 2-4. `2-2`で取得したaタグにeventを追加

イベントを付与

```js
elemIframeLinks.forEach(function(link) {
  link.addEventListener('click', clickEvent(elemIframe))
})
```

### 2-5. iframeのload時に処理を行う

```js
// iframe内の要素をクリックした時の処理
function clickEvent(elemIframe) {
  elemIframe.addEventListener('load', loadEvent)
}
```


# 詰まったところ

## 1. `addEventListener`を意識しないと読み込み回数がおかしくなる

`addEventListener`の第２引数を無名関数にすると複数回実行した時に処理が無駄に走ってしまう。

```js
// NG
elemIframe.addEventListener('load', function() {
  loadEvent()
})
// OK
elemIframe.addEventListener('load', loadEvent)
```

### NGの動き

![ng.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214546/b4bb8b55-77d4-c804-7bbb-bea14b82543a.gif)

### OKの動き

![ok.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214546/7f775fb7-2512-d2cf-afad-f250e9e0b0f4.gif)