背景
ReactでCSS in JSを利用している際、SVGアイコンのようなReactコンポーネントをCSSのcontent
プロパティやbackground-image
プロパティに埋め込みたい場合がある。
ReactでCSS in JSを利用している際、SVGアイコンのようなReactコンポーネントをCSSのcontent
プロパティやbackground-image
プロパティに埋め込みたい場合がある。
content: url()
を想定しているが、background-image: url()
でも同様の方法で実現できる。CSSの疑似要素(::before
や::after
)のcontent
プロパティや、要素のbackground-image
プロパティにSVGを表示する方法のひとつとして、data:image/svg+xml
スキームを利用する方法がある。
以下のような形式の文字列が必要になる。
data:image/svg+xml, + SVGのXMLコードをURLエンコードしたもの
SVGのXMLコードは、そのままではURLの一部として安全に使えない文字(例: <
、>
、#
など)を含む可能性があるため、さらにencodeURIComponent
関数を使ってURLエンコードする必要がある。
Reactコンポーネントとして定義されたSVGアイコンを、上記のデータURI形式の文字列に変換する関数を作成する。この変換には、ReactコンポーネントをHTML文字列にレンダリングするrenderToStaticMarkup
関数(react-dom/server
パッケージに含まれる)を利用する。
// SvgComponentToDataUrlScheme.ts
import type { ReactElement } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
export const SvgComponentToDataUrlScheme = (Component: ReactElement) => {
// 1. Reactコンポーネントを純粋なSVGマークアップ文字列に変換
const SVG = renderToStaticMarkup(Component);
// 2. SVGマークアップをURLエンコード
const encodedSvg = encodeURIComponent(svgMarkup);
// 3. データURIスキームの接頭辞と結合して返す
return `data:image/svg+xml,${encodedSvg}`;
};
上記のSvgComponentToDataUrlScheme
関数を使って、実際にCSS in JSでSVGアイコンを表示する例は以下の通り。
import { SvgComponentToDataUrlScheme } from '@/lib/SvgComponentToDataUrlScheme';
import { ExternalLinkIcon } from '@radix-ui/react-icons';
import { css } from '@emotion/react';
const IconExternalLink = SvgComponentToDataUrlScheme(<ExternalLinkIcon />);
export const IconExternalLinkStyle = css`
&::before {
display: inline-block;
margin-left: 0.15em;
vertical-align: middle;
content: url(${IconExternalLink});
}
`;
このように、SvgComponentToDataUrlScheme
関数にSVGコンポーネントを渡すことで、CSSのurl()
内で直接使用できるデータURI文字列を取得できる。
renderToStaticMarkup
とrenderToString
の違いReactコンポーネントをHTMLの文字列に変換する関数として、renderToStaticMarkupとrenderToStringがある。今回のSVG変換ではrenderToStaticMarkupを使用したが、なぜこちらが適しているのか、両者の違いを明確にしておく。
renderToString
renderToString
は、主にサーバーサイドレンダリング(SSR)で利用される。Reactアプリケーションをサーバー側でHTML文字列に変換したものをブラウザに送信することで、初期表示を高速化したりSEOを向上させたりする目的で使われる。
import { renderToString } from 'react-dom/server';
import App from './App';
export function render(url, context) {
return renderToString(<App />);
}
しかし、renderToString
は、data-reactroot
やdata-reactid
などのReactが内部で利用する属性を出力する。これは、ハイドレーションが必要な場合に利用される。
ハイドレーションが必要ない静的ページの場合、renderToStaticMarkup
を使用すれば、Reactの特定のデータ属性やイベントハンドラは含まれず、純粋なHTMLが出力される。
renderToStaticMarkup
一方、renderToStaticMarkup
は、Reactコンポーネントを純粋なHTML文字列に変換する。renderToString
とは異なり、Reactが内部的に使用する特別な属性(data-reactid
など)を出力しない。
この関数は、ハイドレーションが不要な静的なHTMLコンテンツを生成する場合に適している。今回のSVGアイコンの埋め込みでは、生成されたSVG文字列に対して後からReactがインタラクティブ性を付与する必要はない(ハイドレーションは不要)ため、余計な属性を含まず、ファイルサイズも小さくなるrenderToStaticMarkup
がより適切な選択となる。
@types/react-dom/server.d.ts
Reactの型定義ファイル(@types/react-dom/server.d.ts
)にも、これらの関数の違いが説明されている。
/**
* Render a React element to its initial HTML. This should only be used on the server.
* React will return an HTML string. You can use this method to generate HTML on the server
* and send the markup down on the initial request for faster page loads and to allow search
* engines to crawl your pages for SEO purposes.
*
* If you call `ReactDOMClient.hydrateRoot()` on a node that already has this server-rendered markup,
* React will preserve it and only attach event handlers, allowing you
* to have a very performant first-load experience.
*/
/**
* React 要素を初期 HTML にレンダーします。これはサーバーでのみ使用する必要があります。
* React は HTML 文字列を返します。このメソッドを使用して、HTML をサーバー上で生成し、
* 初期リクエストでマークアップをダウンロードしてページの読み込みを高速化し、
* 検索エンジンが SEO の目的でページをクロールできるようにすることができます。
*
* `ReactDOMClient.hydrateRoot()` をすでにこのサーバー側レンダリングされたマークアップを持つノードで呼び出すと、
* React はそれを保持し、イベントハンドラーのみをアタッチして、
* パフォーマンスの高い初回読み込み体験を提供できます。
*/
export function renderToString(element: ReactElement, options?: ServerOptions): string;
/**
* Similar to `renderToString`, except this doesn't create extra DOM attributes
* such as `data-reactid`, that React uses internally. This is useful if you want
* to use React as a simple static page generator, as stripping away the extra
* attributes can save lots of bytes.
*/
/**
* `renderToString` と似ていますが、内部で React が使用する `data-reactid` などの
* 追加の DOM 属性を作成しません。これは、React を単純な静的ページジェネレータとして
* 使用する場合に便利で、余分な属性を削除することで多くのバイトを節約できます。
*/
export function renderToStaticMarkup(element: ReactElement, options?: ServerOptions): string;