リッチテキストをコピーできるボタン作ってって言われたけど、そんなん実装してるの見たこと無い。
できないでしょ。。
ぼくも出来ないだろうな〜と思いながら調べたらできるみたいです。
あまりサンプルや記事も少く実装するのに時間がかかったので、今回はより実務に近い形のサンプルを用意しました。
クリップボードにリッチテキストをコピーする
クリップボードにリッチテキストをコピーすることは可能です。
シンプルなサンプル
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のサンプルは実践に近いものを作成しましたので、必要があれば読み解いてみてください。
最後までありがとうございました!