🕌
手動でSVGファイルをReactコンポーネント化
TL;DR
- SVG ファイルを手動で React コンポーネントに変換します。
-
stroke-width
、fill-opacity
など、必要に応じて適切な形に変換しないとエラーが発生します。-
stroke-width
→strokeWidth
-
fill-opacity
→fillOpacity
-
はじめに
SVG ファイルを React コンポーネントに手動で変換する方法を記載します。
モチベーション
SVG ファイルを React コンポーネントとして利用したい。
SVGファイルをReactコンポーネントに変換する流れ
作業の流れ
- Next.js のプロジェクトを作成
- SVG ファイルを Web からダウンロード
- SVG ファイルの VSCode で開く
- SVG ファイルを React コンポーネントに変換
- インポートし画面に表示
メリット・デメリット
- メリットは手作業だから作業が簡単
- デメリットは更新がある際に都度処理が必要
では実際に SVG を React コンポーネントに変換します。
Next.jsのプロジェクトを作成
Next.js のプロジェクトを作成します。
$ pnpm create next-app next-svg-component --typescript --eslint --src-dir --import-alias "@/*" --use-pnpm --tailwind --app
実行ログ
Library/pnpm/store/v3/tmp/dlx-22604 | Progress: resolved 1, reused 0, dowLibrary/pnpm/store/v3/tmp/dlx-22604 | +1 +
Library/pnpm/store/v3/tmp/dlx-22604 | Progress: resolved 1, reused 0, dowPackages are hard linked from the content-addressable store to the virtual store.
Content-addressable store is at: /Users/hayato94087/Library/pnpm/store/v3
Virtual store is at: Library/pnpm/store/v3/tmp/dlx-22604/node_modules/.pnpm
Library/pnpm/store/v3/tmp/dlx-22604 | Progress: resolved 1, reused 0, dowLibrary/pnpm/store/v3/tmp/dlx-22604 | Progress: resolved 1, reused 0, downloaded 1, added 1, done
Creating a new Next.js app in /Users/hayato94087/Private/next-svg-component.
Using pnpm.
Initializing project with template: app-tw
Installing dependencies:
- react
- react-dom
- next
- typescript
- @types/react
- @types/node
- @types/react-dom
- tailwindcss
- postcss
- autoprefixer
- eslint
- eslint-config-next
╭─────────────────────────────────────────────────────────────────╮
│ │
│ Update available! 7.27.0 → 8.5.1. │
│ Changelog: https://github.com/pnpm/pnpm/releases/tag/v8.5.1 │
│ Run "pnpm add -g pnpm" to update. │
│ │
│ Follow @pnpmjs for updates: https://twitter.com/pnpmjs │
│ │
╰─────────────────────────────────────────────────────────────────╯
Downloading registry.npmjs.org/next/13.4.2: 12.3 MB/12.3 MB, done
Downloading registry.npmjs.org/@next/swc-darwin-x64/13.4.2: 34.3 MB/34.3 MB, donenloading registry.npmjs.org/@next/swc-darwin-x64/13.4.2: 34.3 MB/34.3 MB
Packages: +348
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Packages are hard linked from the content-addressable store to the virtual store.
Content-addressable store is at: /Users/hayato94087/Library/pnpm/store/v3
Virtual store is at: node_modules/.pnpm
Progress: resolved 356, reused 331, downloaded 17, added 348, done
dependencies:
+ @types/node 20.1.4
+ @types/react 18.2.6
+ @types/react-dom 18.2.4
+ autoprefixer 10.4.14
+ eslint 8.40.0
+ eslint-config-next 13.4.2
+ next 13.4.2
+ postcss 8.4.23
+ react 18.2.0
+ react-dom 18.2.0
+ tailwindcss 3.3.2
+ typescript 5.0.4
The integrity of 5632 files was checked. This might have caused installation to take longer.
Done in 12.1s
Initialized a git repository.
Success! Created next-svg-component at /Users/hayato94087/Private/next-svg-component
cd next-svg-component
SVGファイルをWebからダウンロード
Web から SVG ファイルをダウンロードします。
SVGファイルのVSCodeで開く
ダウンロードしたファイルを VSCode で開きます。
Scalable_Vector_Graphics_Fill-opacity.svg
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created by Erik Baas for WikiBooks -->
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
<title>Wikibooks SVG-demo: fill-opacity</title>
<rect id="background" width="100%" height="100%" fill="white" />
<circle cx="40" cy="40" r="35" stroke="black" stroke-width="4" fill="lightgrey" />
<circle cx="60" cy="60" r="35" stroke="black" stroke-width="4" fill="lightgrey" fill-opacity="0.5" />
</svg>
SVGファイルをReactコンポーネントに変換
TSX ファイルを作成します。
$ mkdir src/components
$ touch src/components/LogoSvg.tsx
SVG ファイルの中身をコピペします。props が渡せるように {...props}
を追加しておきます。
src/components/LogoSvg.tsx
const LogoSvg = (
props: JSX.IntrinsicAttributes & React.SVGProps<SVGSVGElement>
) => (
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" {...props}>
<title>Wikibooks SVG-demo: fill-opacity</title>
<rect id="background" width="100%" height="100%" fill="white" />
<circle
cx="40"
cy="40"
r="35"
stroke="black"
stroke-width="4"
fill="lightgrey"
/>
<circle
cx="60"
cy="60"
r="35"
stroke="black"
stroke-width="4"
fill="lightgrey"
fill-opacity="0.5"
/>
</svg>
);
export default LogoSvg;
インポートし画面に表示
画面に表示できるように、src/app/page.tsx
を空にして、LogoSvg
を追加します。
src/app/page.tsx
import LogoSvg from '@/components/LogoSvg'
export default function Home() {
return (
<div>
<LogoSvg />
</div>
)
}
開発環境で起動します。
$ pnpm dev
実行ログ
> next-svg-component@0.1.0 dev /Users/hayato94087/Private/next-svg-component
> next dev
- warn Port 3000 is in use, trying 3001 instead.
- ready started server on 0.0.0.0:3001, url: http://localhost:3001
- event compiled client and server successfully in 2s (311 modules)
- wait compiling...
- wait compiling /page (client and server)...
- event compiled client and server successfully in 3.3s (586 modules)
- wait compiling...
- event compiled successfully in 120 ms (334 modules)
- wait compiling /favicon.ico/route (client and server)...
- event compiled client and server successfully in 439 ms (618 modules)
- wait compiling...
エラー対応
しばらくすると、以下のようにエラーがでます。
Warning: Invalid DOM property `stroke-width`. Did you mean `strokeWidth`?
at circle
at svg
at div
at InnerLayoutRouter (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/layout-router.js:224:11)
at RedirectErrorBoundary (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/redirect-boundary.js:72:9)
at RedirectBoundary (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/redirect-boundary.js:80:11)
at NotFoundBoundary (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/not-found-boundary.js:40:11)
at LoadingBoundary (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/layout-router.js:328:11)
at ErrorBoundary (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/error-boundary.js:86:11)
at InnerScrollAndFocusHandler (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/layout-router.js:139:9)
at ScrollAndFocusHandler (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/layout-router.js:211:11)
at RenderFromTemplateContext (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/render-from-template-context.js:15:44)
at Lazy
at OuterLayoutRouter (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/layout-router.js:337:11)
at Lazy
at body
at html
at RedirectErrorBoundary (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/redirect-boundary.js:72:9)
at RedirectBoundary (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/redirect-boundary.js:80:11)
at NotFoundErrorBoundary (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/not-found-boundary.js:33:9)
at NotFoundBoundary (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/not-found-boundary.js:40:11)
at ReactDevOverlay (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/react-dev-overlay/internal/ReactDevOverlay.js:66:9)
at HotReload (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/react-dev-overlay/hot-reloader-client.js:277:11)
at Router (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/app-router.js:86:11)
at ErrorBoundaryHandler (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/error-boundary.js:62:9)
at ErrorBoundary (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/error-boundary.js:86:11)
at AppRouter (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/app-router.js:361:13)
at Lazy
at Lazy
at ServerComponentWrapper (/Users/hayato94087/Private/next-svg-component/node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/server/app-render/create-server-components-renderer.js:78:31)
at ServerComponentWrapper (/Users/hayato94087/Private/next-svg-component/node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/server/app-render/create-server-components-renderer.js:78:31)
at InsertedHTML (/Users/hayato94087/Private/next-svg-component/node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/server/app-render/app-render.js:835:33)
Warning: Invalid DOM property `fill-opacity`. Did you mean `fillOpacity`?
at circle
at svg
at div
at InnerLayoutRouter (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/layout-router.js:224:11)
at RedirectErrorBoundary (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/redirect-boundary.js:72:9)
at RedirectBoundary (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/redirect-boundary.js:80:11)
at NotFoundBoundary (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/not-found-boundary.js:40:11)
at LoadingBoundary (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/layout-router.js:328:11)
at ErrorBoundary (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/error-boundary.js:86:11)
at InnerScrollAndFocusHandler (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/layout-router.js:139:9)
at ScrollAndFocusHandler (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/layout-router.js:211:11)
at RenderFromTemplateContext (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/render-from-template-context.js:15:44)
at Lazy
at OuterLayoutRouter (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/layout-router.js:337:11)
at Lazy
at body
at html
at RedirectErrorBoundary (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/redirect-boundary.js:72:9)
at RedirectBoundary (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/redirect-boundary.js:80:11)
at NotFoundErrorBoundary (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/not-found-boundary.js:33:9)
at NotFoundBoundary (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/not-found-boundary.js:40:11)
at ReactDevOverlay (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/react-dev-overlay/internal/ReactDevOverlay.js:66:9)
at HotReload (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/react-dev-overlay/hot-reloader-client.js:277:11)
at Router (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/app-router.js:86:11)
at ErrorBoundaryHandler (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/error-boundary.js:62:9)
at ErrorBoundary (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/error-boundary.js:86:11)
at AppRouter (webpack-internal:///(sc_client)/./node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/client/components/app-router.js:361:13)
at Lazy
at Lazy
at ServerComponentWrapper (/Users/hayato94087/Private/next-svg-component/node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/server/app-render/create-server-components-renderer.js:78:31)
at ServerComponentWrapper (/Users/hayato94087/Private/next-svg-component/node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/server/app-render/create-server-components-renderer.js:78:31)
at InsertedHTML (/Users/hayato94087/Private/next-svg-component/node_modules/.pnpm/next@13.4.2_biqbaboplfbrettd7655fr4n2y/node_modules/next/dist/server/app-render/app-render.js:835:33)
注目すべき点を抜粋すると以下です。
Warning: Invalid DOM property `stroke-width`. Did you mean `strokeWidth`?
Warning: Invalid DOM property `fill-opacity`. Did you mean `fillOpacity`?
fill-opacity
と stroke-width
を fillOpacity
と strokeWidth
に修正します。
src/components/LogoSvg.tsx
const LogoSvg = (
props: JSX.IntrinsicAttributes & React.SVGProps<SVGSVGElement>
) => (
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" {...props}>
<title>Wikibooks SVG-demo: fill-opacity</title>
<rect id="background" width="100%" height="100%" fill="white" />
<circle
cx="40"
cy="40"
r="35"
stroke="black"
- stroke-width="4"
+ strokeWidth="4"
fill="lightgrey"
/>
<circle
cx="60"
cy="60"
r="35"
stroke="black"
- stroke-width="4"
+ strokeWidth="4"
fill="lightgrey"
- fill-opacity="0.5"
+ fillOpacity="0.5"
/>
</svg>
);
export default LogoSvg;
これでエラーが消えます。
まとめ
SVG ファイルを手動で React コンポーネントに変換しました。stroke-width
、fill-opacity
など、必要に応じて適切な形に変換しないとエラーが発生しました。stroke-width
→ strokeWidth
、fill-opacity
→ fillOpacity
を変換し修正しました。
Discussion