你可能不知道的dialog彈窗

XboxYan發表於2023-01-30
歡迎關注我的公眾號:前端偵探

想必大家都知道 HTML5 中有 dialog 這樣一個標籤,顧名思義,就是“彈窗”。除了有良好的語義外,隨著瀏覽器的不斷更新迭代,還出現了許多你可能不知道的特性,快速瞭解一下吧~

image.png

一、開啟和關閉方法

首先,在不查閱任何官方文件的情況下,先做一個選擇題目

請問:下面哪組方法(開啟/關閉)是合法的?

image.png

思考 10 秒...

?

?

?

?

正確答案是 C,也就是

// 開啟彈窗
dialog.show()
// 開啟關閉
dialog.close()

很多同學可能會覺得是 A 或者 B,但是官方文件上確是 C,確實是一個令人迷惑的方法。至於原因,有解釋說,dialog是存在於頁面上的元素,所以開啟用show,而關閉表示中斷彈窗內的行為,所以用close,只能說有聯絡,但比較牽強,或許就是規範設計疏忽了,大家記住就好。

還有一個,如果需要彈窗預設顯示,大家先猜猜看?

image.png

再來思考 10 秒...

?

?

?

?

正確答案是 B,也就是

<dialog open></dialog>

?是不是又和上面的show()對不上了?

除了dialog元素,details元素的開啟屬性也是open

二、表單提交特性

彈窗很多時候的作用都是充當表單輸入。

比如一個彈窗中需要有一個關閉或者取消按鈕

<dialog>
  <h3>歡迎關注前端偵探</h3>
  <button>關閉</button>
</dialog>

通常情況下,我們可能需要用到前面提到的dialog.close()方法來主動關閉彈窗。

其實,還可以透過表單特性實現這樣一個效果,具體做法是:

  1. dialog中巢狀一層form
  2. form新增一個method=dialog屬性

如下

<dialog>
  <form method="dialog">
    <h3>歡迎關注前端偵探</h3>
    <button>關閉</button>
  </form>
</dialog>

這樣就可以不用繫結額外的事件來實現關閉功能了,效果如下

Kapture 2023-01-14 at 12.52.33.gif

其實原因就在於表單上的method=dialog屬性,如果表單在dialog元素中,提交時就會觸發關閉對話方塊

https://developer.mozilla.org...

預設情況下,表單裡的button都會觸發提交,等同於type=submit,但如果設定了type=button或者type=reset,則不會觸發表單提交,自然也不會關閉彈窗

<dialog>
  <form method="dialog">
    <h3>歡迎關注前端偵探</h3>
    <button>關閉</button>
    <button type="button">不會關閉</button>
  </form>
</dialog>

效果如下

Kapture 2023-01-14 at 13.10.53.gif

另外還有一個小特性,dialog還有一個returnValue屬性,可以返回表單中提交按鈕的value

<dialog>
  <form method="dialog">
    <h3>歡迎關注前端偵探</h3>
    <button value="AAA">提交</button>
  </form>
</dialog>

在提交後可以列印彈窗的returnValue,如下

image.png

不過暫時還沒有想到很有用的場景

三、模態視窗特性

彈窗除了可以透過dialog.show()開啟之外,還提供了一個模態視窗,方法是

dailog.showModal()

透過這個方法開啟的彈窗,會自帶一個半透明的背景,並且完全水平垂直居中

Kapture 2023-01-14 at 13.28.37.gif

這個半透明的背景並不是普通的元素,而是一個叫做::backdrop的偽元素控制的,並且目前只有透過dailog.showModal()這個方法才能生成

要自定義背景也很容易

dialog::backdrop {
  background: rgba(255,0,0,.25);
}

這樣就變成了半透明紅色

image.png

如果希望開啟彈窗有動畫,可以自定義預設樣式,透過visibility的方式實現隱藏顯示

dialog{
  position: fixed;
  margin: auto;
  inset: 0;
  width: fit-content;
  height: fit-content;
  display: block;
  visibility: hidden;
  opacity: 0;
  transform: translateY(100px);
  transition: .2s;
}
dialog[open] {
  visibility: visible;
  opacity: 1;
  transform: translateY(0);
}

效果如下

Kapture 2023-01-14 at 13.45.13.gif

其實這裡我是不太建議動畫的,彈窗就是要反應快速,加了動畫反而會拖累整體。

另外,還可以透過:modal偽類來區分是普通彈窗還是模態彈窗

dialog:modal{
  /*模態彈窗*/
}
dialog:not(:modal){
  /*普通彈窗*/
}

這樣可以更多進行自定義行為

模態彈窗還有一個非常省心的點,就是無需關注層級。它有一套非常直觀的規則,哪個後開啟,哪個層級最高,比如這樣兩個彈窗

<dialog id="dialog2">
  ...
</dialog>
<dialog id="dialog1">
  ...
  <button type="button">開啟彈窗2</button>
  ...
</dialog>

在不指定層級的情況下,肯定是後面的層級更高,但如果是透過dialog.showModal開啟,就非常直觀了,後開啟的彈窗肯定會覆蓋前面的,效果如下

Kapture 2023-01-14 at 14.05.04.gif

說到這裡,我又想到了一些UI元件庫裡大到可怕的z-index,大機率就是透過計算得出的

四、焦點隔離特性

除了上面一些比較直觀的特性外,還有一些可能會忽略的,比如焦點的控制。

預設情況下,開啟彈窗後會自動聚焦到彈窗內的第一個可聚焦元素上

Kapture 2023-01-14 at 14.34.43.gif

還可以有input輸入框

<dialog>
  <form method="dialog">
    <h3>歡迎關注前端偵探</h3>
    <input name="txt">
    <button>關閉</button>
  </form>
</dialog>

開啟彈窗後可以直接輸入,無需額外操作,簡直不要太方便

Kapture 2023-01-14 at 14.36.37.gif

注意,注意,注意:經測試發現,如果新增了開啟動畫,聚焦特性就會失效

當然,這些只是小兒科,一點點額外的 JS 也能解決,下面介紹一個系統級別的焦點隔離特性。什麼意思呢?就是說在開啟彈窗後,彈窗就成了一個獨立的載體,焦點只能在這個範圍內移動,也就是說,無論tab鍵如何切換,焦點不會跑到彈窗外面去,下面是一個對比效果

  1. 普通彈窗效果

Kapture 2023-01-14 at 14.45.00.gif

  1. 模態彈窗效果

Kapture 2023-01-14 at 14.46.49.gif

這個效果其實是和新出的inert屬性作用比較類似的,但是要比inert出現的更早,也可以說是透過inert屬性將這種隔離特性通用化了,讓平民老百姓也可以享受這種高階特性。有興趣的可以訪問我之前的這篇文章:快速瞭解 inert 屬性

五、頂層特性 top-layer

最後介紹一個即便是 JS 也無法模擬的系統級新特性,top-layer

不知道大家有沒有遇到這樣的問題,有些彈窗由於業務需要,不得已寫在了某些容器下面,即便是fixed定位,也會有失效的時候,比如下面這個例子,父容器如果有transform相關屬性並且超出隱藏,就會出現這樣被裁剪的情形

image.png

上面這個例子來源於 xy-ui 中的 dialog 元件,後續最佳化,敬請期待~

在以前,或者說很多框架中,都會想辦法把彈窗放到最外層的 body下,這樣就不受影響了,比如下面是vue3中的處理方式

<div>
  <Teleport to="body"> <!--將子內容傳送到body下-->
      <dialog></dialog>
  </Teleport>
</div>

但現在,有了全新的 top-layer ,一切都好辦了,比如下面是一個透過dialog.showModal()開啟的彈窗

image.png

你會發現,雖然dialog仍然在原來位置上,但真正渲染到了一個#top-layer的層級上,這個層級非常特殊,已經超越了html文件流,可以說是獨一檔的存在,這樣,無論的dialog在什麼位置,最後渲染的地方其實都在#top-layer層級上,自然也不會被父容器裁剪被隱藏了,示意如下

image.png

是不是和現代框架有些許類似呢?下面是一個彈窗裡巢狀另外一個彈窗

<dialog id="dialog1">
  ...
  <button type="button" >開啟彈窗2</button>
  <dialog id="dialog2">
        ...
  </dialog>
</dialog>

效果如下

image.png

其實這也是後開啟的彈窗永遠要高於之前彈窗的原因,#top-layer是動態建立的,只有開啟的彈窗才會渲染到該層級之下。

另外,經測試發現,firefox 以及 safari 雖然在開發者工具上看不到#top-layer,但是dialog表現基本幾乎一致,應該是已經成為了標準規範,日後可以放心使用。

六、dom 其實也是在不斷髮展的

dialog其實我在兩三年之前就有研究過,那時還沒有這麼多新特性,也有可能是研究不精,後來就擱置了,然後最近在專案中用了dialog,無意中又發現了很多有意思的新特性,這意味著,dom 其實也是在不斷髮展的,有必要把以前已經用過的再翻出來過一遍,說不定還能發現意想不到的結果,下面是要點總結

  1. dialog 的開啟和關閉方法很迷惑,分別是show() / close()
  2. dialog中表單元素在新增method=dialog屬性之後,只要觸發表單提交就會自動關閉彈窗,無需額外 js
  3. 透過showModal()可以開啟模態彈窗,並且後開啟的彈窗永遠比先開啟的彈窗層級要高,無需手動計算層級
  4. 開啟彈窗會自動聚焦到彈窗內的第一個可聚焦元素,方便快速輸入
  5. 模態彈窗的焦點不會聚焦到彈窗外部,這和inert特性比較類似
  6. 模態彈窗其實是渲染到了html文件流之外,一個叫#top-layer的層級上,因此不會受到原父容器的影響

很多同學往往過於關注 JavaScript和現代流行框架,而忽視了原生 dom 的發展,但原生 dom 帶來的很多新特性往往是系統級別的,有可能你辛辛苦苦實現的一個功能,原生一個屬性就搞定,而且效果更好,所以請不要忘了原生。最後,如果覺得還不錯,對你有幫助的話,歡迎點贊、收藏、轉發❤❤❤

歡迎關注我的公眾號:前端偵探

相關文章