背景
Reactでアイコン付きボタンを作っていて、「アイコンの色もボタンに合わせたいなー」ということがありました。
(というより、アイコンにカスタマイズ性を持たせたい)
備忘録として...
ゴール
以下のようにアイコン付きのボタンを表示させる時に、アイコンの色を文字色と同じように揃えます。
結論
fill="currentColor"
によって、アイコンの色を親要素で適応されているcolor
で塗りつぶす。
サンプルコード
コンポーネントの作りはざっくり以下のような感じ
-
ButtonWithIcon
というコンポーネント名だが、その実態はButton
コンポーネントの拡張で、iconがあると、そのアイコンを左側に表示 -
props
としてcolor
を受け取って、文字色に適用 -
alt
はアクセシビリティ的に必要なので追加
全体コード
import React, { ReactNode } from 'react';
type ButtonWithIconProps = {
children: ReactNode;
icon?: 'heart' | 'star';
color?: string;
alt?: string;
};
const buttonStyle = {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
gap: '4px',
borderRadius: '4px',
backgroundColor: 'gray',
paddingTop: '8px',
paddingBottom: '8px',
paddingRight: '16px',
paddingLeft: '16px',
};
export const ButtonWithIcon = (props: ButtonWithIconProps) => {
const { children, icon, color } = props;
return (
<button
style={{
...buttonStyle,
color,
}}
>
{icon && renderSvgIcon(props)}
{children}
</button>
);
};
function renderSvgIcon(props: ButtonWithIconProps) {
const svgAlt = getAlt(props);
const icon = props.icon;
const svgProps = {
...(svgAlt ? { 'aria-label': svgAlt } : { 'aria-hidden': true }),
fill: 'currentColor',
xmlns: 'http://www.w3.org/2000/svg',
width: '16px',
height: '16px',
viewBox: '0 0 512 512',
};
switch (icon) {
case 'heart':
return (
<svg {...svgProps}>
<path
d="M380.63,32.196C302.639,33.698,264.47,88.893,256,139.075c-8.47-50.182-46.638-105.378-124.63-106.879
C59.462,30.814,0,86.128,0,187.076c0,129.588,146.582,189.45,246.817,286.25c3.489,3.371,2.668,3.284,2.668,3.284
c1.647,2.031,4.014,3.208,6.504,3.208v0.011c0,0,0.006,0,0.011,0c0,0,0.006,0,0.011,0v-0.011c2.489,0,4.856-1.177,6.503-3.208
c0,0-0.821,0.086,2.669-3.284C365.418,376.526,512,316.664,512,187.076C512,86.128,452.538,30.814,380.63,32.196z"
></path>
</svg>
);
case 'star':
return (
<svg {...svgProps}>
<polygon points="256,12.531 327.047,183.922 512,198.531 370.938,319.047 414.219,499.469 256,402.563 97.781,499.469 141.063,319.047 0,198.531 184.953,183.922"></polygon>
</svg>
);
default:
return null;
}
}
function getAlt({ alt, icon }: ButtonWithIconProps) {
if (alt) return alt;
switch (icon) {
case 'heart':
return 'いいね';
case 'star':
return 'お気に入り';
default:
return '';
}
}
細かく解説
svgファイルのレンダリング方法
svgのレンダリングには複数の手法があるので、せっかくなので紹介します。
Img タグを使用する
もっとも有名で簡単。
この方法は、SVGファイルを直接インポートし、それらを img
タグの src
プロパティとして渡します。
import star from "./assets/star.svg";
export const Icon= () => {
return <img src={star} alt="" />;
}
この方法では、SVGの幅や高さは変更できても色の変更などができず、スタイルのカスタマイズの柔軟性が制限されます。
この方法は簡単ですが、そのデメリットから非推奨なやり方です。
※Next.jsを使用している場合、'next/image'を使用すると思いますが、これも同様です。
SVG要素を返すカスタムReactコンポーネント
今回使用した方法です。推し
create-react-app
でアプリを立ち上げているなら、すでにSVGR
が組み込まれているため、Reactコンポーネントのようにsvg
を使用できます。
const svgProps = {
...(svgAlt ? { 'aria-label': svgAlt } : { 'aria-hidden': true }),
fill: 'currentColor',
xmlns: 'http://www.w3.org/2000/svg',
width: '16px',
height: '16px',
viewBox: '0 0 512 512',
};
return (
<svg {...svgProps}>
<polygon points="256,12.531 327.047,183.922 512,198.531 370.938,319.047 414.219,499.469 256,402.563 97.781,499.469 141.063,319.047 0,198.531 184.953,183.922"></polygon>
</svg>
);
この方法を使用すると、塗りつぶしの色を変更などsvgタグのプロパティに簡単にアクセスできます。
<svg fill="#fff" />
今回は、button
のカラーと合わせたいのでcurrentColor
を使用します。
アクセシビリティな要素
以下の要素です。
const svgProps = {
...(svgAlt ? { 'aria-label': svgAlt } : { 'aria-hidden': true }),
要素の既定の アクセシブル名 がなかったり、その内容を正確に記述していなかったりして、オブジェクトに意味を与えるために関連付けることができるコンテンツが DOM に表示されていないことがあります。よくある例は、 SVG やアイコンフォント(使用すべきではない)を含む、テキストのないボタンです。
というようにユーザーにボタンの機能を明確に伝えることができるので、svgタグを使用する時につけておく方が良さそう
aria-hidden 属性を使用することで、アクセシビリティ API から操作不可能なコンテンツを隠すことができます。
alt
がないなら、あえて情報を取得させないようにしておく。
svgファイルの不要な記述は消しておく
今回サンプルコードのためのsvgファイル
はフリー素材を利用しました。
そのまま利用すると、fill
プロパティなどで色が入ってしまうので、そういった要素を削除します。
ついでに不要なプロパティを削ってデータをスッキリさせましょう。
svg
は描画時に演算を要するので、データは軽い方が好ましいです。
before
<!--?xml version="1.0" encoding="utf-8"?-->
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="_x32_" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" style="width: 64px; height: 64px; opacity: 1;" xml:space="preserve">
<style type="text/css">
.st0{fill:#4B4B4B;}
</style>
<g>
<path class="st0" d="M380.63,32.196C302.639,33.698,264.47,88.893,256,139.075c-8.47-50.182-46.638-105.378-124.63-106.879
C59.462,30.814,0,86.128,0,187.076c0,129.588,146.582,189.45,246.817,286.25c3.489,3.371,2.668,3.284,2.668,3.284
c1.647,2.031,4.014,3.208,6.504,3.208v0.011c0,0,0.006,0,0.011,0c0,0,0.006,0,0.011,0v-0.011c2.489,0,4.856-1.177,6.503-3.208
c0,0-0.821,0.086,2.669-3.284C365.418,376.526,512,316.664,512,187.076C512,86.128,452.538,30.814,380.63,32.196z" style="fill: rgb(75, 75, 75);"></path>
</g>
</svg>
style、version、id、xmlns:xlink、gタグ
など不要な要素は削除し、他のsvg要素と共通している部分は括り出します。サイズもいい感じに変更しましょう。
今回はないですが、traslate
で座標を冗長に変更しているファイルを結構見るので、不要な演算も消しましょう。
after
const svgProps = {
...(svgAlt ? { 'aria-label': svgAlt } : { 'aria-hidden': true }),
fill: 'currentColor',
xmlns: 'http://www.w3.org/2000/svg',
width: '16px',
height: '16px',
viewBox: '0 0 512 512',
};
<svg {...svgProps}>
<path
d="M380.63,32.196C302.639,33.698,264.47,88.893,256,139.075c-8.47-50.182-46.638-105.378-124.63-106.879
C59.462,30.814,0,86.128,0,187.076c0,129.588,146.582,189.45,246.817,286.25c3.489,3.371,2.668,3.284,2.668,3.284
c1.647,2.031,4.014,3.208,6.504,3.208v0.011c0,0,0.006,0,0.011,0c0,0,0.006,0,0.011,0v-0.011c2.489,0,4.856-1.177,6.503-3.208
c0,0-0.821,0.086,2.669-3.284C365.418,376.526,512,316.664,512,187.076C512,86.128,452.538,30.814,380.63,32.196z"
></path>
</svg>
かなりスッキリしましたね。
最後に
ここまでみていただきありがとうございます!
結論はfill="currentColor"
というだけなのですが、コンポーネントを作るまでに考えたことも補足で書いていると少し長くなりました。
参考
How to Use SVG in React
aria-label
aria-labelを使ってみよう
aria-hidden