inert 属性が便利
◆ inert 属性を指定した要素とその要素の子孫要素が不活性化
◆ 見た目上は存在するけど存在しないような扱いになる
◆ マウス操作やフォーカスや入力等はできない
◆ overflow:hidden のエリアに隠れてる要素や ダイアログ等で裏側に表示される要素に使える
◆ 見た目上は存在するけど存在しないような扱いになる
◆ マウス操作やフォーカスや入力等はできない
◆ overflow:hidden のエリアに隠れてる要素や ダイアログ等で裏側に表示される要素に使える
Chrome 102 から inert 属性が使えるようになりました
hidden のようにどの要素にでも設定できて プロパティに true/false を設定して切り替えることもできます
true になるとその要素と要素の内側の要素すべてが不活性化します
この状態ではユーザーが操作はできず クリックしてもリスナ登録した関数は呼び出されません
リンクをクリックしても反応しませんし マウスを乗せたときに表示される移動先も表示されません
また input などのフォームパーツにフォーカスできず入力もできません
画面に見た目上は残したいけど操作は完全にできなくしたいということはよくあります
画面内のスクショさえ撮れれば DOM は消して代わりに画像を配置することで実現できるのになんて思ったこともあるくらいです
そんな 完全にユーザーは操作できず見た目上だけ存在するという状態を簡単に実現できる便利な属性です
これでは キーボードでタブキーを押してフォーカスを移動すればダイアログの裏側にある input にフォーカスして編集できてしまったりします
(例)
プレビュー機能で試せます
裏側にフォーカスさせないように 1 つ 1 つに tabindex を設定していく方法もありますが inert 属性を使えば 簡単に対処できます
ダイアログを表示するときには ダイアログ外の要素のルートの要素に inert 属性をつけます
ダイアログを非表示にするときには inert 属性を外します
さっきの例に inert 属性を使ったバージョンです
その都合で display を none とせず overflow が hidden なコンテナ内で隠れてるだけみたいなケースがあります
こういう場合もタブキーでフォーカスを切り替えると切り替わってしまって中途半端にスクロールされてしまう問題があります
(例)
これも tabindex をつけることで対処ではできるものの 非表示にするので display を none にするほうが簡単です
ですが アニメーション中に表示を残すならアニメーション後に display を none にするような制御が必要になって少し面倒です
こういうときでも inert 属性を使えば簡単です
このとき ひとつひとつに disabled を設定するのは面倒です
最近はあまり見かけない気がしますが 以前は上から透明要素を被せて操作できなくしたり pointer-events を none にして操作できなくしているのを見たことがあります
簡単な方法で入力できなくはしているものの この方法ではマウス操作でフォーカスできないだけです
キーボードでタブキーを押して フォーカスすれば変更できてしまいます
(例)
こういうことをしたい場合にも inert 属性が使えます
ただこのケースでは fieldset タグを使い disabled を設定するほうが適切かもしれません
inert 属性との違いは inert 属性だと完全に不活性化し マウス操作ではそこに何もないような動きにになります
テキストの選択もできません
それに対して disabled は編集不可なだけでテキストを選択してコピーしたりはできます
また inert 属性では disabled のスタイルが適用されません
hidden のようにどの要素にでも設定できて プロパティに true/false を設定して切り替えることもできます
true になるとその要素と要素の内側の要素すべてが不活性化します
この状態ではユーザーが操作はできず クリックしてもリスナ登録した関数は呼び出されません
リンクをクリックしても反応しませんし マウスを乗せたときに表示される移動先も表示されません
また input などのフォームパーツにフォーカスできず入力もできません
画面に見た目上は残したいけど操作は完全にできなくしたいということはよくあります
画面内のスクショさえ撮れれば DOM は消して代わりに画像を配置することで実現できるのになんて思ったこともあるくらいです
そんな 完全にユーザーは操作できず見た目上だけ存在するという状態を簡単に実現できる便利な属性です
ダイアログ
例えばダイアログを表示するとき 単純に作るとページ内の全体に要素を被せて裏側を操作できなくしますこれでは キーボードでタブキーを押してフォーカスを移動すればダイアログの裏側にある input にフォーカスして編集できてしまったりします
(例)
プレビュー機能で試せます
<!DOCTYPE html>
<style>
.inputs {
display: flex;
flex-direction: column;
gap: 20px;
width: 400px;
}
.inputs input {
font-size: 18px;
padding: 3px 6px;
}
.dialog {
display: none;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
justify-content: center;
align-items: center;
}
.dialog.show {
display: flex;
}
.dialog .backdrop {
background: #0002;
position: absolute;
width: 100%;
height: 100%;
}
.dialog .body {
width: 320px;
text-align: center;
background: white;
box-shadow: 0 2px 5px 3px #0003;
z-index: 1;
padding: 15px;
}
.btn {
margin-top: 15px;
}
</style>
<script type="module">
const dialog = document.querySelector(".dialog")
window.onclick = (eve) => {
const button = eve.target.closest("button")
if (button) {
dialog.classList.toggle("show")
}
const back = eve.target.closest(".backdrop")
if (back) {
dialog.classList.remove("show")
}
}
</script>
<div class="inputs">
<input>
<input>
<input>
<input>
<input>
<input>
<button>OK</button>
</div>
<div class="dialog">
<div class="backdrop"></div>
<div class="body">
<div>Message</div>
<div class="btn"><button>OK</button></div>
</div>
</div>
裏側にフォーカスさせないように 1 つ 1 つに tabindex を設定していく方法もありますが inert 属性を使えば 簡単に対処できます
ダイアログを表示するときには ダイアログ外の要素のルートの要素に inert 属性をつけます
ダイアログを非表示にするときには inert 属性を外します
さっきの例に inert 属性を使ったバージョンです
<!DOCTYPE html>
<style>
.inputs {
display: flex;
flex-direction: column;
gap: 20px;
width: 400px;
}
.inputs input {
font-size: 18px;
padding: 3px 6px;
}
.dialog {
display: none;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
justify-content: center;
align-items: center;
}
.dialog.show {
display: flex;
}
.dialog .backdrop {
background: #0002;
position: absolute;
width: 100%;
height: 100%;
}
.dialog .body {
width: 320px;
text-align: center;
background: white;
box-shadow: 0 2px 5px 3px #0003;
z-index: 1;
padding: 15px;
}
.btn {
margin-top: 15px;
}
</style>
<script type="module">
const dialog = document.querySelector(".dialog")
window.onclick = (eve) => {
const button = eve.target.closest("button")
if (button) {
dialog.classList.toggle("show")
}
const back = eve.target.closest(".backdrop")
if (back) {
dialog.classList.remove("show")
}
document.querySelector(".inputs").inert = dialog.classList.contains("show")
}
</script>
<div class="inputs">
<input>
<input>
<input>
<input>
<input>
<input>
<button>OK</button>
</div>
<div class="dialog">
<div class="backdrop"></div>
<div class="body">
<div>Message</div>
<div class="btn"><button>OK</button></div>
</div>
</div>
見えない要素
タブの切り替えだったり モバイル端末風なスタック形式のメニューなどでは アニメーションで表示内容を切り替えることがありますその都合で display を none とせず overflow が hidden なコンテナ内で隠れてるだけみたいなケースがあります
こういう場合もタブキーでフォーカスを切り替えると切り替わってしまって中途半端にスクロールされてしまう問題があります
(例)
<!DOCTYPE html>
<style>
.tab {
border: 1px solid gray;
width: 400px;
height: 400px;
display: flex;
flex-direction: column;
}
.header {
display: flex;
border-bottom: 1px solid gray;
flex: none;
}
.header [data-tab] {
padding: 5px 10px;
border-top: 3px solid transparent;
cursor: pointer;
}
.header [data-tab].selected {
border-top-color: red;
background: #f0f0f0;
}
.body {
flex: 1;
overflow: hidden;
position: relative;
}
.body .scroll {
display: flex;
position: absolute;
left: 0;
transition: left .3s ease 0s;
}
.body [data-tab] {
box-sizing: border-box;
width: 400px;
flex: none;
padding: 10px;
}
</style>
<script type="module">
document.querySelector(".header").onclick = (eve) => {
const elem = eve.target.closest("[data-tab]")
if (!elem) return
document.querySelector(".header .selected")?.classList.remove("selected")
elem.classList.add("selected")
const content = document.querySelector(`.body [data-tab="${elem.dataset.tab}"]`)
document.querySelector(".scroll").style.left = `-${content.offsetLeft}px`
}
</script>
<div class="tab">
<div class="header">
<div data-tab="t1" class="selected">Tab1</div>
<div data-tab="t2">Tab2</div>
<div data-tab="t3">Tab3</div>
</div>
<div class="body">
<div class="scroll">
<div data-tab="t1">
<h1>Tab1</h1>
<div><input value="test"></div>
<div><input></div>
</div>
<div data-tab="t2">
<h1>Tab2</h1>
<div><input value="test"></div>
<div><input></div>
<div><input></div>
<div><input></div>
<div><input></div>
</div>
<div data-tab="t3">
<h1>Tab3</h1>
<div><input value="test"></div>
<div><input></div>
<div><input></div>
</div>
</div>
</div>
</div>
これも tabindex をつけることで対処ではできるものの 非表示にするので display を none にするほうが簡単です
ですが アニメーション中に表示を残すならアニメーション後に display を none にするような制御が必要になって少し面倒です
こういうときでも inert 属性を使えば簡単です
<!DOCTYPE html>
<style>
.tab {
border: 1px solid gray;
width: 400px;
height: 400px;
display: flex;
flex-direction: column;
}
.header {
display: flex;
border-bottom: 1px solid gray;
flex: none;
}
.header [data-tab] {
padding: 5px 10px;
border-top: 3px solid transparent;
cursor: pointer;
}
.header [data-tab].selected {
border-top-color: red;
background: #f0f0f0;
}
.body {
flex: 1;
overflow: hidden;
position: relative;
}
.body .scroll {
display: flex;
position: absolute;
left: 0;
transition: left .3s ease 0s;
}
.body [data-tab] {
box-sizing: border-box;
width: 400px;
flex: none;
padding: 10px;
}
</style>
<script type="module">
document.querySelector(".header").onclick = (eve) => {
const elem = eve.target.closest("[data-tab]")
if (!elem) return
document.querySelector(".header .selected")?.classList.remove("selected")
elem.classList.add("selected")
const content = document.querySelector(`.body [data-tab="${elem.dataset.tab}"]`)
document.querySelector(".scroll").style.left = `-${content.offsetLeft}px`
for (const elem of document.querySelectorAll(".body [data-tab]")) {
elem.inert = elem !== content
}
}
for (const [index, elem] of document.querySelectorAll(".body [data-tab]").entries()) {
elem.inert = index !== 0
}
</script>
<div class="tab">
<div class="header">
<div data-tab="t1" class="selected">Tab1</div>
<div data-tab="t2">Tab2</div>
<div data-tab="t3">Tab3</div>
</div>
<div class="body">
<div class="scroll">
<div data-tab="t1">
<h1>Tab1</h1>
<div><input value="test"></div>
<div><input></div>
</div>
<div data-tab="t2">
<h1>Tab2</h1>
<div><input value="test"></div>
<div><input></div>
<div><input></div>
<div><input></div>
<div><input></div>
</div>
<div data-tab="t3">
<h1>Tab3</h1>
<div><input value="test"></div>
<div><input></div>
<div><input></div>
</div>
</div>
</div>
</div>
まとめて disabled
フォームの一部が別の入力内容に応じて有効無効が切り替わるとき まとめて disabled にしたいことがありますこのとき ひとつひとつに disabled を設定するのは面倒です
最近はあまり見かけない気がしますが 以前は上から透明要素を被せて操作できなくしたり pointer-events を none にして操作できなくしているのを見たことがあります
簡単な方法で入力できなくはしているものの この方法ではマウス操作でフォーカスできないだけです
キーボードでタブキーを押して フォーカスすれば変更できてしまいます
(例)
<!DOCTYPE html>
<style>
.block {
border: 1px solid gray;
padding: 20px;
width: 400px;
}
.s1 .block {
position: relative;
}
.s1 .cover {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: none;
background: #0002;
}
.s1 .block.disabled .cover {
display: block;
}
.s2 .block.disabled {
pointer-events: none;
background-color: #0002;
}
.s2 .block.disabled > * {
opacity: .5;
}
</style>
<script type="module">
for (const section of document.querySelectorAll("section")) {
section.addEventListener("change", (eve) => {
section.querySelector(".block").classList.toggle(
"disabled",
!eve.target.checked
)
})
}
</script>
<h1>Cover</h1>
<section class="s1">
<label><input type="checkbox" checked> 有効</label>
<div class="block">
<div class="cover"></div>
<div><input value="test"></div>
<div><input></div>
<div><input></div>
</div>
</section>
<h1>Pointer events: none</h1>
<section class="s2">
<label><input type="checkbox" checked> 有効</label>
<div class="block">
<div><input value="test"></div>
<div><input></div>
<div><input></div>
</div>
</section>
こういうことをしたい場合にも inert 属性が使えます
<!DOCTYPE html>
<style>
.block {
border: 1px solid gray;
padding: 20px;
width: 400px;
}
.block[inert] {
background: #0001;
}
.block[inert] > * {
opacity: .5;
}
</style>
<script type="module">
for (const section of document.querySelectorAll("section")) {
section.addEventListener("change", (eve) => {
section.querySelector(".block").inert = !eve.target.checked
})
}
</script>
<h1>inert</h1>
<section>
<label><input type="checkbox" checked> 有効</label>
<div class="block">
<div><input value="test"></div>
<div><input></div>
<div><input></div>
</div>
</section>
ただこのケースでは fieldset タグを使い disabled を設定するほうが適切かもしれません
inert 属性との違いは inert 属性だと完全に不活性化し マウス操作ではそこに何もないような動きにになります
テキストの選択もできません
それに対して disabled は編集不可なだけでテキストを選択してコピーしたりはできます
また inert 属性では disabled のスタイルが適用されません