
リッチテキストをコピーできるボタン作ってって言われたけど、そんなん実装してるの見たこと無い。
できないでしょ。。

ぼくも出来ないだろうな〜と思いながら調べたらできるみたいです。
あまりサンプルや記事も少く実装するのに時間がかかったので、今回はより実務に近い形のサンプルを用意しました。
クリップボードにリッチテキストをコピーする
クリップボードにリッチテキストをコピーすることは可能です。
シンプルなサンプル
navigator.clipboardを使用して実装しています。
Copy Rich Textのボタンをクリックすると、クリップボードにリッチテキストとしてコピーできます。
貼り付けるとリッチテキストになります(リンク付きのテキスト)。

See the Pen Copy Rich Text by shimpei (@shimpei) on CodePen.
navigator.clipboardからdocument.execCommand メソッド(非推奨)に移行しました。
以前はdocument.execCommand メソッドを使用して実装していたようですが、このメソッドは非推奨になり、navigator.clipboardでの実装が推奨されています。
完成形のイメージ
下記は画像ですが、Codepenの方で左のアイコンをクリックするとリッチテキストでコピーされます。

WordPressに埋め込んだCodePenだとうまく動作しないことがあるので、CodePenで動作確認してみてください。
See the Pen CopyableRichText|ant-design by shimpei (@shimpei) on CodePen.
React、Next.js、Ant Designを使用したコードの例
簡単なサンプルや紹介記事はありましたが、いざ実務で使うとなるとうまく実装できずに困ったので、実務に近いかたちのサンプルを用意しました。
ちょっと実力不足で一つ一つ解説できないですが、おそらくこのコードを読み解こうとするような熱心なあなたであればきっと理解し、あなたの環境で実装できるかと思います。
僕はおよそこんな感じで実装しました。
blobのタイプをtype: 'text/html'にしています。これでクリップボードに指定したタイプでコピーできます。
blobはMIME タイプを指定できるので、多分画像とかもコピーできると思います。
テキスト系:
text/plain: プレーンテキスト
text/html: HTML ドキュメント
text/css: CSS スタイルシート
text/javascript: JavaScript コード
application/json: JSON データ
画像系:
image/jpeg: JPEG 画像
image/png: PNG 画像
image/gif: GIF 画像
image/svg+xml: SVG ベクター画像
音声系:
audio/mpeg: MP3 オーディオ
audio/wav: WAV オーディオ
audio/ogg: Ogg Vorbis オーディオ
動画系:
video/mp4: MP4 動画
video/webm: WebM 動画
video/ogg: Ogg Theora 動画
アプリケーション系:
application/pdf: PDF ドキュメント
application/zip: ZIP アーカイブ
application/octet-stream: 未知のバイナリデータ
const { createRoot } = ReactDOM;
const { LinkOutlined, SnippetsOutlined, CheckOutlined } = icons;
const { Typography, Avatar, List, Radio, Space } = antd;
const { Paragraph, Text } = Typography;
const data = [
  {
    title: 'ページトップへ戻るボタンの作り方【JavaScript】',
    postUrl: "https://sinpe-pgm.com/pagetop-button-js/",
    description: "JavaScriptで作るにはどうしたらいいんだろう?"
  },
  {
    title: '【コピペOK】複数のモーダルを同じクラス名で設置する方法',
    postUrl: "https://sinpe-pgm.com/multiple-modals/",
    description: "複数のモーダルを実装するの難しい。これまで2~3個だったから#modal01、#modal02とかしていたけど、今回10個以上あるから全部にid割り振ってられないよ、、"
  },
  //  略...
];
const firstItem = data[0];
const url = firstItem.postUrl;
const name = firstItem.title;
const encodedUrl = encodeURI(url);
const handleCopyToClipboard = async (richText) => {
  const blob = new Blob([richText], { type: 'text/html' });
  const blobPlain = new Blob([richText], { type: 'text/plain' });
  const item = [new ClipboardItem({ 'text/html': blob, 'text/plain': blobPlain })];
  try {
    await navigator.clipboard.write(item);
    message.success('コピーされました');
  } catch (error) {
    console.error('Error copying rich text to clipboard:', error);
  }
};
const App = () => {
  const handleCopyToClipboardRichText = (postUrl, title) => {
    const richText = `<a href="${postUrl}" target="_blank">${title}</a>`;
    handleCopyToClipboard(richText);
  };
  const renderItem = (item, index) => (
    <List.Item>
      <List.Item.Meta
        avatar={<Avatar src={`https://api.dicebear.com/7.x/miniavs/svg?seed=${index}`} />}
        title={<a target="_blank" href={item.postUrl}>{item.title}</a>}
        description={item.description}
      />
      <div className="icon-wrap">
        <Paragraph
          className="icon"
          copyable={{
            text: item.postUrl,
            icon: [<LinkOutlined key="copy-icon" />, <CheckOutlined key="copied-icon" />],
            tooltips: ['テキスト', 'clicked!!'],
          }}
        ></Paragraph>
        <Paragraph
          className="icon"
          copyable={{
            icon: [<SnippetsOutlined key="copy-icon" />, <CheckOutlined key="copied-icon" />],
            tooltips: ['リッチテキスト', 'clicked!!'],
            onCopy: () => {
              handleCopyToClipboardRichText(item.postUrl, item.title);
            },
          }}
        ></Paragraph>
      </div>
    </List.Item>
  );
  return (
    <>
      <Typography.Title level={2}>リッチテキストをクリップボードにコピーするサンプル</Typography.Title>
      <Space
        direction="vertical"
        style={{
          marginBottom: '20px',
        }}
        size="middle"
      >
      </Space>
      <List
        pagination={{
          defaultPageSize: 5
        }}
        dataSource={data}
        renderItem={renderItem}
      />
    </>
  );
};
const ComponentDemo = App;
createRoot(mountNode).render(<ComponentDemo />);URLが日本語の場合にURLエンコーディングする
URLに日本語が入っていたりしてエンコーディングしたい場合は下記のように実装します。
const encodedUrl = encodeURI(url);
うまく実装できたたつもりでもエラーはあります。
テスト段階では日本語にURLなんて使わないですが、実際に利用するとそういった変なURLがあったりして、そのせいでエラーになったりします。
URLのエンコードは簡単なので実装しておきましょう♪
最後に
解説は以上です。
説明は少なかったですがCodePenのサンプルは実践に近いものを作成しましたので、必要があれば読み解いてみてください。
最後までありがとうございました!
 しんぺー
		しんぺー	
