Open14

SVG IconのVue化やる関連

hashrockhashrock

ここでやっている変換をやれば良さそうである。

https://react-svgr.com/docs/what-is-svgr/

実際にcliを試してみる。こうなった。

import * as React from "react";
const SvgInput = (props) => (
  <svg xmlns="http://www.w3.org/2000/svg" width={48} height={1} {...props}>
    <path fill="#063855" fillRule="evenodd" d="M0 0h48v1H0z" />
  </svg>
);
export default SvgInput;

自動でcurrentColorはやってくれなかった(cliオプションでできる)。サンプルでは1em widthになってるけどそれもなにかのオプションかな?

hashrockhashrock

サイズ指定をどうするか。
基本的には残したほうがいい気がする。SVGはサイズ指定がない場合にはimgのようには振る舞わず、枠が伸びて内容がセンタリングするような挙動になる。(preserveAspectRatio="MidYMid meet"が適用される)

https://www.npmjs.com/package/@tabler/icons-vue?activeTab=code

tabler-iconsの場合は上からsizeをpropsで渡すために消しているっぽい。

import { h } from 'vue';
import defaultAttributes from './defaultAttributes.mjs';

const createVueComponent = (type, iconName, iconNamePascal, iconNode) => ({ color = "currentColor", size, stroke, title, class: classes, ...rest }, { attrs, slots }) => {
  let children = [...iconNode.map((child) => h(...child)), ...slots.default ? [slots.default()] : []];
  if (title)
    children = [h("title", title), ...children];
  return h(
    "svg",
    {
      ...defaultAttributes[type],
      width: size,
      height: size,
      ...attrs,
      class: ["tabler-icon", `tabler-icon-${iconName}`],
      ...type === "filled" ? {
        fill: color
      } : {
        "stroke-width": stroke ?? defaultAttributes[type]["stroke-width"],
        stroke: color
      },
      ...rest
    },
    children
  );
};

export { createVueComponent as default };
hashrockhashrock

imgで使うかインラインSVGで使うか。

  • まずimgだとcurrentColorを使っての色の差し込みができない。
  • クラスを使っての上書きもできない。
  • インラインSVGだとプレゼンテーション属性で指定した色などを外部からスタイルで上書きできる(優先度が低いらしい)
hashrockhashrock

SVG to Vue.js componentがやってるこれは賢いかもしれない。グレースケールの色を使っていたら削除して差し込み用のクラスを当てる。
プレゼンテーション属性は優先順位が低いので削除せずとも上書きできるとは思うが

Export options
‘.fill-current-color’ instead of grayscale fill

‘.stroke-current-color’ instead of grayscale stroke

Removes grayscale fill/stroke and adds class to the node. Only works if all fills/strokes in the frame are grayscale (if frame has a saturated fill/stroke color the entire frame will be ignored). Best for icons. Don't forget add css rules:

.fill-current-color { fill: currentColor }

.stroke-current-color { stroke: currentColor }
hashrockhashrock

おや、imgとインラインSVGでスタイルでwidthだけ当てたときの挙動が違うな。

  • img: アスペクト比を保って拡大
  • インラインSVG: キャンバスが伸びる(height: autoを指定すると拡大)

あー、そもそも属性で書いたスタイルって優先度低いのか。勝手に高いと思い込んでた

https://woshidan.hatenablog.com/entry/2014/03/29/002004

そのため、SVGの属性に直に書いたwidth, heightは、.w-8とかで個別に上書きされてしまう。そうなるとwidthとheightがアスペクト比が壊れた状態で拡大される。その結果preserveAspectRatioが発動すると。

hashrockhashrock

imgとしてSVGを読み込んだ場合は、画像のimgと同じような挙動になるので、使い勝手が変わりそうだなぁ。
どっちにせよ、インラインSVGの場合はheightとwidthを何処かから与えてあげないと事故りそうである。レスポンシブSVGにするときは意図してwidthやheightを削除するほうが良さそう(width: 100%などだけ指定したときにアスペクト比が破壊されそう)

hashrockhashrock

使うときは以下のどっちかだと思うがどっちがいいんだろ?

<span style="color: #333">
  <IconUser :size="16" />
</span>
<IconUser class="text-gray-800 w-6 h-6"  />
hashrockhashrock

iconifyのエクスポートがデフォルト width="1em" height="1em" になってるのは面白いな。
imgで使う場合には機能しないけど。インラインSVGで使う場合にはfont-sizeに応じたサイズにすることができる。使う側は楽かな?

hashrockhashrock

unplugins-iconsいい気がする。
FileSystemIconLoaderというのを使えばOK。

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import Icons from "unplugin-icons/vite";
import { promises as fs } from "node:fs";
import { FileSystemIconLoader } from "unplugin-icons/loaders";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    Icons({
      customCollections: {
        "my-iconset": FileSystemIconLoader(
          "./assets/icons",
          (svg) => svg.replace(/^<svg /, '<svg fill="currentColor" '),
        ),
      },
    }),
  ],
});

自前でcurrentColorを設定すればいいけど、ダルかったら一色currentColorと置換したい色を決めておけばあとは自動でやれるっぽい。width / heightの1em設定もやりたければ置換でやれば〜って感じっぽいな。

あり物のアイコンセット使う場合もautoInstall機能あるのいいっすね。iconifyからimportしたら対応したアイコンセットを勝手にnpm install -Dやってくれる。iconifyのクソデカアイコンセットJSONをインストールするのあんま良くないと思うのでこういう仕組みはみんな幸せになると思う。

hashrockhashrock

結局アイコンなんで、サボらずに利用側でwidthとheightを設定するようにしている限り、変なことは起きないっぽい。currentColorの差し込みをしたい場合は自前でVue Component化するかunplugin-icons使う。duotone iconみたいに複数色差し込みをしなきゃいけないような場合は手作業でやってprops経由で色を直接いれるかtailwind classをSVG内に差し込む仕組みが必要だろう。

SVGをimgとして読み込む場合はcurrentColorを捨てるみたいなケースだろう。でもダークテーマとか入ってくるようだったら基本インラインSVGにしたほうが自由が効きそう。例えばこんなふうにしたいことがあるわけだよね。

<IconUser class="text-gray-800 dark:text-gray-100"  />

それとは別にSVGイラストをレスポンシブにしたいかどうか、みたいなやつは仕様でハマったりとかはありそう。