🎯

Shadow DOM 内にフォントファイルの読み込みを利かす方法

2023/02/19に公開

問題点と目的

Shadow DOM 内で @mdi/font を読み込むとCSS は適用されるものの肝心のフォントファイルが読み込まれず正常にアイコンが表示されないという罠がある。

簡単な解決法としては Shadow DOM の外側で同じ CSS を読み込めばよい。ただせっかく Web Components 化してるのに利用する側に余計な手間をかけさせたくはないので、なんとかして Web Components 側で解決したい。

基本

<html>
<body>
  <my-tag></my-tag>
  <script>
    const mdi_css = `<link href="https://cdn.jsdelivr.net/npm/@mdi/font/css/materialdesignicons.min.css" rel="stylesheet">`
    const template = document.createElement("template")
    template.innerHTML = `
${mdi_css}
<i class="mdi mdi-account"></i>
`
    class MyTag extends HTMLElement {
      connectedCallback() {
        this.attachShadow({mode: "open"})
        this.shadowRoot.appendChild(template.content.cloneNode(true))
        this.innerHTML = mdi_css
      }
    }
    customElements.define("my-tag", MyTag)
  </script>
</body>
</html>

Shadow Host の innerHTML は Shadow DOM の外側になるので innerHTML に link タグを指定してやればよい。

Vue.js 2

<template lang="pug">
.MyTag
  i.mdi.mdi-account
</template>

<script>
export default {
  beforeMount() {
    const unique_id = "d38954813255e4b9e5d098caae4cc2f4"
    if (!document.getElementById(unique_id)) {
      const template = document.createElement("template")
      template.innerHTML = `<link href="https://cdn.jsdelivr.net/npm/@mdi/font/css/materialdesignicons.min.css" rel="stylesheet" id="${unique_id}">`
      const el = document.querySelector("head")
      el.appendChild(template.content.cloneNode(true))
    }
  },
}
</script>

<style lang="sass">
@import url("https://cdn.jsdelivr.net/npm/@mdi/font/css/materialdesignicons.min.css");
</style>

Vue.js 2 の場合 vue-cli-service によって Web Components 化の部分がブラックボックス化されているので小細工が難しい。ただ Shadow DOM 内から外は見えるので beforeMount のタイミングで head タグ[1]のなかに link タグを埋め込んでやればよい。さらにこのままだと Web Components を呼ぶ度に link タグが溜って[2]みっともないので id をつけて1回だけ追加するように工夫した。

参考

https://stackoverflow.com/questions/60504404/how-to-use-material-design-icons-in-a-web-component

脚注
  1. bodyでもいい ↩︎

  2. 溜ってもブラウザがキャッシュしてるからそんな問題はない ↩︎

Discussion