Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
能每一個(gè)前端工程師都想要理解瀏覽器的工作原理。
我們希望知道從在瀏覽器地址欄中輸入 url 到頁(yè)面展現(xiàn)的短短幾秒內(nèi)瀏覽器究竟做了什么;
我們希望了解平時(shí)常常聽(tīng)說(shuō)的各種代碼優(yōu)化方案是究竟為什么能起到優(yōu)化的作用;
我們希望更細(xì)化的了解瀏覽器的渲染流程。
瀏覽器的多進(jìn)程架構(gòu)
一個(gè)好的程序常常被劃分為幾個(gè)相互獨(dú)立又彼此配合的模塊,瀏覽器也是如此,以 Chrome 為例,它由多個(gè)進(jìn)程組成,每個(gè)進(jìn)程都有自己核心的職責(zé),它們相互配合完成瀏覽器的整體功能,每個(gè)進(jìn)程中又包含多個(gè)線程,一個(gè)進(jìn)程內(nèi)的多個(gè)線程也會(huì)協(xié)同工作,配合完成所在進(jìn)程的職責(zé)。
對(duì)一些前端開(kāi)發(fā)同學(xué)來(lái)說(shuō),進(jìn)程和線程的概念可能會(huì)有些模糊,為了更好的理解瀏覽器的多進(jìn)程架構(gòu),這里我們簡(jiǎn)單討論一下進(jìn)程和線程。
進(jìn)程(process)和線程(thread)
進(jìn)程就像是一個(gè)有邊界的生產(chǎn)廠間,而線程就像是廠間內(nèi)的一個(gè)個(gè)員工,可以自己做自己的事情,也可以相互配合做同一件事情。
當(dāng)我們啟動(dòng)一個(gè)應(yīng)用,計(jì)算機(jī)會(huì)創(chuàng)建一個(gè)進(jìn)程,操作系統(tǒng)會(huì)為進(jìn)程分配一部分內(nèi)存,應(yīng)用的所有狀態(tài)都會(huì)保存在這塊內(nèi)存中,應(yīng)用也許還會(huì)創(chuàng)建多個(gè)線程來(lái)輔助工作,這些線程可以共享這部分內(nèi)存中的數(shù)據(jù)。如果應(yīng)用關(guān)閉,進(jìn)程會(huì)被終結(jié),操作系統(tǒng)會(huì)釋放相關(guān)內(nèi)存。更生動(dòng)的示意圖如下:
一個(gè)進(jìn)程還可以要求操作系統(tǒng)生成另一個(gè)進(jìn)程來(lái)執(zhí)行不同的任務(wù),系統(tǒng)會(huì)為新的進(jìn)程分配獨(dú)立的內(nèi)存,兩個(gè)進(jìn)程之間可以使用 IPC (Inter Process Communication)進(jìn)行通信。很多應(yīng)用都會(huì)采用這樣的設(shè)計(jì),如果一個(gè)工作進(jìn)程反應(yīng)遲鈍,重啟這個(gè)進(jìn)程不會(huì)影響應(yīng)用其它進(jìn)程的工作。
如果對(duì)進(jìn)程及線程的理解還存在疑惑,可以參考下述文章:
http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html
瀏覽器的架構(gòu)
有了上面的知識(shí)做鋪墊,我們可以更合理的討論瀏覽器的架構(gòu)了,其實(shí)如果要開(kāi)發(fā)一個(gè)瀏覽器,它可以是單進(jìn)程多線程的應(yīng)用,也可以是使用 IPC 通信的多進(jìn)程應(yīng)用。
不同瀏覽器的架構(gòu)模型
不同瀏覽器采用了不同的架構(gòu)模式,這里并不存在標(biāo)準(zhǔn),本文以 Chrome 為例進(jìn)行說(shuō)明 :
Chrome 采用多進(jìn)程架構(gòu),其頂層存在一個(gè) Browser process 用以協(xié)調(diào)瀏覽器的其它進(jìn)程。
Chrome 的不同進(jìn)程
具體說(shuō)來(lái),Chrome 的主要進(jìn)程及其職責(zé)如下:
Browser Process:
Renderer Process:
Plugin Process:
不同進(jìn)程負(fù)責(zé)的瀏覽器區(qū)域示意圖
Chrome 還為我們提供了「任務(wù)管理器」,供我們方便的查看當(dāng)前瀏覽器中運(yùn)行的所有進(jìn)程及每個(gè)進(jìn)程占用的系統(tǒng)資源,右鍵單擊還可以查看更多類別信息。
通過(guò)「頁(yè)面右上角的三個(gè)點(diǎn)點(diǎn)點(diǎn) — 更多工具 — 任務(wù)管理器」即可打開(kāi)相關(guān)面板。
Chrome 多進(jìn)程架構(gòu)的優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
某一渲染進(jìn)程出問(wèn)題不會(huì)影響其他進(jìn)程
更為安全,在系統(tǒng)層面上限定了不同進(jìn)程的權(quán)限
缺點(diǎn)
由于不同進(jìn)程間的內(nèi)存不共享,不同進(jìn)程的內(nèi)存常常需要包含相同的內(nèi)容。
為了節(jié)省內(nèi)存,Chrome 限制了最多的進(jìn)程數(shù),最大進(jìn)程數(shù)量由設(shè)備的內(nèi)存和 CPU 能力決定,當(dāng)達(dá)到這一限制時(shí),新打開(kāi)的 Tab 會(huì)共用之前同一個(gè)站點(diǎn)的渲染進(jìn)程。
測(cè)試了一下在 Chrome 中打開(kāi)不斷打開(kāi)知乎首頁(yè),在 Mac i5 8g 上可以啟動(dòng)四十多個(gè)渲染進(jìn)程,之后新打開(kāi) tab 會(huì)合并到已有的渲染進(jìn)程中。
Chrome 把瀏覽器不同程序的功能看做服務(wù),這些服務(wù)可以方便的分割為不同的進(jìn)程或者合并為一個(gè)進(jìn)程。以 Broswer Process 為例,如果 Chrome 運(yùn)行在強(qiáng)大的硬件上,它會(huì)分割不同的服務(wù)到不同的進(jìn)程,這樣 Chrome 整體的運(yùn)行會(huì)更加穩(wěn)定,但是如果 Chrome 運(yùn)行在資源貧瘠的設(shè)備上,這些服務(wù)又會(huì)合并到同一個(gè)進(jìn)程中運(yùn)行,這樣可以節(jié)省內(nèi)存,示意圖如下。
iframe 的渲染 – Site Isolation
在上面的進(jìn)程圖中我們還可以看到一些進(jìn)程下還存在著 Subframe,這就是 Site Isolation 機(jī)制作用的結(jié)果。
Site Isolation 機(jī)制從 Chrome 67 開(kāi)始默認(rèn)啟用。這種機(jī)制允許在同一個(gè) Tab 下的跨站 iframe 使用單獨(dú)的進(jìn)程來(lái)渲染,這樣會(huì)更為安全。
iframe 會(huì)采用不同的渲染進(jìn)程
Site Isolation 被大家看做里程碑式的功能, 其成功實(shí)現(xiàn)是多年工程努力的結(jié)果。Site Isolation 不是簡(jiǎn)單的疊加多個(gè)進(jìn)程。這種機(jī)制在底層改變了 iframe 之間通信的方法,Chrome 的其它功能都需要做對(duì)應(yīng)的調(diào)整,比如說(shuō) devtools 需要相應(yīng)的支持,甚至 Ctrl + F 也需要支持。關(guān)于 Site Isolation 的更多內(nèi)容可參考下述鏈接:
https://developers.google.com/web/updates/2018/07/site-isolation
介紹完了瀏覽器的基本架構(gòu)模式,接下來(lái)我們看看一個(gè)常見(jiàn)的導(dǎo)航過(guò)程對(duì)瀏覽器來(lái)說(shuō)究竟發(fā)生了什么。
導(dǎo)航過(guò)程發(fā)生了什么
也許大多數(shù)人使用 Chrome 最多的場(chǎng)景就是在地址欄輸入關(guān)鍵字進(jìn)行搜索或者輸入地址導(dǎo)航到某個(gè)網(wǎng)站,我們來(lái)看看瀏覽器是怎么看待這個(gè)過(guò)程的。
我們知道瀏覽器 Tab 外的工作主要由 Browser Process 掌控,Browser Process 又對(duì)這些工作進(jìn)一步劃分,使用不同線程進(jìn)行處理:
瀏覽器主進(jìn)程中的不同線程
回到我們的問(wèn)題,當(dāng)我們?cè)跒g覽器地址欄中輸入文字,并點(diǎn)擊回車獲得頁(yè)面內(nèi)容的過(guò)程在瀏覽器看來(lái)可以分為以下幾步:
1. 處理輸入
UI thread 需要判斷用戶輸入的是 URL 還是 query;
2. 開(kāi)始導(dǎo)航
當(dāng)用戶點(diǎn)擊回車鍵,UI thread 通知 network thread 獲取網(wǎng)頁(yè)內(nèi)容,并控制 tab 上的 spinner 展現(xiàn),表示正在加載中。
network thread 會(huì)執(zhí)行 DNS 查詢,隨后為請(qǐng)求建立 TLS 連接。
UI thread 通知 Network thread 加載相關(guān)信息
如果 network thread 接收到了重定向請(qǐng)求頭如 301,network thread 會(huì)通知 UI thread 服務(wù)器要求重定向,之后,另外一個(gè) URL 請(qǐng)求會(huì)被觸發(fā)。
3. 讀取響應(yīng)
當(dāng)請(qǐng)求響應(yīng)返回的時(shí)候,network thread 會(huì)依據(jù) Content-Type 及 MIME Type sniffing 判斷響應(yīng)內(nèi)容的格式。
判斷響應(yīng)內(nèi)容的格式
如果響應(yīng)內(nèi)容的格式是 HTML ,下一步將會(huì)把這些數(shù)據(jù)傳遞給 renderer process,如果是 zip 文件或者其它文件,會(huì)把相關(guān)數(shù)據(jù)傳輸給下載管理器。
Safe Browsing 檢查也會(huì)在此時(shí)觸發(fā),如果域名或者請(qǐng)求內(nèi)容匹配到已知的惡意站點(diǎn),network thread 會(huì)展示一個(gè)警告頁(yè)。此外 CORB 檢測(cè)也會(huì)觸發(fā)確保敏感數(shù)據(jù)不會(huì)被傳遞給渲染進(jìn)程。
4. 查找渲染進(jìn)程
當(dāng)上述所有檢查完成,network thread 確信瀏覽器可以導(dǎo)航到請(qǐng)求網(wǎng)頁(yè),network thread 會(huì)通知 UI thread 數(shù)據(jù)已經(jīng)準(zhǔn)備好,UI thread 會(huì)查找到一個(gè) renderer process 進(jìn)行網(wǎng)頁(yè)的渲染。
收到 Network thread 返回的數(shù)據(jù)后,UI thread 查找相關(guān)的渲染進(jìn)程
由于網(wǎng)絡(luò)請(qǐng)求獲取響應(yīng)需要時(shí)間,這里其實(shí)還存在著一個(gè)加速方案。當(dāng) UI thread 發(fā)送 URL 請(qǐng)求給 network thread 時(shí),瀏覽器其實(shí)已經(jīng)知道了將要導(dǎo)航到那個(gè)站點(diǎn)。UI thread 會(huì)并行的預(yù)先查找和啟動(dòng)一個(gè)渲染進(jìn)程,如果一切正常,當(dāng) network thread 接收到數(shù)據(jù)時(shí),渲染進(jìn)程已經(jīng)準(zhǔn)備就緒了,但是如果遇到重定向,準(zhǔn)備好的渲染進(jìn)程也許就不可用了,這時(shí)候就需要重啟一個(gè)新的渲染進(jìn)程。
5. 確認(rèn)導(dǎo)航
進(jìn)過(guò)了上述過(guò)程,數(shù)據(jù)以及渲染進(jìn)程都可用了, Browser Process 會(huì)給 renderer process 發(fā)送 IPC 消息來(lái)確認(rèn)導(dǎo)航,一旦 Browser Process 收到 renderer process 的渲染確認(rèn)消息,導(dǎo)航過(guò)程結(jié)束,頁(yè)面加載過(guò)程開(kāi)始。
此時(shí),地址欄會(huì)更新,展示出新頁(yè)面的網(wǎng)頁(yè)信息。history tab 會(huì)更新,可通過(guò)返回鍵返回導(dǎo)航來(lái)的頁(yè)面,為了讓關(guān)閉 tab 或者窗口后便于恢復(fù),這些信息會(huì)存放在硬盤(pán)中。
6. 額外的步驟
一旦導(dǎo)航被確認(rèn),renderer process 會(huì)使用相關(guān)的資源渲染頁(yè)面,下文中我們將重點(diǎn)介紹渲染流程。當(dāng) renderer process 渲染結(jié)束(渲染結(jié)束意味著該頁(yè)面內(nèi)的所有的頁(yè)面,包括所有 iframe 都觸發(fā)了 onload 時(shí)),會(huì)發(fā)送 IPC 信號(hào)到 Browser process, UI thread 會(huì)停止展示 tab 中的 spinner。
Renderer Process 發(fā)送 IPC 消息通知 browser process 頁(yè)面已經(jīng)加載完成。
當(dāng)然上面的流程只是網(wǎng)頁(yè)首幀渲染完成,在此之后,客戶端依舊可下載額外的資源渲染出新的視圖。
在這里我們可以明確一點(diǎn),所有的 JS 代碼其實(shí)都由 renderer Process 控制的,所以在你瀏覽網(wǎng)頁(yè)內(nèi)容的過(guò)程大部分時(shí)候不會(huì)涉及到其它的進(jìn)程。不過(guò)也許你也曾經(jīng)監(jiān)聽(tīng)過(guò) beforeunload 事件,這個(gè)事件再次涉及到 Browser Process 和 renderer Process 的交互,當(dāng)當(dāng)前頁(yè)面關(guān)閉時(shí)(關(guān)閉 Tab ,刷新等等),Browser Process 需要通知 renderer Process 進(jìn)行相關(guān)的檢查,對(duì)相關(guān)事件進(jìn)行處理。
瀏覽器進(jìn)程發(fā)送 IPC 消息給渲染進(jìn)程,通知要離開(kāi)當(dāng)前網(wǎng)站了
如果導(dǎo)航由 renderer process 觸發(fā)(比如在用戶點(diǎn)擊某鏈接,或者 JS 執(zhí)行 window.location = "http://newsite.com" ) renderer process 會(huì)首先檢查是否有 beforeunload 事件處理器,導(dǎo)航請(qǐng)求由 renderer process 傳遞給 Browser process。
如果導(dǎo)航到新的網(wǎng)站,會(huì)啟用一個(gè)新的 render process 來(lái)處理新頁(yè)面的渲染,老的進(jìn)程會(huì)留下來(lái)處理類似 unload 等事件。
關(guān)于頁(yè)面的生命周期,更多內(nèi)容可參考 Page Lifecycle API 。
瀏覽器進(jìn)程發(fā)送 IPC 消息到新的渲染進(jìn)程通知渲染新的頁(yè)面,同時(shí)通知舊的渲染進(jìn)程卸載。
除了上述流程,有些頁(yè)面還擁有 Service Worker (服務(wù)工作線程),Service Worker 讓開(kāi)發(fā)者對(duì)本地緩存及判斷何時(shí)從網(wǎng)絡(luò)上獲取信息有了更多的控制權(quán),如果 Service Worker 被設(shè)置為從本地 cache 中加載數(shù)據(jù),那么就沒(méi)有必要從網(wǎng)上獲取更多數(shù)據(jù)了。
值得注意的是 service worker 也是運(yùn)行在渲染進(jìn)程中的 JS 代碼,因此對(duì)于擁有 Service Worker 的頁(yè)面,上述流程有些許的不同。
當(dāng)有 Service Worker 被注冊(cè)時(shí),其作用域會(huì)被保存,當(dāng)有導(dǎo)航時(shí),network thread 會(huì)在注冊(cè)過(guò)的 Service Worker 的作用域中檢查相關(guān)域名,如果存在對(duì)應(yīng)的 Service worker,UI thread 會(huì)找到一個(gè) renderer process 來(lái)處理相關(guān)代碼,Service Worker 可能會(huì)從 cache 中加載數(shù)據(jù),從而終止對(duì)網(wǎng)絡(luò)的請(qǐng)求,也可能從網(wǎng)上請(qǐng)求新的數(shù)據(jù)。
Service Worker 依據(jù)具體情形做處理。
關(guān)于 Service Worker 的更多內(nèi)容可參考:
https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle
如果 Service Worker 最終決定通過(guò)網(wǎng)上獲取數(shù)據(jù),Browser 進(jìn)程 和 renderer 進(jìn)程的交互其實(shí)會(huì)延后數(shù)據(jù)的請(qǐng)求時(shí)間 。Navigation Preload 是一種與 Service Worker 并行的加速加載資源的機(jī)制,服務(wù)端通過(guò)請(qǐng)求頭可以識(shí)別這類請(qǐng)求,而做出相應(yīng)的處理。
更多內(nèi)容可參考:
https://developers.google.com/web/updates/2017/02/navigation-preload
渲染進(jìn)程是如何工作的?
渲染進(jìn)程幾乎負(fù)責(zé) Tab 內(nèi)的所有事情,渲染進(jìn)程的核心目的在于轉(zhuǎn)換 HTML CSS JS 為用戶可交互的 web 頁(yè)面。渲染進(jìn)程中主要包含以下線程:
渲染進(jìn)程包含的線程
1. 主線程 Main thread
2. 工作線程 Worker thread
3. 排版線程 Compositor thread
4. 光柵線程 Raster thread
后文我們將逐步介紹不同線程的職責(zé),在此之前我們先看看渲染的流程。
1. 構(gòu)建 DOM
當(dāng)渲染進(jìn)程接收到導(dǎo)航的確認(rèn)信息,開(kāi)始接受 HTML 數(shù)據(jù)時(shí),主線程會(huì)解析文本字符串為 DOM。
渲染 html 為 DOM 的方法由 HTML Standard 定義。
2. 加載次級(jí)的資源
網(wǎng)頁(yè)中常常包含諸如圖片,CSS,JS 等額外的資源,這些資源需要從網(wǎng)絡(luò)上或者 cache 中獲取。主進(jìn)程可以在構(gòu)建 DOM 的過(guò)程中會(huì)逐一請(qǐng)求它們,為了加速 preload scanner 會(huì)同時(shí)運(yùn)行,如果在 html 中存在 <img><link> 等標(biāo)簽,preload scanner 會(huì)把這些請(qǐng)求傳遞給 Browser process 中的 network thread 進(jìn)行相關(guān)資源的下載。
3.JS 的下載與執(zhí)行
當(dāng)遇到 <script> 標(biāo)簽時(shí),渲染進(jìn)程會(huì)停止解析 HTML,而去加載,解析和執(zhí)行 JS 代碼,停止解析 html 的原因在于 JS 可能會(huì)改變 DOM 的結(jié)構(gòu)(使用諸如 documwnt.write()等 API)。
不過(guò)開(kāi)發(fā)者其實(shí)也有多種方式來(lái)告知瀏覽器應(yīng)對(duì)如何應(yīng)對(duì)某個(gè)資源,比如說(shuō)如果在<script> 標(biāo)簽上添加了 async 或 defer 等屬性,瀏覽器會(huì)異步的加載和執(zhí)行 JS 代碼,而不會(huì)阻塞渲染。更多的方法可參考 Resource Prioritization – Getting the Browser to Help You。
4. 樣式計(jì)算
僅僅渲染 DOM 還不足以獲知頁(yè)面的具體樣式,主進(jìn)程還會(huì)基于 CSS 選擇器解析 CSS 獲取每一個(gè)節(jié)點(diǎn)的最終的計(jì)算樣式值。即使不提供任何 CSS,瀏覽器對(duì)每個(gè)元素也會(huì)有一個(gè)默認(rèn)的樣式。
渲染進(jìn)程主線程計(jì)算每一個(gè)元素節(jié)點(diǎn)的最終樣式值
5. 獲取布局
想要渲染一個(gè)完整的頁(yè)面,除了獲知每個(gè)節(jié)點(diǎn)的具體樣式,還需要獲知每一個(gè)節(jié)點(diǎn)在頁(yè)面上的位置,布局其實(shí)是找到所有元素的幾何關(guān)系的過(guò)程。其具體過(guò)程如下:
通過(guò)遍歷 DOM 及相關(guān)元素的計(jì)算樣式,主線程會(huì)構(gòu)建出包含每個(gè)元素的坐標(biāo)信息及盒子大小的布局樹(shù)。布局樹(shù)和 DOM 樹(shù)類似,但是其中只包含頁(yè)面可見(jiàn)的元素,如果一個(gè)元素設(shè)置了 display:none ,這個(gè)元素不會(huì)出現(xiàn)在布局樹(shù)上,偽元素雖然在 DOM 樹(shù)上不可見(jiàn),但是在布局樹(shù)上是可見(jiàn)的。
6. 繪制各元素
即使知道了不同元素的位置及樣式信息,我們還需要知道不同元素的繪制先后順序才能正確繪制出整個(gè)頁(yè)面。在繪制階段,主線程會(huì)遍歷布局樹(shù)以創(chuàng)建繪制記錄。繪制記錄可以看做是記錄各元素繪制先后順序的筆記。
主線程依據(jù)布局樹(shù)構(gòu)建繪制記錄
7. 合成幀
熟悉 PS 等繪圖軟件的童鞋肯定對(duì)圖層這一概念不陌生,現(xiàn)代 Chrome 其實(shí)利用了這一概念來(lái)組合不同的層。
復(fù)合是一種分割頁(yè)面為不同的層,并單獨(dú)柵格化,隨后組合為幀的技術(shù)。不同層的組合由 compositor 線程(合成器線程)完成。
主線程會(huì)遍歷布局樹(shù)來(lái)創(chuàng)建層樹(shù)(layer tree),添加了 will-change CSS 屬性的元素,會(huì)被看做單獨(dú)的一層。
主線程遍歷布局樹(shù)生成層樹(shù)
你可能會(huì)想給每一個(gè)元素都添加上 will-change,不過(guò)組合過(guò)多的層也許會(huì)比在每一幀都柵格化頁(yè)面中的某些小部分更慢。為了更合理的使用層,可參考 堅(jiān)持僅合成器的屬性和管理層計(jì)數(shù) 。
一旦層樹(shù)被創(chuàng)建,渲染順序被確定,主線程會(huì)把這些信息通知給合成器線程,合成器線程會(huì)柵格化每一層。有的層的可以達(dá)到整個(gè)頁(yè)面的大小,因此,合成器線程將它們分成多個(gè)磁貼,并將每個(gè)磁貼發(fā)送到柵格線程,柵格線程會(huì)柵格化每一個(gè)磁貼并存儲(chǔ)在 GPU 顯存中。
柵格線程會(huì)柵格化每一個(gè)磁貼并存儲(chǔ)在 GPU 顯存中
一旦磁貼被光柵化,合成器線程會(huì)收集稱為繪制四邊形的磁貼信息以創(chuàng)建合成幀。
合成幀隨后會(huì)通過(guò) IPC 消息傳遞給瀏覽器進(jìn)程,由于瀏覽器的 UI 改變或者其它拓展的渲染進(jìn)程也可以添加合成幀,這些合成幀會(huì)被傳遞給 GPU 用以展示在屏幕上,如果滾動(dòng)發(fā)生,合成器線程會(huì)創(chuàng)建另一個(gè)合成幀發(fā)送給 GPU。
合成器線程會(huì)發(fā)送合成幀給 GPU 渲染
合成器的優(yōu)點(diǎn)在于,其工作無(wú)關(guān)主線程,合成器線程不需要等待樣式計(jì)算或者 JS 執(zhí)行,這就是為什么合成器相關(guān)的動(dòng)畫(huà) 最流暢,如果某個(gè)動(dòng)畫(huà)涉及到布局或者繪制的調(diào)整,就會(huì)涉及到主線程的重新計(jì)算,自然會(huì)慢很多。
瀏覽器對(duì)事件的處理
瀏覽器通過(guò)對(duì)不同事件的處理來(lái)滿足各種交互需求,這一部分我們一起看看從瀏覽器的視角,事件是什么,在此我們先主要考慮鼠標(biāo)事件。
在瀏覽器的看來(lái),用戶的所有手勢(shì)都是輸入,鼠標(biāo)滾動(dòng),懸置,點(diǎn)擊等等都是。
當(dāng)用戶在屏幕上觸發(fā)諸如 touch 等手勢(shì)時(shí),首先收到手勢(shì)信息的是 Browser process, 不過(guò) Browser process 只會(huì)感知到在哪里發(fā)生了手勢(shì),對(duì) tab 內(nèi)內(nèi)容的處理是還是由渲染進(jìn)程控制的。
事件發(fā)生時(shí),瀏覽器進(jìn)程會(huì)發(fā)送事件類型及相應(yīng)的坐標(biāo)給渲染進(jìn)程,渲染進(jìn)程隨后找到事件對(duì)象并執(zhí)行所有綁定在其上的相關(guān)事件處理函數(shù)。
事件從瀏覽器進(jìn)程傳送給渲染進(jìn)程
前文中,我們提到過(guò)合成器可以獨(dú)立于主線程之外通過(guò)合成柵格化層平滑的處理滾動(dòng)。如果頁(yè)面中沒(méi)有綁定相關(guān)事件,組合器線程可以獨(dú)立于主線程創(chuàng)建組合幀。如果頁(yè)面綁定了相關(guān)事件處理器,主線程就不得不出來(lái)工作了。這時(shí)候合成器線程會(huì)怎么處理呢?
這里涉及到一個(gè)專業(yè)名詞「理解非快速滾動(dòng)區(qū)域(non-fast scrollable region)」由于執(zhí)行 JS 是主線程的工作,當(dāng)頁(yè)面合成時(shí),合成器線程會(huì)標(biāo)記頁(yè)面中綁定有事件處理器的區(qū)域?yàn)?non-fast scrollable region ,如果存在這個(gè)標(biāo)注,合成器線程會(huì)把發(fā)生在此處的事件發(fā)送給主線程,如果事件不是發(fā)生在這些區(qū)域,合成器線程則會(huì)直接合成新的幀而不用等到主線程的響應(yīng)。
涉及 non-fast scrollable region 的事件,合成器線程會(huì)通知主線程進(jìn)行相關(guān)處理。
web 開(kāi)發(fā)中常用的事件處理模式是事件委托,基于事件冒泡,我們常常在最頂層綁定事件:
復(fù)制代碼
document.body.addEventListener('touchstart', event => { if (event.target === area) { event.preventDefault(); } } );
上述做法很常見(jiàn),但是如果從瀏覽器的角度看,整個(gè)頁(yè)面都成了 non-fast scrollable region 了。
這意味著即使操作的是頁(yè)面無(wú)綁定事件處理器的區(qū)域,每次輸入時(shí),合成器線程也需要和主線程通信并等待反饋,流暢的合成器獨(dú)立處理合成幀的模式就失效了。
由于事件綁定在最頂部,整個(gè)頁(yè)面都成為了 non-fast scrollable region。
為了防止這種情況,我們可以為事件處理器傳遞 passive: true 做為參數(shù),這樣寫(xiě)就能讓瀏覽器即監(jiān)聽(tīng)相關(guān)事件,又讓組合器線程在等等主線程響應(yīng)前構(gòu)建新的組合幀。
復(fù)制代碼
document.body.addEventListener('touchstart', event => { if (event.target === area) { event.preventDefault() } }, {passive: true} );
不過(guò)上述寫(xiě)法可能又會(huì)帶來(lái)另外一個(gè)問(wèn)題,假設(shè)某個(gè)區(qū)域你只想要水平滾動(dòng),使用 passive: true 可以實(shí)現(xiàn)平滑滾動(dòng),但是垂直方向的滾動(dòng)可能會(huì)先于event.preventDefault()發(fā)生,此時(shí)可以通過(guò) event.cancelable 來(lái)防止這種情況。
復(fù)制代碼
document.body.addEventListener('pointermove', event => { if (event.cancelable) { event.preventDefault(); // block the native scroll /* * do what you want the application to do here */ } }, {passive: true});
也可以使用 css 屬性 touch-action 來(lái)完全消除事件處理器的影響,如:
復(fù)制代碼
#area { touch-action: pan-x; }
查找到事件對(duì)象
當(dāng)組合器線程發(fā)送輸入事件給主線程時(shí),主線程首先會(huì)進(jìn)行命中測(cè)試(hit test)來(lái)查找對(duì)應(yīng)的事件目標(biāo),命中測(cè)試會(huì)基于渲染過(guò)程中生成的繪制記錄( paint records )查找事件發(fā)生坐標(biāo)下存在的元素。
主線程依據(jù)繪制記錄查找事件相關(guān)元素。
事件的優(yōu)化
一般我們屏幕的刷新速率為 60fps,但是某些事件的觸發(fā)量會(huì)不止這個(gè)值,出于優(yōu)化的目的,Chrome 會(huì)合并連續(xù)的事件 (如 wheel, mousewheel, mousemove, pointermove, touchmove ),并延遲到下一幀渲染時(shí)候執(zhí)行 。
而如 keydown, keyup, mouseup, mousedown, touchstart, 和 touchend 等非連續(xù)性事件則會(huì)立即被觸發(fā)。
Chrome 會(huì)合并連續(xù)事件到下一幀觸發(fā)。
合并事件雖然能提示性能,但是如果你的應(yīng)用是繪畫(huà)等,則很難繪制一條平滑的曲線了,此時(shí)可以使用 getCoalescedEvents API 來(lái)獲取組合的事件。示例代碼如下:
復(fù)制代碼
window.addEventListener('pointermove', event => { const events = event.getCoalescedEvents(); for (let event of events) { const x = event.pageX; const y = event.pageY; // draw a line using x and y coordinates. } });
花了好久來(lái)整理上面的內(nèi)容,整理的過(guò)程收獲還挺大的,也希望這篇筆記能對(duì)你有所啟發(fā),如果有任何疑問(wèn),歡迎一起來(lái)討論。
本文經(jīng)作者授權(quán)轉(zhuǎn)載,原文鏈接為:
https://zhuanlan.zhihu.com/p/47407398
參考鏈接
們從小時(shí)候就喜歡給一切東西起外號(hào),游戲和游戲里的經(jīng)典人物更是如此。越是經(jīng)典的東西外號(hào)越是言簡(jiǎn)意賅,這大概就是“格斗之王”的譯名最終輸給“拳皇”的原因吧。經(jīng)典的東西并不需要過(guò)多的描述,一個(gè)最簡(jiǎn)單的詞語(yǔ)就能代表。
最近這幾年,雖然經(jīng)典的“拳皇”游戲不復(fù)往日榮光,但相關(guān)改編游戲一部接著一部,作為著名的街機(jī)IP,擁有這樣的待遇無(wú)可厚非。只是SNK不怎么珍惜自己的羽毛,授權(quán)改編的游戲不是回合制就是角色扮演,除了那些正版授權(quán)的角色人物,我們嗅不到一絲當(dāng)年的味道。你們是不是已經(jīng)忘了,當(dāng)年在中國(guó)打天下的這個(gè)系列是個(gè)格斗游戲?
我們知道,以手機(jī)觸屏想要還原當(dāng)年搖桿按鍵的操作模式確實(shí)困難,但在移動(dòng)游戲快速發(fā)展的時(shí)代,無(wú)論是老玩家還是新人,都在期待改編游戲能真正傳承“格斗”的精神。懶洋洋無(wú)所謂勝負(fù)不是格斗精神,不妥協(xié)、懷著不服的信念KO一切才是真格斗——從這個(gè)意義上來(lái)說(shuō),騰訊即將推出的“‘拳皇’正統(tǒng)格斗手游”《拳皇命運(yùn)》是一個(gè)有望傳承這一精神的作品。試玩兩天之后給我的感受是,《拳皇命運(yùn)》手游的確有那么點(diǎn)不妥協(xié)的意思,至少這一次我手上在玩的是一個(gè)格斗游戲了。
可以感受到,《拳皇命運(yùn)》從畫(huà)風(fēng)和體驗(yàn)上試圖找回《拳皇'97》的感覺(jué)
格斗游戲的核心當(dāng)然應(yīng)該在“格斗”二字上。格是見(jiàn)招防守,斗是拆招進(jìn)攻。無(wú)論是用搖桿還是用觸屏控制,關(guān)鍵是把這種攻防關(guān)系做出來(lái),才能傳達(dá)格斗游戲的精髓。
《拳皇命運(yùn)》在游戲系統(tǒng)上沒(méi)有照搬街機(jī)“拳皇”的橫版模式,而是活用《地下城勇士》的動(dòng)作格斗系統(tǒng),使角色可以在前后、上下兩個(gè)軸上移動(dòng)。這樣的移動(dòng)方式更有利于游戲PvE關(guān)卡的設(shè)計(jì),同時(shí)也讓PvP有了更多的對(duì)抗性變數(shù),是值得贊許的。為了更好地還原手機(jī)上操作的打擊感,游戲放棄了“拳皇”中的跳躍和搓招。好在游戲保留了回避的設(shè)計(jì),還追加了起身無(wú)敵時(shí)間,給了挨打玩家的不止一種反擊方式。
總的來(lái)說(shuō),由于街機(jī)只有XY兩個(gè)軸,手游中增加了Z軸,還是大幅豐富了游戲的操作性和策略性。
游戲提供兩種反手機(jī)會(huì)——受擊回避與起身無(wú)敵時(shí)間
由于手機(jī)觸屏難以還原搖桿搓招,游戲非常明智地放棄了街機(jī)的搓招系統(tǒng),轉(zhuǎn)而使用了在手游上比較成熟的技能冷卻系統(tǒng)。游戲?yàn)榻巧暨x了幾個(gè)具有代表性的必殺技,配合普通攻擊出招就可以打出連擊,不過(guò)每個(gè)必殺技施放之后有一定的冷卻時(shí)間,以控制格斗的節(jié)奏性。
從試玩的體驗(yàn)來(lái)說(shuō),角色的必殺技在畫(huà)面表現(xiàn)上完全還原了街機(jī)版的動(dòng)作,在特效上還有一定的加強(qiáng),玩起來(lái)爽快感十足。多試幾個(gè)人物后,也可以體會(huì)到制作人員并非隨意選擇和設(shè)計(jì)必殺技,而是根據(jù)角色量身定制。角色大都擁有“起手技”和“連擊技”,不同技能可發(fā)揮牽制、突進(jìn)、反擊和爆發(fā)等不同作用,有些角色可能沒(méi)有防守技能而擁有多個(gè)突進(jìn)技,而另一些角色可能主打防守反擊。原本熟悉的人物招式在這個(gè)全新的游戲里需要我們重新去熟悉,無(wú)腦發(fā)技能很難在競(jìng)技場(chǎng)里取得好成績(jī),只有同時(shí)掌握敵我雙方角色的技能特點(diǎn)才有可能取得連勝。
總體來(lái)說(shuō),《拳皇命運(yùn)》的戰(zhàn)斗系統(tǒng)比街機(jī)版“拳皇”更為輕量化,現(xiàn)階段版本下角色招式的判定范圍比較大,有些必殺擁有三分之一屏的打擊判定,不過(guò)由于冷卻時(shí)間的存在以及倒地角色起身時(shí)有無(wú)敵時(shí)間,這讓游戲PvP的節(jié)奏總體來(lái)說(shuō)還算不錯(cuò)。優(yōu)勢(shì)玩家無(wú)法一口氣打掉對(duì)手一管血,劣勢(shì)玩家依然有機(jī)會(huì)起身后反殺——?jiǎng)儇?fù)的關(guān)鍵仍舊在于對(duì)角色技能的熟悉程度。
角色技能特效在游戲中的表現(xiàn)得到加強(qiáng)
PvP是游戲的重要玩法,不同等級(jí)角色數(shù)值被拉平,主要考驗(yàn)操作與對(duì)角色的熟悉度
《拳皇命運(yùn)》在戰(zhàn)斗系統(tǒng)方面雖然有些變化,但依然是目前“拳皇”手游產(chǎn)品中相對(duì)更出色、更能還原格斗感的一款作品。只有格斗游戲才有可能傳承“拳皇”“用不服的信念KO一切”的IP精神。當(dāng)然游戲也并不止步于此,它還用豐富的細(xì)節(jié)和多樣玩法讓自己成為一款傳承街機(jī)情懷的游戲。
我們知道,“拳皇”是街機(jī)文化的產(chǎn)物,游戲在很多細(xì)節(jié)上都體現(xiàn)了這種文化的延續(xù)。比如開(kāi)戰(zhàn)時(shí)的“Ready GO”,KO之后屏幕紅白相間的閃爍等等。游戲過(guò)程中模擬投幣,包括輸入角色名字時(shí)跳出的“拳皇”格斗大賽的信封等小趣味都很有代入感。游戲“主城”背景里的各種細(xì)節(jié)也都透露著上世紀(jì)90年代都市的商業(yè)氣息,彌漫著揮之不去的懷舊味道——街機(jī)游戲正是那樣一個(gè)時(shí)代的產(chǎn)物。
游戲能夠以這樣高頻度的方式呈現(xiàn)各類街機(jī)元素,體現(xiàn)的是制作方對(duì)街機(jī)文化的喜愛(ài)之情,這種感情對(duì)玩家也很有感染力。
以信封的形式輸入用戶名,很有代入感
游戲?qū)謾C(jī)文化的致敬并沒(méi)有流于形式,在關(guān)卡設(shè)計(jì)上同樣有很多體現(xiàn)。
普通關(guān)卡會(huì)讓人想起玩《快打旋風(fēng)》的日子,角色必殺技的加入讓?xiě)?zhàn)斗多了幾分快感。除了普通關(guān)卡,游戲還加入了不少特殊的關(guān)卡玩法,比如有一關(guān)開(kāi)車追逐戰(zhàn)就明顯致敬了《恐龍快打》(俗稱“恐龍島”)的第二關(guān),雖然那個(gè)游戲與“拳皇”無(wú)關(guān),但也是兒時(shí)街機(jī)房回憶的一部分,玩起來(lái)不由讓人會(huì)心一笑。
這樣的關(guān)卡在游戲中不止一處,不少關(guān)卡都需要以特定的方式過(guò)關(guān),將敵人引到指定地點(diǎn)或者只能用特殊攻擊造成傷害等等。雖然這些關(guān)卡對(duì)很多人來(lái)說(shuō)可能只會(huì)仔細(xì)玩一次,往后只視作掃蕩拿材料的地方,但制作方愿意在這些細(xì)節(jié)的地方花心思,本身就體現(xiàn)了制作著對(duì)街機(jī)文化的敬意。這種態(tài)度帶給新玩家的是一種與眾不同的新鮮感,帶給老玩家的是一種情感共鳴。
致敬《恐龍快打》的游戲關(guān)卡很有情懷味
模擬街機(jī)遙感畫(huà)面的細(xì)節(jié)讓人回憶起當(dāng)年時(shí)光
“拳皇”這個(gè)IP到現(xiàn)在依然流行,當(dāng)然不只是因?yàn)楦穸吩睾徒謾C(jī)文化這么簡(jiǎn)單。很多新生代玩家沒(méi)有進(jìn)過(guò)街機(jī)房,照樣知道不知火舞的大名。“拳皇”人物已經(jīng)跳脫游戲之外而成為一種更廣泛的文化符號(hào)。小時(shí)候我們饑渴游戲,只爭(zhēng)勝負(fù),甚至沒(méi)來(lái)得及關(guān)心一下游戲的劇情與角色的來(lái)歷,只知道不知火舞遇到安迪出場(chǎng)時(shí)要穿婚紗,草薙京八神庵相遇時(shí)互相仇視,卻不知道小時(shí)候在同學(xué)間傳看的所謂正版“拳皇”漫畫(huà)實(shí)際上并非真正的原版“拳皇”故事,而是由香港地區(qū)業(yè)者購(gòu)買(mǎi)版權(quán)后自行演繹的版本。當(dāng)然,這樣耳濡目染的結(jié)果就是,我們記憶中的“拳皇”故事可能早就已經(jīng)是經(jīng)過(guò)本土化的中國(guó)版本了。
游戲主界面背景中有不少彩蛋,等著玩家們?nèi)ネ诰?/p>
SNK官方雖然零零星星地推出過(guò)漫畫(huà)、動(dòng)畫(huà)和廣播劇,但由于SNK幾度沉浮,“拳皇”IP多次易手,至今也沒(méi)有一部完整的動(dòng)畫(huà)或漫畫(huà)將各代故事統(tǒng)一起來(lái),各種改編也往往根據(jù)自己的需求去解讀角色,搞得粉絲一頭霧水。
2017年,由SNK出品并擔(dān)任制片方的3D動(dòng)畫(huà)《拳皇命運(yùn)》開(kāi)始在國(guó)內(nèi)播出,正式重啟了“拳皇”世界觀。動(dòng)畫(huà)“拳皇”從《拳皇’94》的故事開(kāi)始講起,將“拳皇”故事以游戲?yàn)榫€索娓娓道來(lái)。無(wú)論是新入坑的玩家還是記憶已經(jīng)模糊的老玩家,這都是一次重新了解“拳皇”世界的機(jī)會(huì)。這部動(dòng)畫(huà)的同名游戲《拳皇命運(yùn)》在劇情上自然也是從《拳皇’94》特瑞的故事開(kāi)始講起。
劇情從系列最初講起,對(duì)新老玩家來(lái)說(shuō)都是一個(gè)梳理和了解系列故事線的機(jī)會(huì)
所以我在這里建議,即使老玩家也別忙著將游戲劇情一鍵跳過(guò)。本作在劇情演繹上下了不少工夫,通過(guò)劇情我們有機(jī)會(huì)對(duì)那些熟悉的角色有一個(gè)更全面的了解。“拳皇”原來(lái)就是一個(gè)類似“復(fù)仇者聯(lián)盟”那樣的大亂斗游戲,并沒(méi)有給客串角色太多戲份。這次游戲的故事不但從系列原初開(kāi)始講起,還將不同小隊(duì)的人物背景串聯(lián)起來(lái),在劇情上的確是一次補(bǔ)課的機(jī)會(huì)。
在人物的美術(shù)風(fēng)格上,游戲結(jié)合了“拳皇”經(jīng)典與當(dāng)代主流審美,沒(méi)有采用動(dòng)畫(huà)版的3D人設(shè),應(yīng)該說(shuō)這種2D風(fēng)格更符合“拳皇”老玩家的審美。此外,游戲以《拳皇’97》為核心,自然少不了幾十名角色的收集與養(yǎng)成。除了劇情關(guān)卡期間就會(huì)贈(zèng)送給玩家的一些人物之外,剩下的就需要玩家自己通過(guò)戰(zhàn)斗和任務(wù)來(lái)收集了。
人物的升級(jí)升星系統(tǒng)這里不再贅述,值得一提的是本作引入了人物的時(shí)裝系統(tǒng)。這個(gè)系統(tǒng)在《地下城與勇士》時(shí)代就已經(jīng)很成熟,能做到在課金的前提下最大限度地保持格斗游戲平衡。現(xiàn)在時(shí)裝系統(tǒng)被引入到“拳皇”的游戲中,也算是對(duì)格斗游戲傷害最小的一種課金方式了。從現(xiàn)在公開(kāi)的版本來(lái)看,游戲沒(méi)有把一件套裝拆成衣褲鞋帽幾件套來(lái)賣(mài),而是整體一口價(jià),并且可以在游戲中自由替換,還是比較厚道的。
總的來(lái)說(shuō),《拳皇命運(yùn)》在還原經(jīng)典劇情的基礎(chǔ)上,結(jié)合了當(dāng)下的時(shí)尚審美,優(yōu)化了立繪、模型、技能、配音等細(xì)節(jié),最終呈現(xiàn)出來(lái)的效果還是不錯(cuò)的。
游戲搭載時(shí)裝系統(tǒng),算是對(duì)格斗競(jìng)技影響最小的一種課金形式
“拳皇”的成功并非一蹴而就,而是從《龍虎之拳》《餓狼傳說(shuō)》等游戲一步步積累傳承而來(lái)。一招一式不但要考慮角色平衡性,還要考慮是否符合角色性格。玩家永遠(yuǎn)比制作人有更多的智慧,每一個(gè)人物都要經(jīng)得起玩家的推敲才能成為經(jīng)典。“拳皇”經(jīng)過(guò)一次次迭代,直到《拳皇’97》才真正走上巔峰,其成功的秘密就在于去粗取精,一步步摸索到格斗的精華。
如果說(shuō)RPG游戲是制作人講一個(gè)故事給玩家聽(tīng),那么格斗游戲就是制作人與玩家共同完成的一種游戲類型,沒(méi)有足夠的積累就難以做出經(jīng)典的格斗系統(tǒng)。這也是“拳皇”改編手游總是回避格斗的原因。在手機(jī)上,格斗游戲幾乎沒(méi)有積累,誰(shuí)也不敢輕易去碰。而“拳皇”這個(gè)品牌,格斗精神與經(jīng)典角色兩者缺一不可,只賣(mài)人物IP的“拳皇”是跛腳走路,往往走不遠(yuǎn)。
《拳皇命運(yùn)》的不同之處就是用格斗玩法補(bǔ)齊了“拳皇”IP游戲的另一只腳,將街機(jī)時(shí)代不服輸?shù)木裢ㄟ^(guò)戰(zhàn)斗系統(tǒng)傳承下來(lái),再結(jié)合系列重啟的劇情,讓這個(gè)古老IP不只是80后老玩家的情懷專場(chǎng),還是新玩家的入坑機(jī)會(huì)。
從這個(gè)意義上說(shuō),《拳皇命運(yùn)》如此設(shè)定帶來(lái)的是兩重意涵:從游戲細(xì)節(jié)上看,這款格斗手游比較好地還原了“拳皇”系列的味道,觸碰到了此前誰(shuí)都不敢涉足的領(lǐng)域;從設(shè)計(jì)思路上看,《拳皇命運(yùn)》也體現(xiàn)出了“不妥協(xié)”的態(tài)度,延續(xù)我們以為早已遠(yuǎn)去的街機(jī)廳格斗熱血。這當(dāng)然都是格斗游戲KO精神的展現(xiàn),從中可以看到制作團(tuán)隊(duì)向經(jīng)典致敬的誠(chéng)意,這份誠(chéng)意顯然是新老玩家們都能感受到的。
經(jīng)典之所以成為經(jīng)典就是不怕時(shí)間的消磨,或許20年前的老玩家們很難想象,在未來(lái)有一天,“拳皇”系列中“不服的信念,KO一切”的精神將隨著新生的游戲作品在眾多中國(guó)玩家中不斷地傳遞下去。
希望在正式開(kāi)服的版本中,這句口號(hào)可以更加落到實(shí)處
騰訊首款由SNK正版授權(quán)的格斗手游《拳皇命運(yùn)》已經(jīng)確定于5月份正式上線,目前游戲官網(wǎng)已經(jīng)開(kāi)放預(yù)約。
v.qq.com/iframe/player.html?vid=i0632l9hqfk&tiny=0&auto=0
歡迎在頭條關(guān)注觸樂(lè),閱讀更多有趣的游戲內(nèi)容。
xBrowser是將基于Chromium的瀏覽器與Java應(yīng)用程序集成,以處理和顯示HTML5、CSS3、JavaScript、Flash等。
近日,JxBrowser v7.12發(fā)布啦!JAVA應(yīng)用程序?yàn)g覽器集成控件JxBrowser更新至7.12, 現(xiàn)在可以在Chromium 84上運(yùn)行,并且在使用DOM和JavaScript-Java Bridge API時(shí)提供了許多新選項(xiàng),點(diǎn)擊文末“了解更多”下載最新版JxBrowser。
新增功能
Chromium 84
在這個(gè)Chromium構(gòu)建中,一些與JxBrowser封裝的功能已經(jīng)被移除或改變,所以這個(gè)JxBrowser版本在公共API中引入了一些突破性的變化。
可信事件
com.teamdev.jxbrowser.dom.event.Event接口已經(jīng)擴(kuò)展了isTrusted()方法,允許檢測(cè)事件是由用戶操作產(chǎn)生的,還是通過(guò)EventTarget.dispatchEvent()創(chuàng)建/修改并發(fā)送的。
MouseEvent:頁(yè)面位置
com.teamdev.jxbrowser.dom.event.MouseEvent接口已經(jīng)擴(kuò)展了pageLocation(),允許獲取事件發(fā)生時(shí)鼠標(biāo)光標(biāo)在文檔坐標(biāo)系中的位置。
MouseEvent:頁(yè)面位置
允許從com.teamdev.jxbrowser.dom.event.KeyEvent中獲取鍵碼的功能已經(jīng)被重新設(shè)計(jì)為與DOM KeyboardEvent類似。我們?yōu)镈OM鍵碼引入了DomKeyCode枚舉,并擴(kuò)展了com.teamdev.jxbrowser.dom.event.KeyEvent的方法,允許獲取DOM鍵碼和一個(gè)代表與物理鍵相關(guān)聯(lián)的UTF-8字符的字符串,如果它有打印表示的話。例如
document.addEventListener(EventType.KEY_PRESS, event -> {
if (event instanceof KeyEvent) {
KeyEvent keyEvent = (KeyEvent) event;
DomKeyCode keyCode = keyEvent.domKeyCode();
String character = keyEvent.character();
System.out.println("DOM KeyEvent: keyCode=" + keyCode + ", character=" + character);
}
}, false);
JS可訪問(wèn)的Java類
JavaScript-Java Bridge API已經(jīng)擴(kuò)展了com.teamdev.jxbrowser.js.JsAccessibleClasses,它允許告訴人們特定類型的Java實(shí)例可以從JavaScript中訪問(wèn)。例如
JsAccessibleClasses.add(ArrayList.class, LinkedList.class);
Cookie SameSite
增加了SameSite cookie屬性支持。
改進(jìn)功能
Bug修復(fù)
如果您對(duì)jxbrowser感興趣,可以點(diǎn)擊下方“了解更多”了解具體授權(quán)和使用機(jī)制。
*請(qǐng)認(rèn)真填寫(xiě)需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。