見出し画像

svgをReactコンポーネント化する

こんにちは。サイボウズ株式会社 開発本部 People Experienceチームの貴島(@jnkykn)です。先週、Cybozu TechGaroon開発チームのまとめページを追加したのですが、併せて、酒井(@sakay_y)さんから「今、ロゴのsvgをそのまま表示しているXのアイコンを、コンポーネントにしませんか?表示サイズも良い感じに調整していただけると、更に良いですね。」とお題をいただいたので、Xのアイコンをコンポーネントにしました。

svgをReactのコンポーネントにする方法

Cybozu Techで表示しているGitHubなどのアイコンは、Semantic UIのアイコンを使用しています。Twitterのアイコンはあるものの、X(旧Twitter)のアイコンがなかったので、酒井(@sakay_y)さんがXの公式ロゴのsvgファイルを表示することで対応してくださっていました。先週svgのコンポーネント化について調べて、X公式ロゴのsvgをコンポーネントの返却値に埋め込んだのが現在のページのXアイコンです。今日、改めて調べてみると、他にもコンポーネント化の方法があることがわかったので試したいと思います。

SVGをReactで表示する方法を確かめる

現在のCybozu TechのXアイコンは、手動でSVGファイルをReactコンポーネント化を参考に、XアイコンのSVGをそのままコンポーネントの返却値として埋め込んでいます。

const XIcon = (
    props: JSX.IntrinsicAttributes & React.SVGProps<SVGSVGElement>
  ) => (
    <svg width="1200" height="1227" viewBox="0 0 1200 1227" fill="none" xmlns="http://www.w3.org/2000/svg">
    <path d="M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z" fill="white"/>
    </svg>
  );
  export default XIcon;

width="1200" height="1227"なので、そのまま表示するとこんな大きさです。

width="1200" height="1227"の大きなXロゴがブラウザいっぱいに表示されている
width="1200" height="1227"なので、こんな大きさ

実際のページは、酒井(@sakay_y)さんがいい感じのスタイルにしてくださっているので、違和感ない大きさです。

Cybozu Techに表示されているXアイコン
他のSemantic UIのアイコンと揃って表示されているXアイコンのコンポーネント
1200x1227から30x13.5に
30x13.5で表示されているXアイコン

コンポーネント化にあたって、SVGのサイズも調整できると良いと思いつつ、そのまま1200x1227でコンポーネント化してしまったのですが、mdnのSVG属性リファレンスを見ると、widthとheightは、「ユーザー座標系における要素の幅と高さを定義する」ようです。ということは、図形を定義する際の大きさじゃなくて、表示するときの大きさということですね。というわけで、XIconコンポーネントのSVGのwidthとheightを1/10のwidth="120" height="122.7"に変更してみると、先程のテストページのXロゴが小さくなりました。ここを変更するだけで良かったんですね。なるほどー。

120x122.7にリサイズされたXロゴが表示されたテストページ
1200x1227から120x122.7にリサイズされた

「SVG何もわからない」から、少し前進できました。対応したときは、そこまでの余裕がないと思っていたのですが、公式ドキュメントを読むのが一番の早道だった説がありますね。ちなみに、コンポーネントを配置する際にも、<XIcon width={120} height={120} />のようにサイズを指定することができます。

他の方法を試します

今日、改めてSVGのReactコンポーネント化について調べていて、React環境で手軽にSVGを表示する方法を見つけました。この記事によると、SVGを直接コンポーネントとして取り込むことができるようです。なんですって!?それは試さないわけにはいきませんね。やってみます。が、そのままインポートできないようです。

『Module ‘”*.svg”‘ has no exported member ‘ReactComponent’』というエラーが出た
型定義ファイルの修正が必要らしい

index.d.ts を追加

declare module '*.svg' {
  import React = require('react');
  export const ReactComponent: React.FC<React.SVGProps<SVGSVGElement>>;
  const src: string;
  export default src;
}

tsconfig.json に、index.d.tsを追加

"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "index.d.ts"],

しかし、うまくいきません。

Runtime Error"Error: Unsupported Server Component type: undefined"でページ表示できない
Runtime Errorで表示できない
Error: Unsupported Server Component type: undefined
    at stringify (<anonymous>)
    at stringify (<anonymous>)
digest: "1910126267"
 ⚠ ./src/app/page.tsx
Attempted import error: 'ReactComponent' is not exported from './logo.svg' (imported as 'XIconSVG').

Import trace for requested module:
./src/app/page.tsx
 GET / 500 in 7403ms

Create React AppのAdding SVGsを見ると、Reactの対応バージョンが書かれていました。

Note: this feature is available with react-scripts@2.0.0 and higher, and react@16.3.0 and higher.

Adding SVGs

環境の問題かもしれないので、ReactReactのバージョンを確認しました。

$ npm list --depth=0
next-svg-component@0.1.0 /home/tetrapod/SVG-React/next-svg-component
├── @eslint-community/eslint-utils@4.4.0 extraneous -> ./node_modules/.pnpm/@eslint-community+eslint-utils@4.4.0_eslint@8.57.0/node_modules/@eslint-community/eslint-utils
<省略>
├── react-dom@18.3.1 -> ./node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom
├── react@18.3.1 -> ./node_modules/.pnpm/react@18.3.1/node_modules/react
├── tailwindcss@3.4.3 -> ./node_modules/.pnpm/tailwindcss@3.4.3/node_modules/tailwindcss
└── typescript@5.4.5 -> ./node_modules/.pnpm/typescript@5.4.5/node_modules/typescript

Reactのバージョンは問題なさそうなのですが、react-scriptsが無いのが問題では?インストールしてみます。

$npm i react-scripts

しかし、TypeScriptのバージョンが解決できずインストールできませんでした。

npmのインストールエラーtypescript@5.4.5が見つかったが解決できなかったのが問題らしい
package.jsonのバージョンコンフリクトが発生した模様
  "devDependencies": {
    "typescript": "^5",        ←ここ

そこで、--legacy-peer-depsオプションを付けて強制的にインストールしようとしたのですが、うまくいきません。

$ npm i react-scripts --legacy-peer-deps
npm error Cannot read properties of null (reading 'matches')

npm error A complete log of this run can be found in: /home/tetrapod/.npm/_logs/2024-06-02T13_20_41_675Z-debug-0.log

この環境に追加するのを諦めて、新しくReactアプリの環境を作り直すことにしました。

$ npx create-react-app my-app --template typescript

Creating a new React app in /home/tetrapod/SVG-React/my-app.

Installing packages. This might take a couple of minutes.
Installing react, react-dom, and react-scripts with cra-template-typescript...

あっ、react-scriptsがインストールされている!!!これは、いけそう!

App.tsxをXのロゴのSVGをコンポーネントとしてインポートして、表示するように修正します。

import React from 'react';
import logo from './logo.svg';
import './App.css';
import { ReactComponent as XIconSVG } from "./Xlogo.svg";

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <div><p>logo.svgをコンポーネントとしてインポート</p>
      <XIconSVG width={200} height={200} fill={"red"} />
      </div>
      </header>
    </div>
  );
}

export default App;

さて、実行してみます。

Reactのロゴの下に、SVGをコンポーネントとして配置したXロゴが表示されている
Xのlogo.svgを直接コンポーネントとしてインポートして表示できた

まとめ

今日は試しませんでしたが、svgの表示方法としては、この他に、svgファイルをimgのsrcに設定する方法もあります(これは、わりと直感的)。先週、ゴリゴリっと勢いで対応したXロゴのsvgのコンポーネント化を、改めて調べて試してみました。今の実装のままでも良いのですが、改善の余地ありだなと思いました。業務時間中に消化不良だったところが少しすっきりできたので、今日はここまでにします。

この記事が気に入ったらサポートをしてみませんか?