inert属性を使ってフォーカスやクリックを無効化する

Webアプリケーションを作っていると 操作を無効にしたい場面がいろいろと出てきます。

例えば
- API通信中は全画面にLoadingを表示し、さらに背景にフォーカス遷移を防止する。
- モーダルダイアログ表示中はタブキーによって背景(親画面)へのフォーカス移動を防止する。

などのケースが存在します。

最近ほとんどのブラウザで使えるようになった `inert` という属性を使うことで 特定の領域内でのフォーカスやクリックの発生を無効化することができるようになります。

`inert` 属性を利用することでできることについては 参考のリンク先をご確認ください。


## 使い方

以下のように 適応したい領域のルートに `inert` 属性を付与します。

``` html
<div inert>
  <div>
    ここにあなたのお名前と血液型を選択して登録してください。
  </div>
  <div>
    <label for="yourName">お名前:</label>
    <input type="text" id="yourName">
  </div>
  <div>
    <label for="bloodType">血液型:</label>
    <select id="bloodType">
      <option value=""></option>
      <option value="A">A</option>
      <option value="B">B</option>
      <option value="O">O</option>
      <option value="AB">AB</option>
    </select>
  </div>
  <div>
    <button>登録</button>
  </div>
</div>
```

![sample1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628749/8585965d-3c22-710f-e006-45b1d1d4dd51.gif)


このようにすることで領域選択やフォーカス、クリックが発生しなくなります。

### スタイルを適用する

`inert` 属性を付与しても見た目上は変化がありません。

スタイルを適用することで見た目を変化させることができます。
(最近はCSSもネストができたり`:is`でまとめてかけたり便利ですね)

``` css
[inert] {
  & :is(input, select, button) {
    background-color: gray;
  }
}
```

![sample2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628749/01021c2f-47d2-c42a-e38a-e204ac0b14fb.png)


### JavaScriptを使って動的に付与する

実際のアプリケーションでは動的に`inert` を付与することがほとんどだと思います。

`JavaScript` では以下のようにすることで適用することができます。

```html
<!-- 切り替え可能にするためにidを追加 -->
<div id="target">
  <div>
    ここにあなたのお名前と血液型を選択して登録してください。
  </div>
  <div>
    <label for="yourName">お名前:</label>
    <input type="text" id="yourName">
  </div>
  <div>
    <label for="bloodType">血液型:</label>
    <select id="bloodType">
      <option value=""></option>
      <option value="A">A</option>
      <option value="B">B</option>
      <option value="O">O</option>
      <option value="AB">AB</option>
    </select>
  </div>
  <div>
    <button>登録</button>
  </div>
</div>
<!-- 切り替え用ボタン -->
<button id="toggleInert">inert 切り替え</button>
```

```javascript
const targetElem = document.getElementById("target")
const toggleInertElem = document.getElementById("toggleInert")

// クリック時に切り替える
toggleInertElem.addEventListener("click", () => {
  // 要素のinert属性にtrue/falseを設定する。
  // このコードでは反転した値を設定している。
  targetElem.inert = !targetElem.inert
})
```

![sample3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628749/9ec14ac8-d5a0-1058-1f23-b8f7da21b751.gif)


### Vueを使った例

実際の開発では何らかのフロントエンドライブラリと組み合わせることがほとんどでしょう

ここでは Vueを使ってモーダルを表示する例を紹介します。モーダルには[Bulma](https://bulma.io/documentation/components/modal/)のモーダルを利用します。


``` vue
<script setup>
import { ref } from "vue";

const inert = ref(false);
const visibleModal = ref(false);

function show() {
  inert.value = true;
  visibleModal.value = true;
}

function closeModal() {
  inert.value = false;
  visibleModal.value = false;
}
</script>

<template>
  <div>
    <!-- inertにバインド -->
    <div :inert="inert">
      <div class="field">
        <label class="label">Name</label>
        <div class="control">
          <input class="input" type="text" placeholder="Text input" />
        </div>
      </div>

      <button class="button is-primary" @click="show">モーダル開く</button>

      <!-- Teleportの機能を利用しモーダル自体は このコンポーネントのDOMの外側に配置する。そうすることでinertの領域の外側となりモーダルはinertの影響を受けない-->
      <Teleport to="body">
        <template v-if="visibleModal">
          <div class="modal is-active">
            <div class="modal-background"></div>
            <div class="modal-card">
              <header class="modal-card-head">
                <p class="modal-card-title">モーダルヘッダー</p>
              </header>
              <section class="modal-card-body">
                モーダルの本体
              </section>
              <footer class="modal-card-foot">
                <button class="button is-success" @click="closeModal">OK</button>
              </footer>
            </div>
          </div>
        </template>
      </Teleport>
    </div>
  </div>
</template>

```

![sample-vue.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628749/96793746-37d3-8e0d-63ee-b15faec831aa.gif)


ここでは単純な例にしていますが実際は無効化の対象領域がページ全体などになることが多いと思うので VuexやPiniaを用いてグローバルに状態管理をするか、親のコンポーネントからprovide/injectしてもよいかもしれません。


※dialog要素を使ってモーダルを表示する時はフォーカスが背景に移動しないようになっているそうですが、まだまだdiv要素を利用したモーダルが多いと思うので一例として紹介しました。


## 参考

- [Can I use : HTML attribute: inert](https://caniuse.com/mdn-html_global_attributes_inert)
- [inert (MDN)](https://developer.mozilla.org/ja/docs/Web/HTML/Global_attributes/inert)
- [The inert attribute](https://html.spec.whatwg.org/multipage/interaction.html#the-inert-attribute)