CSS in JSでcontentプロパティにSVGコンポーネントを挿入する方法

5 min read

環境

  • React
  • CSS in JS
    Emotionを利用しているが、ほかのCSS in JSでも同様の方法で実現できるはず。
    content: url()を想定しているが、background-image: url()でも同様の方法で実現できる。
  • SVGアイコンコンポーネント
    @radix-ui/react-icons

方法

疑似要素のcontentプロパティにdata:image/svg+xmlスキームを指定することで、SVGを埋め込むことができる。

文字列として必要になるのは、data:image/svg+xml,の接頭辞に合わせて、SVGをエンコードしたものがあればよい。SVGはencodeURIComponentでエンコードすれば要件を満たす。

// SvgComponentToDataUrlScheme.ts
import type { ReactElement } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';

export const SvgComponentToDataUrlScheme = (Component: ReactElement) => {
  const SVG = renderToStaticMarkup(Component);
  return `data:image/svg+xml,${encodeURIComponent(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にコンポーネントを渡すことで、data:image/svg+xml,スキームのURLを取得できる。

renderToStaticMarkuprenderToStringの違い

コンポーネントをHTMLの文字列にする関数として、renderToStaticMarkuprenderToStringがある。


renderToStringといえば、カスタムサーバーを実装するときに利用されるため、見かける機会が多い。

import { renderToString } from 'react-dom/server';
import App from './App';

export function render(url, context) {
  return renderToString(<App />);
}

しかし、renderToStringは、data-reactidなどのReactが内部で利用する属性を出力する。これは、ハイドレーションが必要な場合に利用される。
ハイドレーションが必要ない静的ページの場合、renderToStaticMarkupを使用すれば、Reactの特定のデータ属性やイベントハンドラは含まれず、純粋なHTMLが出力される。

動的な機能とクライアントサイドのリアクティビティが重要であればrenderToStringを、静的なコンテンツに焦点を当てる場合はrenderToStaticMarkupが適している。

@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;