Claude.aiのArtifactsのプレビューは一体どういう仕組なの？

Claude.aiの「Artifacts」、皆さん使ってますか？

HTMLの生成をお願いすると、上手にHTMLを生成してくれます。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/773a2e8e-270f-add1-ed71-1798ecfa327a.png)

さらに、HTMLのプレビューまで切り替えられます。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/2aad0eda-7b03-3bdd-3a72-6d228c0ec02d.png)

これは一体どういうテクノロジーでできているのでしょうか？？

## 開発者ツールで確認

ブラウザの開発者ツールを使って、プレビュー部分がどうやってできているか見てみます。
すると、iFrameが見つかりました。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/d3f1f3e5-63f4-8834-ebdc-6af1df6ed926.png)

iFrameの「srcdoc」属性にHTMLがセットされていることがわかりました。

## iFrameの仕様を確認

MDNドキュメントで、iFrameの仕様を確認します。

https://developer.mozilla.org/ja/docs/Web/HTML/Element/iframe

srcdoc属性の説明は以下のとおりです。

> 埋め込むインライン HTML で、 src 属性を上書きします。ブラウザーが srcdoc 属性に対応していない場合は、 src 属性の URL で代替されます。

srcdocにHTMLを埋め込むことができそうです。

## やってみた

最低限のHTMLを書いてみました。Bodyの中はiFrameのみです。srcdocに太字にした文字をセットしてみました。

```html
<!DOCTYPE html>
<html>
<head>
    <meta charset='utf-8'>
    <meta http-equiv='X-UA-Compatible' content='IE=edge'>
    <title>Page Title</title>
    <meta name='viewport' content='width=device-width, initial-scale=1'>
    <link rel='stylesheet' type='text/css' media='screen' href='main.css'>
</head>
<body>
    <iframe srcdoc="<html><b>Hello, World!</b></html>"></iframe>
</body>
</html>
```

期待したとおりに出ましたね。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/5112c105-dd38-3648-d4de-d14acf6aad32.png)

Claudeが作ってくれたHTMLを貼り付けてみましょう

<details><summary>HTML</summary>

```html
<!DOCTYPE html>
<html>
<head>
    <meta charset='utf-8'>
    <meta http-equiv='X-UA-Compatible' content='IE=edge'>
    <title>Page Title</title>
    <meta name='viewport' content='width=device-width, initial-scale=1'>
    <link rel='stylesheet' type='text/css' media='screen' href='main.css'>
</head>
<body>
    <iframe width="600" height="400" srcdoc="<!DOCTYPE html>
        <html lang=&quot;ja&quot;>
        <head>
            <meta charset=&quot;UTF-8&quot;>
            <meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;>
            <title>ログイン</title>
            <style>
                body {
                    font-family: Arial, sans-serif;
                    display: flex;
                    justify-content: center;
                    align-items: center;
                    height: 100vh;
                    margin: 0;
                    background-color: #f0f0f0;
                }
                .login-container {
                    background-color: white;
                    padding: 2em;
                    border-radius: 5px;
                    box-shadow: 0 0 10px rgba(0,0,0,0.1);
                }
                h2 {
                    text-align: center;
                    color: #333;
                }
                form {
                    display: flex;
                    flex-direction: column;
                }
                label {
                    margin-top: 1em;
                }
                input {
                    padding: 0.5em;
                    margin-top: 0.5em;
                }
                button {
                    margin-top: 1em;
                    padding: 0.5em;
                    background-color: #007bff;
                    color: white;
                    border: none;
                    border-radius: 3px;
                    cursor: pointer;
                }
                button:hover {
                    background-color: #0056b3;
                }
            </style>
        </head>
        <body>
            <div class=&quot;login-container&quot;>
                <h2>ログイン</h2>
                <form action=&quot;#&quot; method=&quot;post&quot;>
                    <label for=&quot;username&quot;>ユーザー名：</label>
                    <input type=&quot;text&quot; id=&quot;username&quot; name=&quot;username&quot; required>
                    
                    <label for=&quot;password&quot;>パスワード：</label>
                    <input type=&quot;password&quot; id=&quot;password&quot; name=&quot;password&quot; required>
                    
                    <button type=&quot;submit&quot;>ログイン</button>
                </form>
            </div>
        </body>
        </html>"></iframe>
</body>
</html>
```

</details>

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/06d25b5b-8dd2-ddd7-fd88-04a1320c27a3.png)


お～～～～

---

一見難しそうなブロック崩しも、同じ理屈でできています。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/7a0f3ba7-f0bb-b190-8e27-a27d67a318b2.png)

自作iFrameに移植👇️👇️👇️

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/f8312f0a-86bb-759f-b0a6-31dbd78ebbbe.png)


## おまけ

Reactでこんなのを作ると、リアルタイムで反映されて楽しいです。

```jsx
import { useState } from "react"

function App() {

  const [text, setText] = useState("")

  return (
    <>
      <textarea value={text} onChange={e => setText(e.target.value)} style={{ width: "800px", height: "600px" }}></textarea>
      <iframe srcDoc={text} style={{ width: "800px", height: "600px" }} ></iframe>
    </>
  )
}

export default App
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/33e962d7-5483-d926-1d64-24bf79d9d8bc.gif)