摘要:而當(dāng)用戶使用鍵盤操作,焦點(diǎn)發(fā)生變化后,當(dāng)前焦點(diǎn)在哪一項(xiàng)上,必須給出明確的視覺提示,否則極容易帶來困難,甚至誤操作?;剀囨I按下時(shí),則會(huì)到相應(yīng)賬號(hào),或者取消操作關(guān)閉彈出層。再來看看組件庫的組件中的確認(rèn)消息。
問題提出本文作者:文藺
原文地址:http://www.wemlion.com/2016/a...
本文由 @文藺 創(chuàng)作,轉(zhuǎn)載請(qǐng)保留此聲明。
所有權(quán)利保留,請(qǐng)勿用于商業(yè)目的。
需要說明的是,題目中所說的 Modal,指的是所有由前端開發(fā)者自定義的對(duì)話框,如通常用到的 Alert、Prompt、Confirm 等等,經(jīng)常伴隨著一個(gè)半透明的灰黑色全局 mask。
事情源自某天使用某網(wǎng)站的頁面,出現(xiàn)一個(gè)自定義的 Comfirm,習(xí)慣性按下回車確認(rèn),等了很久也不見彈出層關(guān)閉。于是很絕望。繼而發(fā)現(xiàn),真不是這一個(gè)網(wǎng)站的問題。
看看一般瀏覽器原生的 alert、prompt、confirm 可以發(fā)現(xiàn),它們基本上都提供默認(rèn)操作項(xiàng)(eg.確認(rèn)/取消),通過回車鍵可以直接完成。傳統(tǒng)軟件如 PhotoShop 也是如此,否則真不知那些無需鼠標(biāo)的 PS 大神是怎樣練出來的。然而不少自定義的插件,卻并未提供這樣的功能,往往不得不將手挪到鼠標(biāo)或觸摸板上操作,極其不便。
主要存在兩個(gè)方面的問題,一是根本無法直接通過鍵盤操作對(duì)話框;二是雖然可以操作,但視覺提示并不明顯;三是整個(gè)應(yīng)用對(duì)焦點(diǎn)的控制不夠,容易導(dǎo)致成本可能極高誤操作。下面先簡單談?wù)勥@幾個(gè)問題,然后結(jié)合實(shí)際例子說明。
鍵盤、鼠標(biāo)轉(zhuǎn)換成本先談第一個(gè)問題。鍵盤、鼠標(biāo)之間的切換成本應(yīng)該是很高的,尤其是當(dāng)應(yīng)用響應(yīng)與用戶心理預(yù)期不符時(shí),造成的焦慮感或挫敗感很影響用戶體驗(yàn)。
因此在使用彈出 Modal 時(shí),需要根據(jù)業(yè)務(wù)場景,設(shè)置一個(gè)默認(rèn)選項(xiàng)(尤其是確認(rèn)、取消、提交等操作),用戶通過回車鍵或鼠標(biāo)點(diǎn)擊就可以輕松完成任務(wù)。不過,另外一種更好的方式是提供 Tab 和 (Shift + Tab) 兩種操作,通過鍵盤可以自由地在彈出層不同選項(xiàng)之間按照順序切換。
明確的焦點(diǎn)提示接著轉(zhuǎn)到第二個(gè),視覺提示的問題。沒有明確是視覺提示,會(huì)造成用戶的疑惑。
有默認(rèn)選項(xiàng)的時(shí)候,無論是通過閃動(dòng)的光標(biāo)、背景色的變化,還是邊框、outline 等形式,都需要明確提示用戶,Modal 彈出時(shí)默認(rèn)操作項(xiàng)是什么。而當(dāng)用戶使用鍵盤操作,焦點(diǎn)發(fā)生變化后,當(dāng)前焦點(diǎn)在哪一項(xiàng)上,必須給出明確的視覺提示,否則極容易帶來困難,甚至誤操作。
應(yīng)用對(duì)焦點(diǎn)的控制第三個(gè)問題實(shí)際是第二個(gè)問題的延續(xù)。前面提到焦點(diǎn)的視覺提示問題,其實(shí)還有一種可能性,可能導(dǎo)致嚴(yán)重后果,這就是用戶通過 Tab 操作,將焦點(diǎn)移動(dòng)到彈出層 mask 背后的應(yīng)用中。
這時(shí)候,一方面沒有給出清晰的提示,一方面沒有對(duì)焦點(diǎn)進(jìn)行很好的控制,萬一焦點(diǎn)在一個(gè)敏感位置(如一個(gè)外部跳轉(zhuǎn)鏈接),按下回車鍵,這時(shí)候頁面將刷新,用戶此前的數(shù)據(jù)面臨蕩然無存的境地。腦洞再大一點(diǎn),萬一 mask 背后獲得焦點(diǎn)的是巨額交易乃至核按鈕(233333),那么恭喜你,攻城獅生涯就此結(jié)束。
我們知道,當(dāng)網(wǎng)頁彈出原生 alert 時(shí),整個(gè)頁面其他部分處于不可操作狀態(tài)。使用類似的自定義組件替代這些功能時(shí),縱然我們不能阻塞頁面其他所有部分,但也得采取一定的控制,防止意外發(fā)生,最簡便的方法便是將 Tab 的控制權(quán)掌握在開發(fā)者手中。
當(dāng)然,相比于第三點(diǎn),前兩點(diǎn)應(yīng)該是必須項(xiàng),第三項(xiàng)可以算是加分項(xiàng),在一些普通應(yīng)用中不實(shí)現(xiàn)影響也不大。
舉栗子我們來看下一些實(shí)現(xiàn)。
首先是 Github 的 Fork。如圖所示:
首先 fork 操作不提供默認(rèn)選項(xiàng)。但使用 Tab 和 (Shift + Tab) 時(shí),焦點(diǎn)只會(huì)在整個(gè)彈出層中用紫色框線標(biāo)出的四個(gè)部分中依次切換?;剀囨I按下時(shí),則會(huì) fork 到相應(yīng)賬號(hào),或者取消操作、關(guān)閉彈出層。
接著看 SweetAlert,以第一個(gè) Confirm 為例:
Confirm 彈出后,默認(rèn)選項(xiàng)是右邊的 “Yes, delete it!”,點(diǎn)擊回車鍵,就會(huì)產(chǎn)生確認(rèn)反饋。通過 Tab 操作幾次發(fā)現(xiàn),整個(gè)頁面的焦點(diǎn),只能夠在圖中兩個(gè)按鈕之間切換。同時(shí)仔細(xì)觀察還能,獲得焦點(diǎn)狀態(tài)(:focus)的按鈕,會(huì)有一個(gè)不太容易察覺的 box-shadow。
再來看看 Vue 組件庫 Element 的 MessageBox 組件 中的 “確認(rèn)消息” Demo。
先說結(jié)果,嗯,有提供默認(rèn)選項(xiàng),切換到不同選項(xiàng)時(shí),也會(huì)有視覺提示(“取消”按鈕的顏色和邊框色會(huì)變,“確認(rèn)”按鈕背景亮度有明顯變化)。然而有兩點(diǎn)做得還不夠:第一,無法通過 Tab 輕易切換到“?”關(guān)閉按鈕;第二點(diǎn),也是最致命的是,根本就沒有獲取到 Tab 的控制權(quán),以至于多按幾次 Tab 然后回車,頁面跳到主頁,而彈框依然存在(如圖所示)。
和 Element 類似,Weui 中的 Dialog 也存在類似問題,但 Weui 主要面向移動(dòng)端,倒也情有可原。
如何實(shí)現(xiàn)控制一開始我想到了 tabIndex 這個(gè)屬性。但這貨控制起來,十分不方便,在本文場景中使用它,簡直是惹火燒身。
那么我能想到最簡單的實(shí)現(xiàn),莫過于手動(dòng)調(diào)用 focus 事件了。 Simple Demo 。代碼示例如下:
var $ = (sel) => document.querySelector(sel); $("#js-show-alert").addEventListener("click", () => { $("#js-prompt").style.display = "block"; $("#js-confirm").focus(); }); $("#js-cancel").addEventListener("click", () => { if (confirm("Are your sure?")) { $("#js-prompt").style.display = "none"; } }); $("#js-confirm").addEventListener("click", () => { $("#js-prompt").style.display = "none"; alert("Confirmed! The Prompt will disappear"); });
樣式方面,按鈕使用的是 button,整體并未 reset,所以按鈕獲取焦點(diǎn)時(shí),是有 outline 的。這也提醒我們,可以直接通過 :focus 偽類來實(shí)現(xiàn) tab 選中時(shí)的樣式,這樣做好懂又靠譜。
還存在另外一點(diǎn)問題,上面的代碼中,#js-confirm 元素是默認(rèn)獲取焦點(diǎn)的。那么,假如我們不需要任何默認(rèn)焦點(diǎn),而又需要用戶按下 Tab 時(shí),讓 #js-prompt 中的按鈕依次獲得焦點(diǎn)呢?
這里提一點(diǎn)不成熟的想法(23333333)。只要讓容器元素 #js-prompt 獲得焦點(diǎn)即可:$("#js-prompt").focus();。在控制臺(tái)上打印下 HTMLElement.prototype.focus,就會(huì)明白我的意思了。但這么做,可能是野路子(我還沒細(xì)看 HTML 中元素獲得焦點(diǎn)相關(guān)的規(guī)范)。
但是,上面 Demo 的實(shí)現(xiàn),沒有考慮到 Tab 控制權(quán)的接管。要接管 Tab,就得使用事件綁定來完成。下面看下 Github 是如何實(shí)現(xiàn)的。
如何取得控制權(quán)找到 Github 對(duì)應(yīng)的源碼,又通過開發(fā)者工具發(fā)現(xiàn)彈出層容器有個(gè)值為 facebox 的 id。
接下來,開始 Ctrl + F 大法。利用長久積累的火眼金睛,找到下面這樣兩段代碼:
就是它了。
先看第二段,整理后如下,重點(diǎn)直接劃在注釋中:
// 彈出層出現(xiàn) document.addEventListener("facebox:reveal", function() { var t = document.getElementById("facebox"); setTimeout(function() { e(t) }, 0); // 為 document 綁定 keydown 事件 // 事件回調(diào)函數(shù) n 就是第一張截圖的函數(shù) r(document).on("keydown", n); }); // 彈出層關(guān)閉后 document.addEventListener("facebox:afterClose", function() { // 移除出現(xiàn)時(shí)候?yàn)?document // 綁定的 keydown 事件 r(document).off("keydown", n); // 彈出中已經(jīng)獲得焦點(diǎn)的元素 // 強(qiáng)行失去焦點(diǎn) r("#facebox :focus").blur() }); // ....
再來看看函數(shù) n 做了什么。
function n(e) { var t = void 0; // hotkey 是自定義的 // 這里開始控制 tab 和 shift+tab 操作 if ("tab" === (t = e.hotkey) || "shift+tab" === t) { // 阻止默認(rèn)事件 e.preventDefault(); // 彈出層 var n = r("#facebox"), // 變量 i 存儲(chǔ)彈出層中可以獲得焦點(diǎn)的、可見的、可用的 // input, button, .btn, textarea 等元素集合 // filter 用到的函數(shù) o 用于過濾不可見的相關(guān)元素 // o = require("github/visible")["default"] i = r(Array.from(n.find("input, button, .btn, textarea")).filter(o)).filter(function() { return !this.disabled }), // tab 方向 s = "shift+tab" === e.hotkey ? -1 : 1, // i 集合中當(dāng)前獲得焦點(diǎn)的元素的 index a = i.index(i.filter(":focus")), // i 集合中下一個(gè)獲得焦點(diǎn)的元素的位置 u = a + s; // 如果下一個(gè)獲得焦點(diǎn)的位置超出 i 集合范圍 // 或者當(dāng)前沒有任何元素獲得焦點(diǎn)、并且使用的是向前的 tab 鍵 // 則將焦點(diǎn)置于 i 集合中的第一個(gè)元素上 if (u === i.length || -1 === a && "tab" === e.hotkey) { i.first().focus() } // 當(dāng)前沒有任何元素獲得焦點(diǎn) // 并且使用 shift+tab 向前 // 則將焦點(diǎn)置于 i 集合中最后一個(gè)元素 else if(-1 === a){ i.last().focus(); } // 正常行為 // 將焦點(diǎn)置于下一個(gè)元素 else { i.get(u).focus(); } } }
好了,本文也就結(jié)束了。
嗯,今天(2016-12-04)我想起來那個(gè)完全無法使用的例子了:
本文作者:文藺
原文地址:http://www.wemlion.com/2016/a...
轉(zhuǎn)載請(qǐng)注明來源
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/91228.html
摘要:尤其是遇到二次確認(rèn)等場景因此,打算從頭整理移動(dòng)彈窗的基礎(chǔ)知識(shí),以彈窗體系為切入點(diǎn),從定義出發(fā),對(duì)移動(dòng)彈窗進(jìn)行分類,然后分別分析每一類彈窗的應(yīng)用場景,以及在使用過程中需要注意的點(diǎn)。 摘要: 最為常見的【彈窗】反而是最捉摸不定的東西。各種類型的彈窗傻傻分不清楚,不知道在什么場景下應(yīng)該用哪種彈窗。尤其是遇到二次確認(rèn)等場景…… 因此,打算從頭整理移動(dòng)彈窗的基礎(chǔ)知識(shí),以iOS彈窗體系為切入點(diǎn),從...
摘要:其他的項(xiàng)目使用了拼裝樣式驗(yàn)證傳入的屬性是否是函數(shù)驗(yàn)證父組件傳入的數(shù)據(jù)格式是否正確五參考文獻(xiàn)談?wù)劦氖褂檬褂脠鼍? 仿 taro-ui 實(shí)現(xiàn) modal 組件 小程序組件. 簡介: 項(xiàng)目中使用到彈窗類的組件,重新制造了一個(gè)輪子. 源碼地址: https://github.com/xiangxiong... 編寫完modal組件總計(jì)花了28分鐘. 效果圖: showImg(htt...
摘要:其他的項(xiàng)目使用了拼裝樣式驗(yàn)證傳入的屬性是否是函數(shù)驗(yàn)證父組件傳入的數(shù)據(jù)格式是否正確五參考文獻(xiàn)談?wù)劦氖褂檬褂脠鼍? 仿 taro-ui 實(shí)現(xiàn) modal 組件 小程序組件. 簡介: 項(xiàng)目中使用到彈窗類的組件,重新制造了一個(gè)輪子. 源碼地址: https://github.com/xiangxiong... 編寫完modal組件總計(jì)花了28分鐘. 效果圖: showImg(htt...
閱讀 3429·2023-04-25 14:07
閱讀 3505·2021-09-28 09:35
閱讀 2122·2019-08-30 15:55
閱讀 1435·2019-08-30 13:48
閱讀 2528·2019-08-30 13:16
閱讀 3229·2019-08-30 12:54
閱讀 3268·2019-08-30 11:19
閱讀 1911·2019-08-29 17:17