這個(gè)問(wèn)題我大概在一年多以前在某個(gè)用到VML的頁(yè)面中(當(dāng)時(shí)倒是記錄了VML的一個(gè)嚴(yán)重問(wèn)題)首次發(fā)現(xiàn)了這個(gè)Bug。經(jīng)過(guò)一番狗狗之后,也未發(fā)現(xiàn)有同樣的報(bào)告。后來(lái)我又逐漸在幾種其他非VML的情形下重現(xiàn)了這個(gè)奇異的Bug。經(jīng)過(guò)一番探究,我大致推斷出了這個(gè)bug的原因。不過(guò)我一直沒(méi)有公開(kāi)發(fā)布過(guò)這個(gè)有趣的問(wèn)題,只是跟少數(shù)同事提到過(guò)它。這個(gè)bug有個(gè)有趣的特點(diǎn),就是西方人通常不會(huì)碰到這個(gè)bug。
最近,真懶同學(xué)(realazy)在《認(rèn)識(shí)延遲時(shí)間為 0 的 setTimeout》一文中舉例說(shuō)明setTimeout的用途。代碼大意如下:
- $('myButton').onmousedown = function () {
- var input = document.createElement('input');
- input.type = 'text';
- $('myDiv').appendChild(input);
- input.focus();
- }
在IE中,新創(chuàng)建的input沒(méi)有如預(yù)期的獲得焦點(diǎn)。
如果把input.focus()放在一個(gè)setTimeout中延時(shí)執(zhí)行,則就可以獲得焦點(diǎn)。
這個(gè)例子本身其實(shí)并不能證明realazy想要說(shuō)明的觀點(diǎn),因?yàn)樗恍⌒呐龅搅艘粋€(gè)IE的微妙bug。在留言中,Lunatic Sun倒是敏銳的判斷出這是IE的bug,只是這個(gè)bug的本質(zhì)不是那么容易認(rèn)識(shí)到,它其實(shí)并不是onmousedown本身的bug。
實(shí)際上,這是IE的focus機(jī)制的bug。
IE中的所有元素其實(shí)并不是被憑空繪制出來(lái)的,而是統(tǒng)統(tǒng)基于已有的Windows控件之上。除了典型的按鈕、下拉菜單等,普通的div其實(shí)也是一個(gè)textbox控件。
所以IE的HTML focus等實(shí)際是被轉(zhuǎn)換為windows控件的focus,于是在IE中存在兩種不同層次的focus機(jī)制。理想上,HTML的focus應(yīng)該被同步轉(zhuǎn)換為windows控件的focus,然而IE可憐的代碼導(dǎo)致這種轉(zhuǎn)換存在許多bug。我們經(jīng)常遇到的焦點(diǎn)虛線框丟失的問(wèn)題其實(shí)就是一個(gè)例子。
實(shí)際上,在上面的例子中,表面上input沒(méi)有得到焦點(diǎn),但是其實(shí)調(diào)用focus()之后,HTML focus確實(shí)已經(jīng)到了新生成的input中,這一點(diǎn)你可以通過(guò)document.activeElement來(lái)驗(yàn)證,你也可以按tab和shift-tab觀察焦點(diǎn)的切換來(lái)證明這一點(diǎn)。然而,由于mousedown事件默認(rèn)會(huì)獲得控件焦點(diǎn),所以windows控件focus就跑回了你的按鈕上面了。這里出現(xiàn)了windows focus機(jī)制和html focus機(jī)制的脫節(jié)。顯然IE在focus上的同步代碼實(shí)在是太脆弱了。
事實(shí)上,IE對(duì)焦點(diǎn)的控制似乎本來(lái)就不和邏輯。所有的hasLayout元素都能獲得焦點(diǎn)!結(jié)果一個(gè)頁(yè)面上大部分區(qū)域在mousedown之后焦點(diǎn)就不知跑到哪個(gè)元素上了——這顯然不是我們想要的行為——合乎HTML規(guī)范邏輯的行為應(yīng)該是只有交互控件,如表單控件和A元素等,才能獲得焦點(diǎn)。這導(dǎo)致一個(gè)典型的用戶(hù)體驗(yàn)問(wèn)題:在一個(gè)限制高度的可卷動(dòng)區(qū)域中(例如一個(gè)長(zhǎng)表單),拖動(dòng)scrollbar,控件焦點(diǎn)就丟失——實(shí)際焦點(diǎn)跑到scrollbar所在的元素(例如form元素)上了。最嚴(yán)重的是,body元素一般總會(huì)有scrollbar!為了緩解這個(gè)問(wèn)題,微軟為body元素打了補(bǔ)丁,使得body上的scrollbar不會(huì)搶走焦點(diǎn)。然而IE這個(gè)patch打得實(shí)在是太爛了,在標(biāo)準(zhǔn)模式下,canvas從body變成了html元素,所以頁(yè)面scrollbar就到了html元素上,結(jié)果bug又回來(lái)了!
撇開(kāi)搶焦點(diǎn)問(wèn)題,我們回到前面的話(huà)題。
既然html focus還是在input元素上,那么當(dāng)時(shí)windows控件焦點(diǎn)到底跑哪里去了?實(shí)際上這個(gè)焦點(diǎn)跑到了mousedown所發(fā)生的對(duì)象上。比如如果是一個(gè)input按鈕,焦點(diǎn)就會(huì)在該input按鈕實(shí)際對(duì)應(yīng)的windows的Button控件上。不過(guò)button元素的實(shí)現(xiàn)和一般的input不同,所以button元素上的mousedown之后,windows控件焦點(diǎn)實(shí)際上會(huì)跑到button元素外層的那個(gè)元素所對(duì)應(yīng)的windows控件(通常是TextBox控件)中。
如何證明這一點(diǎn)?我過(guò)去用過(guò)一個(gè)調(diào)試工具可以顯示出每個(gè)html控件實(shí)際的windows控件,也能查看實(shí)際的windows系統(tǒng)焦點(diǎn)。不過(guò)現(xiàn)在想不起來(lái)那個(gè)工具的名稱(chēng)了。搞笑的是,此處還會(huì)出現(xiàn)一個(gè)非常orz的癥狀——也就是本文標(biāo)題所稱(chēng)的“西方人通常發(fā)現(xiàn)不了的一個(gè)IE的bug”——可以證明這一點(diǎn)。
focus問(wèn)題 + 西方人通常發(fā)現(xiàn)不了。各位是否已經(jīng)猜到了呢?大家不妨用realazy的那個(gè)頁(yè)面中的第一個(gè)按鈕來(lái)直接實(shí)驗(yàn)一把。
我會(huì)在下篇blog中繼續(xù)聊這個(gè)話(huà)題