iframeと親ページ間でpostMessageを利用してデータ送受信を行う

# はじめに
[iframe表示部分のクリックイベントを取得する](https://qiita.com/manzoku_bukuro/items/66d07b88eb0b6eb28601)という記事で、iframe内のイベントをiframeのcontentWindowに対しaddEventlistenerを登録して親ページで取得する方法を紹介しました。

この記事ではpostMessageを利用してiframeと親ページ間でやり取りする方法を紹介します。

# この記事の対象者
- ページに埋め込んだiframe要素から情報を取得したい
- ページに埋め込んだiframe要素と親ページ間で相互間のデータ送受信を行いたい
- ページにiframe埋め込みをする前提のページを作成して、イベント発生時に親ページに情報を渡したい

# iframeと親ページ間のデータ送受信
## postMessageを利用した送受信の方法
### 子ページ
#### データを親ページに送信する場合
```js
window.parent.postMessage(message, targetOrigin);

// 例
window.parent.postMessage("sample.jpでしか受信出来ないメッセージです", "https://sample.jp");

window.parent.postMessage({
    type: "sample",
    data: {
        message: "誰でも受信できるメッセージです",
    },
}, "*");

const example = {
    type: "example",
    data: {
        message: "JSONで渡すメッセージです",
    }
}

window.parent.postMessage(JSON.stringfy(example), "*");
```

##### 引数の説明
- message

他のウィンドウに送られるデータです。
決められた型は無いですが、オブジェクトで渡すことでtypeやdataなど親ページで扱いやすい内容で渡すことが出来ます。

- targetOrigin

データを送信した際の受け取り側のオリジンを限定することが出来ます。
例えば`https://sample.jp`とした場合は、オリジンが`https://sample.jp`のウィンドウでしかデータは受信出来ません。
postMessageの送信先が決まっている場合はオリジン指定をしましょう。

#### データを親ページから受信する場合
```js
window.addEventListener("message", (response) => {
    // 取得した内容を利用した処理
}) 
```
postMessageで送信されたデータを受け取るには、addEventListenerで登録したmessageイベントを検知することで取得することが出来ます。

### 親ページ
#### データをiframeで埋め込んだ子ページに送信する場合

```js
const iframeElement = document.querySelector("iframe");
iframeElement.contentWindow.postMessage(message, targetOrigin);
```
iframeのWindowオブジェクトにアクセスして、先ほどと同じようにpostMessageを実行しています。

#### データを子ページから受信する場合
```js
window.addEventListener("message", (response) => {
    // 取得した内容を利用した処理
}) 
```

こちらは子ページで受信する場合と違いありません。

---
以上がiframeと親ページ間でpostMessageを利用してデータ送受信を行う方法になります。

# 使用例
以下postMessageの使用例です。iframeと親ページで連携して相互間のデータ受け渡しをする例を紹介します。

[確認環境](https://manzoku-bukuro.github.io/qiita/iframe-post-message/index.html)
[ソースコード](https://github.com/manzoku-bukuro/qiita/tree/main/iframe-post-message)

![Videotogif (5).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849606/e14832a1-1368-5738-2fff-eccbfb580f42.gif)


## iframeでボタンクリックされた回数を親ページに渡す
### iframe
```js
let leftCount = 0;
const leftClick = () => {
    const message = {
        type: "click",
        data: {
            name: "left",
            count: leftCount,
        }
    }
    window.parent.postMessage(message, "*");
    leftCount++;
}

let rightCount = 0;
const rightClick = () => {
    const message = {
        type: "click",
        data: {
            name: "right",
            count: rightCount,
        }
    }
    window.parent.postMessage(message, "*");
    rightCount++;
}

// オリジンが異なるので親ページでは受信出来ない
const originRestricted = () => {
    const message = {
        type: "click",
        data: {
            name: "restricted",
        }
    }
    window.parent.postMessage(message, "https://sample.jp");
}
```
### 親ページ
```js
window.addEventListener("message", (response) => {
    if (response.data.type === "click") {
        const data = response.data.data;
        if (data.name === "left") {
            const leftNumberElement = document.querySelector("#left-number");
            leftNumberElement.innerHTML = data.count;
        } else if (data.name === "right") {
            const rightNumberElement = document.querySelector("#right-number");
            rightNumberElement.innerHTML = data.count;
        }
    }
})
```

## 親ページで選択された色をiframeで表示する
### 親ページ
```js
const postColor = (color) => {
    const iframeElement = document.querySelector("iframe");
    const message = {
        type: "color",
        data: {
            color: color,
        }
    }
    iframeElement.contentWindow.postMessage(message, "*");
}
```

### iframe
```js
window.addEventListener("message", (response) => {
    if (response.data.type === "color") {
        const messageAreaElement = document.querySelector(".message-area");
        const createDivElement = document.createElement("div");
        const color = response.data.data.color;
        if (color === "red") {
            createDivElement.className = "red";
        } else if (color === "blue") {
            createDivElement.className = "blue";
        } else if (color === "yellow") {
            createDivElement.className = "yellow";
        }
        messageAreaElement.appendChild(createDivElement);
    }
})
```