この記事は、【 可茂IT塾 Advent Calendar 2025 】の14日目の記事です。
特定の箇所だけのSVG画像のカラーを変更したいときありませんか?
既存パッケージ(flutter_svgなど)でもカラー変更は可能ですが、画像全体のカラーが変更されてしまいますよね...
例えば:
そんなSVG問題を解決するためにCustomWidgetを作成しました!
✅ 特定の色コードを指定して置換
✅ SVG要素のIDを指定して色変更
✅ グラデーションの色も個別に変更可能
✅ fill/stroke/style属性など様々な形式に対応
UniversalSvg(
assetPath: 'assets/icons/sample_icon.svg',
colorRules: [
SvgColorRule.byId(
targetId: 'background-circle', // この要素だけ
changedColor: '#00FF00', // 緑色に変更
),
],
)
UniversalSvg(
assetPath: 'assets/icons/gradient_icon.svg',
gradientRules: [
SvgGradientRule(
targetGradientId: 'gradient1',
newColors: [
Color(0xFF6366F1), // 開始色
Color(0xFF8B5CF6), // 終了色
],
),
],
)
UniversalSvg(
assetPath: 'assets/icons/complex_icon.svg',
colorRules: [
SvgColorRule.byColor(
targetColor: '#000000',
changedColor: Theme.of(context).primaryColor.toHex(),
),
SvgColorRule.byId(
targetId: 'accent-part',
changedColor: '#FF6B6B',
),
],
gradientRules: [
SvgGradientRule(
targetGradientId: 'bg-gradient',
newColors: [Colors.purple, Colors.pink],
),
],
)
このWidgetの核心は「SVGをStringとして扱い、該当箇所を文字列置換する」というシンプルな仕組みです。
Future<String> _loadAndProcessSvg() async {
// 1. SVGファイルを文字列として読み込み
final svgContent = await rootBundle.loadString(assetPath);
// 2. 色の置換ルールを適用
String result = _applySvgColorRules(svgContent, colorRules);
// 3. グラデーションの置換ルールを適用
result = _applySvgGradientRules(result, gradientRules);
// 4. 処理済みSVG文字列を返す
return result;
}
_replaceColorInAllFormats)SVGファイル内では色が様々な形式で定義されています:
<!-- fill属性 -->
<path fill="#FF0000" ... />
<!-- stroke属性 -->
<circle stroke="#FF0000" ... />
<!-- style属性(スペースなし) -->
<rect style="fill:#FF0000" ... />
<!-- style属性(スペースあり) -->
<polygon style="fill: #FF0000" ... />
これら全てのパターンに対応するため、複数の置換処理を実行しています:
String _replaceColorInAllFormats(String svgContent, String targetColor, String changedColor) {
String result = svgContent;
// fill属性
result = _replaceIgnoreCase(result, 'fill="$targetColor"', 'fill="$changedColor"');
// stroke属性
result = _replaceIgnoreCase(result, 'stroke="$targetColor"', 'stroke="$changedColor"');
// style内のfill(スペースなし/あり両対応)
result = _replaceIgnoreCase(result, 'fill:$targetColor', 'fill:$changedColor');
result = _replaceIgnoreCase(result, 'fill: $targetColor', 'fill: $changedColor');
// 以下同様...
return result;
}
ポイント: 大文字小文字を区別しない置換(caseSensitive: false)で、#ff0000も#FF0000も確実に置換できます。
_replaceColorById)正規表現を使って、特定のIDを持つ要素を探して色を置換します:
// id="targetId"を含む要素のfill属性を置換
final fillPattern = RegExp(
r'(id="' + RegExp.escape(targetId) + r'"[^>]*?)fill="[^"]*"',
caseSensitive: false,
);
工夫したポイント:
これにより、以下のようなSVGでも確実に色変更できます:
<!-- 既存のfillを置換 -->
<circle id="target" fill="#000000" />
→ <circle id="target" fill="#FF0000" />
<!-- fillがない場合は追加 -->
<rect id="target" />
→ <rect id="target" fill="#FF0000" />
_replaceGradientColors)グラデーションは少し複雑で、<linearGradient>内の複数の<stop>要素を順次置換します:
<linearGradient id="gradient1">
<stop offset="0%" stop-color="#FF0000" />
<stop offset="100%" stop-color="#00FF00" />
</linearGradient>
処理の流れ:
String _replaceGradientColors(String svgContent, String gradientId, List<Color> newColors) {
// 1. 対象のlinearGradientブロック全体を検索
final gradientPattern = RegExp(
r'<linearGradient[^>]*?id="' + RegExp.escape(gradientId) + r'"[^>]*?>[\s\S]*?</linearGradient>'
);
return svgContent.replaceAllMapped(gradientPattern, (match) {
String gradientBlock = match.group(0) ?? '';
// 2. stop要素を検索
final stopMatches = stopPattern.allMatches(gradientBlock).toList();
// 3. 各stop要素の色を順次置換
for (int i = 0; i < stopMatches.length && i < newColors.length; i++) {
String updatedStop = _replaceStopColor(stopElement, newColor);
gradientBlock = gradientBlock.replaceFirst(stopElement, updatedStop);
}
return gradientBlock;
});
}
結果:
<linearGradient id="gradient1">
<stop offset="0%" stop-color="#6366F1" /> <!-- 新しい色1 -->
<stop offset="100%" stop-color="#8B5CF6" /> <!-- 新しい色2 -->
</linearGradient>
githubにサンプルを掲載しているので詳細はこちらからチェックしてもらえると!
cloneしてサンプルも動かすことができます
SVGの特定箇所だけ色を変更したいという要望に応えるため、UniversalSvgウィジェットを作成しました。
文字列置換というシンプルな方法ながら、以下の機能を実現:
実装も比較的シンプルなので、プロジェクトに合わせてカスタマイズしやすいのも特徴です。
同じような課題を抱えている方の参考になれば幸いです!
可茂IT塾ではFlutter/Reactのインターンを募集しています!可茂IT塾のエンジニアの判断で、一定以上のスキルをを習得した方には有給でのインターンも受け入れています。
Read More可茂IT塾ではFlutter/Reactのインターンを募集しています!可茂IT塾のエンジニアの判断で、一定以上のスキルをを習得した方には有給でのインターンも受け入れています。
Read More