3
0

More than 1 year has passed since last update.

Blazor で svg 要素の use xlink:href を使ったらなぜか表示されない話

Last updated at Posted at 2022-12-14

よくある svg の symbol と use の組み合わせでアイコン表示をしてみる

とある Blazor アプリケーションにて、svg の symbol 定義と、その symbol 定義を参照してアイコン表示する実装をすることになりました。

具体的には、まず Razor コンポーネント内に下記のように svg の symbol 定義をマークアップします。(ちなみに下記例は、Google Fonts の Material Symbols から入手した、太陽 (🌞"sun")、月 (🌙"moon")、星 (⭐"star") のアイコンを、それぞれ <symbol id="... で symbol 定義したものの抜粋です。)

App.razor
<svg xmlns="http://www.w3.org/2000/svg" display="none">
    <defs>
        <symbol id="sun" viewBox="0,0,48,48">
            <path d="M24.05 46.55..." />
        </symbol>
        <symbol id="moon" viewBox="0,0,48,48">
            <path d="M24 42q-7.5..." />
        </symbol>
        <symbol id="star" viewBox="0,0,48,48">
            <path d="m16.15 37.75..." />
        </symbol>
    </defs>
</svg>

上記 svg 要素は、あとでこのコンポーネント内に描画する svg 要素からここで定義した図形を参照して表示するための "定義集" です。ですから、上記 svg 要素は表示上は見えては困ります。そこで、display="none" の属性を指定して非表示としてあります。

あとは、表示用の svg 要素で use 要素を使い、上記 symbol 定義の id 属性値を指すセレクタで表示したい図形を指定することで、目的の図形がページ上に表示するようレンダリングします。例えば下記は、id 属性値が "sun" である symbol 定義の図形をページ上に表示する例です。

App.razor
...
<svg xmlns="http://www.w3.org/2000/svg">
    <use xlink:href="#sun" />
</svg>
...

下図はこれを実行してみた例です。
image.png

ここまでは、ごく普通に使われているであろう、svg の symbol および use を組み合わせた図形表示の定番実装に過ぎません。

動的にこれらを表示できるようにする

これは静的な HTML を編集しているのではなく、Blazor ですから、これら svg によるアイコン表示を、動的に表示できるように実装してみます。具体的には、太陽、月、星のいずれかの図形を指定してページに追加できる3つのボタンを実装します。下図が実現イメージです。

movie-001.gif

作るものが決まりましたので、実際に実装していきます。

まずは、太陽・月・星の各図形を示す enum 定数 = SymbolType と、それを格納する List<T> フィールド変数を、Razor コンポーネントに実装します。SymbolType enum 定数の名前は、冒頭でマークアップした、svg の symbol 定義の id 属性値に合せます (C# の命名規則に寄せたので、英字の大小だけ不一致ですが)。

App.razor
...
@code {
    // 👇 Symbol 定義の id 属性値を示す enum 定数を定義
    private enum SymbolType
    {
        Sun,
        Moon,
        Star
    }

    // 👇 上記 Symbol enum 定数を格納する List<T> フィールド変数を定義
    private readonly List<SymbolType> _Symbols = new();
}

この Razor コンポーネントの HTML マークアップ部分で、上記 _Symbol フィールド変数に格納された SymbolType の要素を @foreach(...) ループで列挙して svg 要素としてレンダリングするようにします。なお、前述のとおり、SymbolType enum 定数の名前で Symbol 定義の id 属性値を示すようにしていますが、英字の大小だけ不一致なので、SymbolType enum 定数の名前を小文字化する処理を忘れないようにします。

App.razor
...
<p>
  <!-- 👇 フィールド変数 _Symbols に格納された SymbolType enum 定数を列挙 -->
  @foreach (var symbol in _Symbols)
  {
    <svg xmlns="http://www.w3.org/2000/svg">
      <!-- 👇 列挙された SymbolType enum 定数名を小文字化して、それで id セレクタ指定 -->
      <use xlink:href="#@(symbol.ToString().ToLower())" />
    </svg>
  }
</p>

@code {
    ...

続けて、引数に指定した図形 (SymbolType) を、_Symbols List フィールド変数に要素追加するメソッドを実装します。

App.razor
...
@code {
    ...
    // 👇引数に指定された図形を _Symbols リストに追加
    private void AddSymbol(SymbolType symbol)
    {
        _Symbols.Add(symbol);
    }
}

最後に、太陽・月・星の各図形を追加する 3 つのボタンを設け、そのクリックイベントで上記で用意した図形追加用のメソッド AddSymbol(...) を呼び出すようにします。

App.razor
...
<!-- 👇 太陽・月・星の各図形を追加する 3 つのボタンを設置、クリックされたら AddSymbol を呼び出す -->
<p>
  <button @onclick="@(() => AddSymbol(SymbolType.Sun))">Add Sun</button>
  <button @onclick="@(() => AddSymbol(SymbolType.Moon))">Add Moon</button>
  <button @onclick="@(() => AddSymbol(SymbolType.Star))">Add Star</button>
</p>

<p>
  @foreach (var symbol in _Symbols)
  {
    ...

これで完成です!

早速実行してみるも、なぜか図形が表示されない!?

早速実行してみると...
movie-002.gif
あれれ、たしかに svg 要素は追加されているようですが、肝心の svg 図形が表示されません。何を間違ったのでしょうか。

問題の切り分けのために、xlink:href に指定している Symbol 定義の id セレクタ指定を、即値に一時変更してみました。

App.razor
...
  @foreach (var symbol in _Symbols)
  {
    <svg xmlns="http://www.w3.org/2000/svg">
      <!-- 👇 xlink:href に指定する id セレクタ指定を即値にしてみる -->
      <use xlink:href="#sun" />
      ...

すると、どうやらちゃんと図形表示されるようです。即値にしましたので、どのボタンをクリックしても、太陽 = "#sun" の図形しか表示はされませんが、とにかくそれ以外の実装には間違いはなさそうです。
movie-003.gif
では、SymbolType enum 定数の名前を文字列化したり小文字化したりする処理にミスがあったのでしょうか。引き続き、xlink:href 指定を即値で指定しますが、ただし、C# の文字列即値を Razor 構文で xlink:href 属性に指定するようにしてみます。

App.razor
...
  @foreach (var symbol in _Symbols)
  {
    <svg xmlns="http://www.w3.org/2000/svg">
      <!-- 👇 Razor 構文で C# の文字列即値で指定してみる -->
      <use xlink:href="@("#sun")" />
      ...

すると再び図形が表示されなくなってしまいました...。
movie-002.gif
どうも、Razor 構文で動的に xlink:href を指定するだけで、このようにうまく機能しなくなるようです。これは Blazor 側の問題なのか、svg use 要素の xlink:href の仕様の都合なのか、いずれにせよいったいどうしたものでしょうか...。

xlink:href は廃止予定!?

ネット上に何か同じ事で困っている事例がないか、検索していたところ、以下の検索結果が目に留まりました。
image.png
2016年ということで、既に6年も前の投稿ですが... xlink:href は廃止予定だったんですね!
大変不勉強なもので、このことを知っていませんでした...。
実はこの技法、React 向けの某有名 OSS の実装からパクったで学んだのですが、そちらも歴史が古いせいか、最新コミットでも xlink:href で図形指定していたんで気づかなかったです (所詮言訳ですが)。

改めて MDN 上の記載を確認しても (下記リンク先)、しっかり "Deprecated: This feature is no longer recommended"、すなわち「非推奨: この機能は推奨されなくなりました」と明記されていました。

さておき、上記リンク先の MDN の記載を読むと、xlink:href の代替としては単純に、「 href を使え」 ということだそうです。そこで、先の Blazor アプリでの実装も、xlink:href から href に書き換えてみました。

App.razor
...
  @foreach (var symbol in _Symbols)
  {
    <svg xmlns="http://www.w3.org/2000/svg">
      <!-- 👇 xlink:href ではなく、href を使う -->
      <use href="#@(symbol.ToString().ToLower())" />
      ...

すると...

movie-001.gif

ちゃんと期待どおりに動作するようになりました!

まとめ

Blazor のコンポーネント内で、svg の use 要素を使って、別途定義された Symbol を参照して図形を表示する際、いずれの Symbol を参照するのかのセレクタ指定に xlink:href を使っていると、図形が表示されない場合があることがわかりました。

そしてそもそも xlink:href の使用は少なくとも、自分がわかった範囲でも 2016 年時点で既に非推奨であったようです (stackoverflow にそのような投稿があったのを見つけた、というだけで、裏付け取ってませんが)。

代わりに href 属性でセレクタ指定することで、Blazor コンポーネント内での使用でも、問題なく図形表示されることが確認できました。

今回は、歴史の古い、あるいは、古いブラウザに対応するためのフォールバック指定がされている OSS の実装を手本としていたことが敗因のひとつかと考えています。ちゃんと MDN やその他公式の資料に目を通す習慣がないといけないですね。

Learn, Practice, Share :)

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0