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
要:HI,朋友們好,有許多事情看起來都很簡單,但是當(dāng)自己做的時候發(fā)現(xiàn)其實堅持下去并不容易,比如我的這個計劃,1000家網(wǎng)站計劃,看起來人人都會做,但想要每天堅持寫下來卻是不容易的。之前我也介紹過不少圖形設(shè)計工具了,不知道你們有沒有使用,國內(nèi)比較好用的如:創(chuàng)客貼,也是我經(jīng)常使用的,今天我在找雜志封面制作工具的時候,一搜發(fā)現(xiàn)了一大把,但是后來用了用還是FotoJet比較好用一點,和其他圖形設(shè)計工具都差不多,還支持中文站,挺值得收藏的,后面還有許多有意思的網(wǎng)站等你發(fā)現(xiàn),自己看著需要哪個拿走吧。
超強(qiáng)的雜志封面制作工具:FotoJet
你不需要很好的Photoshop、AI技巧,就能制作出一整本很不錯的封面設(shè)計,信嗎?通過雜志封面制作工具,您只需點擊一下鼠標(biāo),即可將自己的照片放在人物,花花公子,時間,財富等知名雜志的封面模板上。FotoJet是一個提供在線免費的圖片拼貼模版,幫助用戶輕松的實現(xiàn)圖片拼接組合,無需注冊和下載即可使用,網(wǎng)站內(nèi)置了320+套精致的模版和80+經(jīng)典的布局樣式,支持添加文字和剪貼畫圖片、背景圖片。該網(wǎng)站有許多模板可供選擇,可以JPG或PNG格式保存你的雜志封面,或直接分享到社交媒體,支持中文站。
推薦度:★★★★★
支持私有云部署的文檔管理系統(tǒng):KodExplorer
與其把自己的照片、文檔、視頻等重要資料交給某數(shù)字公司、某競價排名公司、某社交軟件公司,還不如把這些個人的重要數(shù)據(jù)緊緊地握在手里。(KodExplorer)可道云是一個基于企業(yè)私有云和在線文檔管理的資源管理器,該平臺是基于Web技術(shù)的私有云和在線文件管理系統(tǒng),可用于服務(wù)器文件管理,支持圖片、音樂、視頻預(yù)覽,在線解壓縮,文件夾拖拽上傳,遠(yuǎn)程離線下載。支持Office的在線預(yù)覽和編輯,可多人協(xié)同編輯作業(yè),文檔歷史版本回溯,更有Photoshop、Ai、AutoCAD等專業(yè)文檔的在線預(yù)覽,支持圖形編輯,圖片粘貼、數(shù)據(jù)報表生成等。多人實時在線協(xié)同編輯,實時查看他人改動,實時保存和自動版本合并。可設(shè)定到期時間、提取密碼、下載權(quán)限,滿足更多場景需求,輕松將文件分享給客戶、同事查看。內(nèi)置輕應(yīng)用和插件市場,數(shù)百種插件和拓展支持,從辦公環(huán)境到個人娛樂,幾乎覆蓋日常使用所有需求。
推薦度:★★★★★
旅游出差比價的報銷助手:報銷吧
現(xiàn)在很多企業(yè)機(jī)構(gòu)的費用報銷,會抱一堆發(fā)票,跑到財務(wù)那里報銷。有時候,發(fā)票比較多,財務(wù)一張一張核對,過于繁瑣;有時候,員工又會把發(fā)票弄丟,雖然可以找其他發(fā)票替代,卻加重了財務(wù)核對的工作量。報銷吧是一家在線的企業(yè)級差旅和費用報銷管理工具,也支持手機(jī)App。基于華為的費用管理思想,同時也是華為云解決方案合作伙伴。平時我們商務(wù)、銷售、市場、老板人員出差要訂票、打車、住酒店,而報銷吧整合國內(nèi)的眾多旅游服務(wù)商,比如:飛鶴航空、攜程與同程網(wǎng)的機(jī)票酒店、滴滴出行企業(yè)版、京東企業(yè)購等,一款軟件內(nèi)可以實現(xiàn)商務(wù)出差全過程,從出差到報銷,無需再下載多個軟件應(yīng)用,只需一個報銷吧,就可以實現(xiàn)應(yīng)用內(nèi)一站式預(yù)訂機(jī)票、酒店、火車及打車和出差比價的功能,由于集中采購所以要比攜程優(yōu)惠很多,出差與報銷的人員(上班族)必備。
推薦度:★★★★★
可視化協(xié)作圖表編輯工具:Lucidchart
這款圖表編輯工具功能強(qiáng)大,不論是在線還是離線都隨時隨地進(jìn)行圖表繪制,同時可輕松與他人分享圖表。Lucidchart是一個基于HTML5的功能完善的在線流程圖繪制和協(xié)作應(yīng)用,可以方便快速的實現(xiàn)流程圖表的繪制,同時還可以和他人進(jìn)行實時的流程圖繪制和修改,所有的變動都會實時的同步,對于群組協(xié)作來說是很方便的工具。提供了很多免費繪圖模板,大大地加快了用戶的繪圖速度。從數(shù)百種形狀中進(jìn)行選擇,支持 Autoprompt,快速添加和連接對象,從任何對象拖拽出新線條,拖放來添加您自己的圖片,導(dǎo)出為(矢量)PDF、PNG 和 JPG- 嵌入圖標(biāo)至博客,包含熱點和狀態(tài)的交互式實體模型,從 microsoft Visio (vdx) 導(dǎo)入文檔,國內(nèi)也有同類產(chǎn)品ProcessOn。
推薦度:★★★★★
可反向在線制作GIF動畫圖片的工具:Freegifmaker.me
想要制作gif動畫圖片嗎?相信不少人都會有這個想法吧。每天你都可以在社交網(wǎng)絡(luò)上看到許多有趣的動畫GIF。但是,你有沒有想過如何動畫看起來相反的順序?倒過來的GIF可能會更有趣。Freegifmaker.me是一個免費在線制作GIF動畫圖片的應(yīng)用,使用非常簡單,無需注冊。制作非常簡單,打開網(wǎng)站,然后選擇一個GIF動畫模板,如大鼻子特效、扭曲、霧化、底片效果、油畫特效. . .等,然后跳轉(zhuǎn)到上傳制作頁面,點擊”瀏覽”上傳圖片,然后在”Select Size”選項處選擇制作GIF 圖片的大小,默認(rèn)是300像素,最后點擊”Generate Animation”開始制作。制作完成后,就可以右鍵–另存為–保存圖片了,也可以通過網(wǎng)站給的分享地址和他人一起分享。支持反向制作GIF動畫圖片,曾用名叫Loogix,后來Freegifmaker.me,很好玩,可以試試。
推薦度:★★★★★
查找你注冊過的站點的網(wǎng)站:REG007
你用你的手機(jī)號和郵箱注冊過哪些網(wǎng)站?是不是有些已經(jīng)忘卻了?沒關(guān)系,這樣的一個網(wǎng)站幫你找到他們測試了一下發(fā)現(xiàn)還是很有用的,除了可以找回你曾經(jīng)注冊過的站點,還可以當(dāng)做一個社會工程學(xué)工具。REG007是一個你注冊過哪些網(wǎng)站,輸入郵箱或手機(jī)號,一搜便知的網(wǎng)站工具。可以在線快速查詢你在那些網(wǎng)站進(jìn)行注冊過,無需注冊任何信息就可以使用。這款在線應(yīng)用可以幫助你重新記憶起你在那些網(wǎng)站注冊,這個對于健忘的人來說非常方便,同樣你也可以用來查詢誰誰在哪個網(wǎng)站注冊過。其實,用這個可以做很多事,也很有許多需要注意的,比如,你要換電話號了,那么就要第一時間把注冊過的網(wǎng)站的手機(jī)號都換掉,提前。
推薦度:★★★★★
在線字體轉(zhuǎn)換網(wǎng)站工具:Font2Web
假如你只有一種otf格式的字體,網(wǎng)站中引入的話,會有很大兼容問題,微軟ie和mac好像都有問題,解決這個問題的辦法可以實施字體轉(zhuǎn)換,把otf轉(zhuǎn)化為你想要的字體。因為在做東西的時候發(fā)現(xiàn)我只備了ttf字體,但是在考慮兼容的情況下,只有這一個字體是不夠的,所以百度發(fā)現(xiàn)了Font2Web這個工具,操作非常簡單。Font2Web是一款在線轉(zhuǎn)換字體服務(wù)的網(wǎng)站,可以快速將ttf字體在線轉(zhuǎn)換成.ttf, .otf, .eot, .woff , .svg等網(wǎng)頁設(shè)計CSS專用字體,上傳ttf文件點擊按鈕就可以實現(xiàn)在線轉(zhuǎn)換,成功后會自動下載一個壓縮包,里面就有eot,otf,svg,wotf各種字體,又一款非常不錯的字體轉(zhuǎn)換在線應(yīng)用,前端程序員、網(wǎng)頁設(shè)計人員收藏必備吧。
推薦度:★★★★★
1000家網(wǎng)站:這是一個從0到1的歷程,是一個自媒體肩負(fù)起互聯(lián)網(wǎng)普及的歷程,是一段為了理想重新上路、累并快樂著的歷程。嗯,1000家網(wǎng)站,互聯(lián)網(wǎng)發(fā)展這么多年,很多人經(jīng)常使用的還是那些常用的網(wǎng)站(資訊、影視、社交、搜索....),有許多(優(yōu)秀的、好玩的、特色的、有趣的、實用的.....國內(nèi)外網(wǎng)站)未被發(fā)掘過,也沒有人去真正了解過,為此,我們推出了這個系列計劃,先讓這1000家網(wǎng)站走近大眾化,我們的理念是:讓人人都能了解,人人都會利用互聯(lián)網(wǎng)學(xué)習(xí)、工作、提升效率,增長見識。如果你也喜歡,請關(guān)注我們的動態(tài)。
多數(shù)設(shè)備的刷新頻率是60Hz,也就說是瀏覽器對每一幀畫面的渲染工作要在16ms內(nèi)完成,超出這個時間,頁面的渲染就會出現(xiàn)卡頓現(xiàn)象,影響用戶體驗。前端的用戶體驗給了前端直觀的印象,因此對B/S架構(gòu)的開發(fā)人員來說,熟悉瀏覽器的內(nèi)部執(zhí)行原理顯得尤為重要。
瀏覽器大體上由以下幾個組件組成,各個瀏覽器可能有一點不同。
注意:chrome瀏覽器與其他瀏覽器不同,chrome使用多個渲染引擎實例,每個Tab頁一個,即每個Tab都是一個獨立進(jìn)程。
Chrome瀏覽器使用多個進(jìn)程來隔離不同的網(wǎng)頁,在Chrome中打開一個網(wǎng)頁相當(dāng)于起了一個進(jìn)程,每個tab網(wǎng)頁都有由其獨立的渲染引擎實例。因為如果非多進(jìn)程的話,如果瀏覽器中的一個tab網(wǎng)頁崩潰,將會導(dǎo)致其他被打開的網(wǎng)頁應(yīng)用。另外相對于線程,進(jìn)程之間是不共享資源和地址空間的,所以不會存在太多的安全問題,而由于多個線程共享著相同的地址空間和資源,所以會存在線程之間有可能會惡意修改或者獲取非授權(quán)數(shù)據(jù)等復(fù)雜的安全問題。
在內(nèi)核控制下各線程相互配合以保持同步,一個瀏覽器通常由以下常駐線程組成:
GUI渲染線程負(fù)責(zé)渲染瀏覽器界面HTML元素,當(dāng)界面需要重繪(Repaint)或由于某種操作引發(fā)回流(reflow)時,該線程就會執(zhí)行。在Javascript引擎運行腳本期間,GUI渲染線程都是處于掛起狀態(tài)的,也就是說被凍結(jié)了.
JS為處理頁面中用戶的交互,以及操作DOM樹、CSS樣式樹來給用戶呈現(xiàn)一份動態(tài)而豐富的交互體驗和服務(wù)器邏輯的交互處理。如果JS是多線程的方式來操作這些UI DOM,則可能出現(xiàn)UI操作的沖突;如果JS是多線程的話,在多線程的交互下,處于UI中的DOM節(jié)點就可能成為一個臨界資源,假設(shè)存在兩個線程同時操作一個DOM,一個負(fù)責(zé)修改一個負(fù)責(zé)刪除,那么這個時候就需要瀏覽器來裁決如何生效哪個線程的執(zhí)行結(jié)果,當(dāng)然我們可以通過鎖來解決上面的問題。但為了避免因為引入了鎖而帶來更大的復(fù)雜性,JS在最初就選擇了單線程執(zhí)行。
GUI渲染線程與JS引擎線程互斥的,是由于JavaScript是可操縱DOM的,如果在修改這些元素屬性同時渲染界面(即JavaScript線程和UI線程同時運行),那么渲染線程前后獲得的元素數(shù)據(jù)就可能不一致。當(dāng)JavaScript引擎執(zhí)行時GUI線程會被掛起,GUI更新會被保存在一個隊列中等到引擎線程空閑時立即被執(zhí)行。由于GUI渲染線程與JS執(zhí)行線程是互斥的關(guān)系,當(dāng)瀏覽器在執(zhí)行JS程序的時候,GUI渲染線程會被保存在一個隊列中,直到JS程序執(zhí)行完成,才會接著執(zhí)行。因此如果JS執(zhí)行的時間過長,這樣就會造成頁面的渲染不連貫,導(dǎo)致頁面渲染加載阻塞的感覺。
瀏覽器定時計數(shù)器并不是由JS引擎計數(shù)的, 因為JS引擎是單線程的, 如果處于阻塞線程狀態(tài)就會影響記計時的準(zhǔn)確, 因此通過單獨線程來計時并觸發(fā)定時是更為合理的方案。
當(dāng)一個事件被觸發(fā)時該線程會把事件添加到待處理隊列的隊尾,等待JS引擎的處理。這些事件可以是當(dāng)前執(zhí)行的代碼塊如定時任務(wù)、也可來自瀏覽器內(nèi)核的其他線程如鼠標(biāo)點擊、AJAX異步請求等,但由于JS的單線程關(guān)系所有這些事件都得排隊等待JS引擎處理。
在XMLHttpRequest在連接后是通過瀏覽器新開一個線程請求,將檢測到狀態(tài)變更時,如果設(shè)置有回調(diào)函數(shù),異步線程就產(chǎn)生狀態(tài)變更事件放到JS引擎的處理隊列中等待處理。
用戶請求的HTML文本(text/html)通過瀏覽器的網(wǎng)絡(luò)層到達(dá)渲染引擎后,渲染工作開始。每次通常渲染不會超過8K的數(shù)據(jù)塊,其中基礎(chǔ)的渲染流程圖:
webkit引擎渲染的詳細(xì)流程,其他引擎渲染流程稍有不同:
渲染流程有四個主要步驟:
以上步驟是一個漸進(jìn)的過程,為了提高用戶體驗,渲染引擎試圖盡可能快的把結(jié)果顯示給最終用戶。它不會等到所有HTML都被解析完才創(chuàng)建并布局渲染樹。它會在從網(wǎng)絡(luò)層獲取文檔內(nèi)容的同時把已經(jīng)接收到的局部內(nèi)容先展示出來。
DOM樹的構(gòu)建過程是一個深度遍歷過程:當(dāng)前節(jié)點的所有子節(jié)點都構(gòu)建好后才會去構(gòu)建當(dāng)前節(jié)點的下一個兄弟節(jié)點。DOM樹的根節(jié)點就是document對象。
DOM樹的生成過程中可能會被CSS和JS的加載執(zhí)行阻塞,具體可以參見下一章。當(dāng)HTML文檔解析過程完畢后,瀏覽器繼續(xù)進(jìn)行標(biāo)記為deferred模式的腳本加載,然后就是整個解析過程的實際結(jié)束觸發(fā)DOMContentLoaded事件,并在async文檔文檔執(zhí)行完之后觸發(fā)load事件。
生成DOM樹的同時會生成樣式結(jié)構(gòu)體CSSOM(CSS Object Model)Tree,再根據(jù)CSSOM和DOM樹構(gòu)造渲染樹Render Tree,渲染樹包含帶有顏色,尺寸等顯示屬性的矩形,這些矩形的順序與顯示順序基本一致。從MVC的角度來說,可以將Render樹看成是V,DOM樹與CSSOM樹看成是M,C則是具體的調(diào)度者,比HTMLDocumentParser等。
可以這么說,沒有DOM樹就沒有Render樹,但是它們之間不是簡單的一對一的關(guān)系。Render樹是用于顯示,那不可見的元素當(dāng)然不會在這棵樹中出現(xiàn)了,譬如 <head>。除此之外,display等于none的也不會被顯示在這棵樹里頭,但是visibility等于hidden的元素是會顯示在這棵樹里頭的。
DOM對象類型很豐富,什么head、title、div,而Render樹相對來說就比較單一了,畢竟它的職責(zé)就是為了以后的顯示渲染用嘛。Render樹的每一個節(jié)點我們叫它渲染器renderer。
一棵Render樹大概是醬紫,左邊是DOM樹,右邊是Render樹:
從上圖我們可以看出,renderer與DOM元素是相對應(yīng)的,但并不是一一對應(yīng),有些DOM元素沒有對應(yīng)的renderer,而有些DOM元素卻對應(yīng)了好幾個renderer,對應(yīng)多個renderer的情況是普遍存在的,就是為了解決一個renderer描述不清楚如何顯示出來的問題,譬如有下拉列表的select元素,我們就需要三個renderer:一個用于顯示區(qū)域,一個用于下拉列表框,還有一個用于按鈕。
另外,renderer與DOM元素的位置也可能是不一樣的。那些添加了 float或者 position:absolute的元素,因為它們脫離了正常的文檔流,構(gòu)造Render樹的時候會針對它們實際的位置進(jìn)行構(gòu)造。
上面確定了renderer的樣式規(guī)則后,然后就是重要的顯示元素布局了。當(dāng)renderer構(gòu)造出來并添加到Render樹上之后,它并沒有位置跟大小信息,為它確定這些信息的過程,接下來是布局(layout)。
瀏覽器進(jìn)行頁面布局基本過程是以瀏覽器可見區(qū)域為畫布,左上角為 (0,0)基礎(chǔ)坐標(biāo),從左到右,從上到下從DOM的根節(jié)點開始畫,首先確定顯示元素的大小跟位置,此過程是通過瀏覽器計算出來的,用戶CSS中定義的量未必就是瀏覽器實際采用的量。如果顯示元素有子元素得先去確定子元素的顯示信息。
布局階段輸出的結(jié)果稱為box盒模型(width,height,margin,padding,border,left,top,…),盒模型精確表示了每一個元素的位置和大小,并且所有相對度量單位此時都轉(zhuǎn)化為了絕對單位。
在繪制(painting)階段,渲染引擎會遍歷Render樹,并調(diào)用renderer的 paint() 方法,將renderer的內(nèi)容顯示在屏幕上。繪制工作是使用UI后端組件完成的。
回流(reflow):當(dāng)瀏覽器發(fā)現(xiàn)某個部分發(fā)生了點變化影響了布局,需要倒回去重新渲染。reflow 會從 <html>這個 root frame 開始遞歸往下,依次計算所有的結(jié)點幾何尺寸和位置。reflow 幾乎是無法避免的。現(xiàn)在界面上流行的一些效果,比如樹狀目錄的折疊、展開(實質(zhì)上是元素的顯示與隱藏)等,都將引起瀏覽器的 reflow。鼠標(biāo)滑過、點擊……只要這些行為引起了頁面上某些元素的占位面積、定位方式、邊距等屬性的變化,都會引起它內(nèi)部、周圍甚至整個頁面的重新渲染。通常我們都無法預(yù)估瀏覽器到底會 reflow 哪一部分的代碼,它們都彼此相互影響著。
重繪(repaint):改變某個元素的背景色、文字顏色、邊框顏色等等不影響它周圍或內(nèi)部布局的屬性時,屏幕的一部分要重畫,但是元素的幾何尺寸沒有變。
每次Reflow,Repaint后瀏覽器還需要合并渲染層并輸出到屏幕上。所有的這些都會是動畫卡頓的原因。Reflow 的成本比 Repaint 的成本高得多的多。一個結(jié)點的 Reflow 很有可能導(dǎo)致子結(jié)點,甚至父點以及同級結(jié)點的 Reflow 。在一些高性能的電腦上也許還沒什么,但是如果 Reflow 發(fā)生在手機(jī)上,那么這個過程是延慢加載和耗電的。可以在csstrigger上查找某個css屬性會觸發(fā)什么事件。
reflow與repaint的時機(jī):
在瀏覽器拿到HTML、CSS、JS等外部資源到渲染出頁面的過程,有一個重要的概念關(guān)鍵渲染路徑(Critical Rendering Path)。例如為了保障首屏內(nèi)容的最快速顯示,通常會提到一個漸進(jìn)式頁面渲染,但是為了漸進(jìn)式頁面渲染,就需要做資源的拆分,那么以什么粒度拆分、要不要拆分,不同頁面、不同場景策略不同。具體方案的確定既要考慮體驗問題,也要考慮工程問題。了解原理可以讓我們更好的優(yōu)化關(guān)鍵渲染路徑,從而獲得更好的用戶體驗。
現(xiàn)代瀏覽器總是并行加載資源,例如,當(dāng) HTML 解析器(HTML Parser)被腳本阻塞時,解析器雖然會停止構(gòu)建 DOM,但仍會識別該腳本后面的資源,并進(jìn)行預(yù)加載。
同時,由于下面兩點:
存在阻塞的 CSS 資源時,瀏覽器會延遲 JavaScript 的執(zhí)行和 DOM 構(gòu)建。另外:
所以,script 標(biāo)簽的位置很重要。實際使用時,可以遵循下面兩個原則:
下面來看看 CSS 與 JavaScript 是具體如何阻塞資源的。
<style>
p { color: red; }
</style>
<link rel="stylesheet" href="index.css">
這樣的 link 標(biāo)簽(無論是否 inline)會被視為阻塞渲染的資源,瀏覽器會優(yōu)先處理這些 CSS 資源,直至 CSSOM 構(gòu)建完畢。
渲染樹(Render-Tree)的關(guān)鍵渲染路徑中,要求同時具有 DOM 和 CSSOM,之后才會構(gòu)建渲染樹。即,HTML 和 CSS 都是阻塞渲染的資源。HTML 顯然是必需的,因為包括我們希望顯示的文本在內(nèi)的內(nèi)容,都在 DOM 中存放,那么可以從 CSS 上想辦法。
最容易想到的當(dāng)然是精簡 CSS 并盡快提供它。除此之外,還可以用媒體類型(media type)和媒體查詢(media query)來解除對渲染的阻塞。
<link href="index.css" rel="stylesheet">
<link href="print.css" rel="stylesheet" media="print">
<link href="other.css" rel="stylesheet" media="(min-width: 30em) and (orientation: landscape)">
第一個資源會加載并阻塞。第二個資源設(shè)置了媒體類型,會加載但不會阻塞,print 聲明只在打印網(wǎng)頁時使用。第三個資源提供了媒體查詢,會在符合條件時阻塞渲染。
關(guān)于CSS加載的阻塞情況:
沒有js的理想情況下,html與css會并行解析,分別生成DOM與CSSOM,然后合并成Render Tree,進(jìn)入Rendering Pipeline;但如果有js,css加載會阻塞后面js語句的執(zhí)行,而(同步)js腳本執(zhí)行會阻塞其后的DOM解析(所以通常會把css放在頭部,js放在body尾)
JavaScript 的情況比 CSS 要更復(fù)雜一些。如果沒有 defer 或 async,瀏覽器會立即加載并執(zhí)行指定的腳本,“立即”指的是在渲染該 script 標(biāo)簽之下的HTML元素之前,也就是說不等待后續(xù)載入的HTML元素,讀到就加載并執(zhí)行。觀察下面的代碼:
<p>Do not go gentle into that good night,</p>
<script>console.log("inline1")</script>
<p>Old age should burn and rave at close of day;</p>
<script src="app.js"></script>
<p>Rage, rage against the dying of the light.</p>
<script src="app.js"></script>
<p>Old age should burn and rave at close of day;</p>
<script>console.log("inline2")</script>
<p>Rage, rage against the dying of the light.</p>
這里的 script 標(biāo)簽會阻塞 HTML 解析,無論是不是 inline-script。上面的 P 標(biāo)簽會從上到下解析,這個過程會被兩段 JavaScript 分別打斷一次(加載、執(zhí)行)。
解析過程中無論遇到的JavaScript是內(nèi)聯(lián)還是外鏈,只要瀏覽器遇到 script 標(biāo)記,喚醒 JavaScript解析器,就會進(jìn)行暫停 (blocked )瀏覽器解析HTML,并等到 CSSOM 構(gòu)建完畢,才去執(zhí)行js腳本。因為腳本中可能會操作DOM元素,而如果在加載執(zhí)行腳本的時候DOM元素并沒有被解析,腳本就會因為DOM元素沒有生成取不到響應(yīng)元素,所以實際工程中,我們常常將資源放到文檔底部。
defer 與 async 可以改變之前的那些阻塞情形,這兩個屬性都會使 script 異步加載,然而執(zhí)行的時機(jī)是不一樣的。注意 async 與 defer 屬性對于 inline-script 都是無效的,所以下面這個示例中三個 script 標(biāo)簽的代碼會從上到下依次執(zhí)行。
<script async>
console.log("1")
</script>
<script defer>
console.log("2")
</script>
<script>
console.log("3")
</script>
上面腳本會按需輸出 1 2 3,故,下面兩節(jié)討論的內(nèi)容都是針對設(shè)置了 src 屬性的 script 標(biāo)簽。
先放個熟悉的圖~
藍(lán)色線代表網(wǎng)絡(luò)讀取,紅色線代表執(zhí)行時間,這倆都是針對腳本的;綠色線代表 HTML 解析。
<script src="app1.js" defer></script>
<script src="app2.js" defer></script>
<script src="app3.js" defer></script>
defer 屬性表示延遲執(zhí)行引入 JavaScript,即 JavaScript 加載時 HTML 并未停止解析,這兩個過程是并行的。整個 document 解析完畢且 defer-script 也加載完成之后(這兩件事情的順序無關(guān)),會執(zhí)行所有由 defer-script 加載的 JavaScript 代碼,再觸發(fā) DOMContentLoaded(初始的 HTML 文檔被完全加載和解析完成之后觸發(fā),無需等待樣式表圖像和子框架的完成加載) 事件 。
defer 不會改變 script 中代碼的執(zhí)行順序,示例代碼會按照 1、2、3 的順序執(zhí)行。所以,defer 與相比普通 script,有兩點區(qū)別:載入 JavaScript 文件時不阻塞 HTML 的解析,執(zhí)行階段被放到 HTML 標(biāo)簽解析完成之后。
async 屬性表示異步執(zhí)行引入的 JavaScript,與 defer 的區(qū)別在于,如果已經(jīng)加載好,就會開始執(zhí)行,無論此刻是 HTML 解析階段還是 DOMContentLoaded 觸發(fā)(HTML解析完成事件)之后。需要注意的是,這種方式加載的 JavaScript 依然會阻塞 load 事件。換句話說,async-script 可能在 DOMContentLoaded 觸發(fā)之前或之后執(zhí)行,但一定在 load 觸發(fā)之前執(zhí)行。
從上一段也能推出,多個 async-script 的執(zhí)行順序是不確定的,誰先加載完誰執(zhí)行。值得注意的是,向 document 動態(tài)添加 script 標(biāo)簽時,async 屬性默認(rèn)是 true。
使用 document.createElement 創(chuàng)建的 script 默認(rèn)是異步的,示例如下。
console.log(document.createElement("script").async); // true
所以,通過動態(tài)添加 script 標(biāo)簽引入 JavaScript 文件默認(rèn)是不會阻塞頁面的。如果想同步執(zhí)行,需要將 async 屬性人為設(shè)置為 false。
如果使用 document.createElement 創(chuàng)建 link 標(biāo)簽會怎樣呢?
const style=document.createElement("link");
style.rel="stylesheet";
style.href="index.css";
document.head.appendChild(style); // 阻塞?
其實這只能通過試驗確定,已知的是,Chrome 中已經(jīng)不會阻塞渲染,F(xiàn)irefox、IE 在以前是阻塞的,現(xiàn)在會怎樣目前不太清楚。
結(jié)合渲染流程,可以針對性的優(yōu)化渲染性能:
這里主要參考Google的瀏覽器渲染性能的基礎(chǔ)講座,想看更詳細(xì)內(nèi)容可以去瞅瞅~
setTimeout(callback)和setInterval(callback)無法保證callback函數(shù)的執(zhí)行時機(jī),很可能在幀結(jié)束的時候執(zhí)行,從而導(dǎo)致丟幀,如下圖:
requestAnimationFrame(callback)可以保證callback函數(shù)在每幀動畫開始的時候執(zhí)行。注意:jQuery3.0.0以前版本的animate函數(shù)就是用setTimeout來實現(xiàn)動畫,可以通過jquery-requestAnimationFrame這個補(bǔ)丁來用requestAnimationFrame替代setTimeout
JS代碼運行在瀏覽器的主線程上,與此同時,瀏覽器的主線程還負(fù)責(zé)樣式計算、布局、繪制的工作,如果JavaScript代碼運行時間過長,就會阻塞其他渲染工作,很可能會導(dǎo)致丟幀。前面提到每幀的渲染應(yīng)該在16ms內(nèi)完成,但在動畫過程中,由于已經(jīng)被占用了不少時間,所以JavaScript代碼運行耗時應(yīng)該控制在3-4毫秒。如果真的有特別耗時且不操作DOM元素的純計算工作,可以考慮放到Web Workers中執(zhí)行。
var dataSortWorker=new Worker("sort-worker.js");
dataSortWorker.postMesssage(dataToSort);
// 主線程不受Web Workers線程干擾
dataSortWorker.addEventListener('message', function(evt) {
var sortedData=e.data;
// Web Workers線程執(zhí)行結(jié)束 // ...});
由于Web Workers不能操作DOM元素的限制,所以只能做一些純計算的工作,對于很多需要操作DOM元素的邏輯,可以考慮分步處理,把任務(wù)分為若干個小任務(wù),每個任務(wù)都放到 requestAnimationFrame中回調(diào)執(zhí)行
var taskList=breakBigTaskIntoMicroTasks(monsterTaskList);
requestAnimationFrame(processTaskList);
function processTaskList(taskStartTime) {
var nextTask=taskList.pop();
// 執(zhí)行小任務(wù)
processTask(nextTask);
if (taskList.length > 0) {
requestAnimationFrame(processTaskList);
}
}
打開 ChromeDevTools>Timeline>JSProfile,錄制一次動作,然后分析得到的細(xì)節(jié)信息,從而發(fā)現(xiàn)問題并修復(fù)問題。
添加或移除一個DOM元素、修改元素屬性和樣式類、應(yīng)用動畫效果等操作,都會引起DOM結(jié)構(gòu)的改變,從而導(dǎo)致瀏覽器要repaint或者reflow。那么這里可以采取一些措施。
盡量保持class的簡短,或者使用Web Components框架。
.box:nth-last-child(-n+1) .title {}
// 改善后
.final-box-title {}
由于瀏覽器的優(yōu)化,現(xiàn)代瀏覽器的樣式計算直接對目標(biāo)元素執(zhí)行,而不是對整個頁面執(zhí)行,所以我們應(yīng)該盡可能減少需要執(zhí)行樣式計算的元素的個數(shù)。
布局就是計算DOM元素的大小和位置的過程,如果你的頁面中包含很多元素,那么計算這些元素的位置將耗費很長時間。布局的主要消耗在于:1. 需要布局的DOM元素的數(shù)量;2. 布局過程的復(fù)雜程度
當(dāng)你修改了元素的屬性之后,瀏覽器將會檢查為了使這個修改生效是否需要重新計算布局以及更新渲染樹,對于DOM元素的幾何屬性修改,比如width/height/left/top等,都需要重新計算布局。對于不能避免的布局,可以使用Chrome DevTools工具的Timeline查看布局的耗時,以及受影響的DOM元素數(shù)量。
老的布局模型以相對/絕對/浮動的方式將元素定位到屏幕上,而Floxbox布局模型用流式布局的方式將元素定位到屏幕上。通過一個小實驗可以看出兩種布局模型的性能差距,同樣對1300個元素布局,浮動布局耗時14.3ms,F(xiàn)lexbox布局耗時3.5ms。IE10+支持。
根據(jù)渲染流程,JS腳本是在layout之前執(zhí)行,但是我們可以強(qiáng)制瀏覽器在執(zhí)行JS腳本之前先執(zhí)行布局過程,這就是所謂的強(qiáng)制同步布局。
requestAnimationFrame(logBoxHeight);
// 先寫后讀,觸發(fā)強(qiáng)制布局
function logBoxHeight() {
// 更新box樣式
box.classList.add('super-big');
// 為了返回box的offersetHeight值
// 瀏覽器必須先應(yīng)用屬性修改,接著執(zhí)行布局過程
console.log(box.offsetHeight);
}
// 先讀后寫,避免強(qiáng)制布局
function logBoxHeight() {
// 獲取box.offsetHeight
console.log(box.offsetHeight);
// 更新box樣式
box.classList.add('super-big');
}
在JS腳本運行的時候,它能獲取到的元素樣式屬性值都是上一幀畫面的,都是舊的值。因此,如果你在當(dāng)前幀獲取屬性之前又對元素節(jié)點有改動,那就會導(dǎo)致瀏覽器必須先應(yīng)用屬性修改,結(jié)果執(zhí)行布局過程,最后再執(zhí)行JS邏輯。
如果連續(xù)快速的多次觸發(fā)強(qiáng)制同步布局,那么結(jié)果更糟糕。比如下面的例子,獲取box的屬性,設(shè)置到paragraphs上,由于每次設(shè)置paragraphs都會觸發(fā)樣式計算和布局過程,而下一次獲取box的屬性必須等到上一步設(shè)置結(jié)束之后才能觸發(fā)。
function resizeWidth() { // 會讓瀏覽器陷入'讀寫讀寫'循環(huán) for (var i=0; i < paragraphs.length; i++) { paragraphs[i].style.width=box.offsetWidth + 'px'; }}
// 改善后方案var width=box.offsetWidth;function resizeWidth() { for (var i=0; i < paragraphs.length; i++) { paragraphs[i].style.width=width + 'px'; }}
注意:可以使用FastDOM來確保讀寫操作的安全,從而幫你自動完成讀寫操作的批處理,還能避免意外地觸發(fā)強(qiáng)制同步布局或快速連續(xù)布局,消除大量操作DOM的時候的布局抖動。
Paint就是填充像素的過程,通常這個過程是整個渲染流程中耗時最長的一環(huán),因此也是最需要避免發(fā)生的一環(huán)。如果Layout被觸發(fā),那么接下來元素的Paint一定會被觸發(fā)。當(dāng)然純粹改變元素的非幾何屬性,也可能會觸發(fā)Paint,比如背景、文字顏色、陰影效果等。
繪制并非總是在內(nèi)存中的單層畫面里完成的,實際上,瀏覽器在必要時會將一幀畫面繪制成多層畫面,然后將這若干層畫面合并成一張圖片顯示到屏幕上。這種繪制方式的好處是,使用transform來實現(xiàn)移動效果的元素將會被正常繪制,同時不會觸發(fā)其他元素的繪制。
瀏覽器會把相鄰區(qū)域的渲染任務(wù)合并在一起進(jìn)行,所以需要對動畫效果進(jìn)行精密設(shè)計,以保證各自的繪制區(qū)域不會有太多重疊。另外可以實現(xiàn)同樣效果的不同方式,應(yīng)該采用性能更好的那種。
打開DevTools,在彈出的面板中,選中 MoreTools>Rendering選項卡下的Paint flashing,這樣每當(dāng)頁面發(fā)生繪制的時候,屏幕就會閃現(xiàn)綠色的方框。通過該工具可以檢查Paint發(fā)生的區(qū)域和時機(jī)是不是可以被優(yōu)化。通過Chrome DevTools中的 Timeline>Paint選項可以查看更細(xì)節(jié)的Paint信息
使用transform/opacity實現(xiàn)動畫效果,會跳過渲染流程的布局和繪制環(huán)節(jié),只做渲染層的合并。
TypeFuncPositiontransform: translate(-px,-px)Scaletransform: scale(-)Rotationtransform: rotate(-deg)Skewtransform: skew(X/Y)(-deg)Matrixtransform: matrix(3d)(..)Opacityopacity: 0-1
使用transform/opacity的元素必須獨占一個渲染層,所以必須提升該元素到單獨的渲染層。
應(yīng)用動畫效果的元素應(yīng)該被提升到其自有的渲染層,但不要濫用。在頁面中創(chuàng)建一個新的渲染層最好的方式就是使用CSS屬性will-change,對于目前還不支持will-change屬性、但支持創(chuàng)建渲染層的瀏覽器,可以通過3D transform屬性來強(qiáng)制瀏覽器創(chuàng)建一個新的渲染層。需要注意的是,不要創(chuàng)建過多的渲染層,這意味著新的內(nèi)存分配和更復(fù)雜的層管理。注意,IE11,Edge17都不支持這一屬性。
.moving-element { will-change: transform; transform: translateZ(0);}
盡管提升渲染層看起來很誘人,但不能濫用,因為更多的渲染層意味著更多的額外的內(nèi)存和管理資源,所以當(dāng)且僅當(dāng)需要的時候才為元素創(chuàng)建渲染層。
* { will-change: transform; transform: translateZ(0);}
開啟 Timeline>Paint選項,然后錄制一段時間的操作,選擇單獨的幀,看到每個幀的渲染細(xì)節(jié),在ESC彈出框有個Layers選項,可以看到渲染層的細(xì)節(jié),有多少渲染層,為何被創(chuàng)建?
用戶輸入事件處理函數(shù)會在運行時阻塞幀的渲染,并且會導(dǎo)致額外的布局發(fā)生。
理想情況下,當(dāng)用戶和頁面交互,頁面的渲染層合并線程將接收到這個事件并移動元素。這個響應(yīng)過程是不需要主線程參與,不會導(dǎo)致JavaScript、布局和繪制過程發(fā)生。但是如果被觸摸的元素綁定了輸入事件處理函數(shù),比如touchstart/touchmove/touchend,那么渲染層合并線程必須等待這些被綁定的處理函數(shù)執(zhí)行完畢才能執(zhí)行,也就是用戶的滾動頁面操作被阻塞了,表現(xiàn)出的行為就是滾動出現(xiàn)延遲或者卡頓。
簡而言之就是你必須確保用戶輸入事件綁定的任何處理函數(shù)都能夠快速的執(zhí)行完畢,以便騰出時間來讓渲染層合并線程完成他的工作。
輸入事件處理函數(shù),比如scroll/touch事件的處理,都會在requestAnimationFrame之前被調(diào)用執(zhí)行。因此,如果你在上述輸入事件的處理函數(shù)中做了修改樣式屬性的操作,那么這些操作就會被瀏覽器暫存起來,然后在調(diào)用requestAnimationFrame的時候,如果你在一開始就做了讀取樣式屬性的操作,那么將會觸發(fā)瀏覽器的強(qiáng)制同步布局操作。
通過requestAnimationFrame可以對樣式修改操作去抖動,同時也可以使你的事件處理函數(shù)變得更輕
為一個產(chǎn)品經(jīng)理,學(xué)習(xí)并掌握基礎(chǔ)的技術(shù)知識是非常重要的。而本篇文章就重點討論前端部分,講一講HTML和CSS、JavaScript的知識要點。
為什么學(xué)習(xí)技術(shù)?我想上面這段話給予了非常好的解釋。
在當(dāng)今時代,職能劃分越來越精細(xì),但你仍然可以看到不同行業(yè)、領(lǐng)域、職業(yè)都密不可分,他們互相融合和滲透。設(shè)計和技術(shù)亦是如此,產(chǎn)品經(jīng)理和程序員亦是如此。
不難發(fā)現(xiàn),成功的產(chǎn)品經(jīng)理有一個共性,那就是在精通設(shè)計與用戶體驗的基礎(chǔ)上,精通技術(shù)或者如何運用技術(shù)。前者如張小龍,后者如喬布斯。
這并非偶然,而是因為技術(shù)和設(shè)計原本就是一體的:設(shè)計使用當(dāng)下的技術(shù)來解決問題,因此設(shè)計中就包含了對技術(shù)的考量與使用。
你可能會說,作為一個執(zhí)行層的產(chǎn)品,并不需要了解這些宏觀的問題。你可能還會說,在技術(shù)如此成熟的當(dāng)下,產(chǎn)品可以盡情發(fā)揮想象或者直接套用現(xiàn)有設(shè)計模式。
可是事實表明,在微觀層面設(shè)計和技術(shù)達(dá)成共識可以顯著提升合作質(zhì)量和效率,這也正是各個平臺(不管是安卓、iOS,還是小程序、網(wǎng)站)推出設(shè)計/開發(fā)規(guī)范的原因;另一方面,只有深諳設(shè)計模式及其背后的技術(shù)基礎(chǔ),才能打破模式、迸發(fā)創(chuàng)意。
在學(xué)習(xí)技術(shù)的同時,我剛好在看交互設(shè)計精髓這本書。書中提到了設(shè)計可以分為對內(nèi)容、形態(tài)、行為的設(shè)計。我驚喜地發(fā)現(xiàn)這些設(shè)計領(lǐng)域剛好可以對應(yīng)一些技術(shù)語言或?qū)崿F(xiàn)方案,例如HTML、CSS、JS,又例如MVC(一種開發(fā)模式,下篇文章會提及)。
所以對產(chǎn)品經(jīng)理而言,學(xué)習(xí)技術(shù)可以幫助你更好地理解產(chǎn)品設(shè)計,更好地執(zhí)行產(chǎn)品工作,更加順暢地與開發(fā)人員合作。
最初我學(xué)習(xí)技術(shù)的動機(jī)來自于想寫一份好的PRD(我的一篇專欄文章講述了這個主題,詳見文章底部的參考資料),為此,我需要知道前端、后端分別想從需求文檔中獲取哪些信息以及他們會如何利用這些信息進(jìn)行開發(fā)。
由此出發(fā),我看了三本書,分別介紹了HTML和CSS、JavaScript、Python這些編程語言在前端開發(fā)、后端開發(fā)中的應(yīng)用。
在這里,簡單總結(jié)一下這些書中提及的技術(shù)原理,并結(jié)合自己的思考和實踐指出這些技術(shù)原理在產(chǎn)品工作中的應(yīng)用,希望可以對自己以及他人有所幫助。
由于內(nèi)容較多,這篇文章將重點討論前端部分,涉及HTML和CSS、JavaScript的內(nèi)容;關(guān)于后端的內(nèi)容,將在下一篇文章中分享。
下面將分別介紹一下HTML、CSS、JavaScript在web前端開發(fā)中的應(yīng)用。
HTML翻譯作“超文本標(biāo)記語言”,和現(xiàn)在廣為使用的markdown一樣,作為一種標(biāo)記語言,HTML通過一套既定的語法來標(biāo)記文本或者富文本內(nèi)容,從而為這些內(nèi)容劃定結(jié)構(gòu)。
這些HTML格式的文件通常存儲在服務(wù)器上,瀏覽器通過互聯(lián)網(wǎng)向服務(wù)器請求這些頁面資源,然后解析并呈現(xiàn)出用戶直接看到的頁面。
在瀏覽器中打開任意web頁面,并檢查其HTML元素,都可以看到其大體的結(jié)構(gòu):
<head>元素存放網(wǎng)頁的基本信息,<body>元素里的內(nèi)容就是用戶將在瀏覽器看到的東西,此外還有許許多多的元素(如:<h>、<p>、<a>、<img>、<div>、<li>、<div>)嵌套起來,共同構(gòu)建了網(wǎng)頁的結(jié)構(gòu)。
這些元素一般由開始標(biāo)記、結(jié)束標(biāo)記、內(nèi)容、屬性等部分構(gòu)成,其中“屬性”可以幫助對這些元素進(jìn)行更加具體的描述。
舉例說明:標(biāo)題元素的寫法如下,<h1></h1>為開始和結(jié)束標(biāo)記,中間包圍的即為標(biāo)題元素的內(nèi)容。
<h1>這里是標(biāo)題</h1>
表單元素的常見寫法如下,其中<form>為開始標(biāo)記,有action 和method兩個屬性,</form>為結(jié)束標(biāo)記,省略號的位置放置表單元素包圍的所有內(nèi)容和輸入控件。
<form action="http://123.com/test.py" method="POST"> ... </form>
在同一網(wǎng)站內(nèi)部,可以通過在<a>元素中使用相對資源路徑鏈接到一個新的頁面。
在網(wǎng)站之外,則可以使用URL(統(tǒng)一資源定位符,用文件地址來標(biāo)識web上的任何資源),通過絕對路徑直接向服務(wù)器請求頁面資源。
URL的結(jié)構(gòu)通常如下:通信協(xié)議(http、https、file等)、服務(wù)器名(常見的www)、域名(服務(wù)器IP地址的簡便記法)、資源的絕對路徑、隨URL傳遞的參數(shù)。
我們設(shè)計出一個頁面,頁面上每一個元素(包括控件、信息、圖片)都由HTML元素(例如<input>、<p>、<img>)來定義或者說實現(xiàn)。
這些頁面控件、信息、圖片的屬性同樣可以由HTML元素屬性來實現(xiàn),例如通過設(shè)置placeholder屬性值,可以為輸入框加入提示文案,通過設(shè)置draggable屬性,讓元素可以拖動。
在頁面結(jié)構(gòu)層面了解設(shè)計與技術(shù)的關(guān)聯(lián),可以幫助從技術(shù)的角度撰寫產(chǎn)品方案。
(1)一方面,知道了技術(shù)會把頁面結(jié)構(gòu)拆解為元素及其屬性,我們在寫文檔時,也應(yīng)當(dāng)以這種思路拆解并描述頁面,寫清楚頁面有哪些信息,哪些輸入控件,哪些顯示控件,這些控件的屬性是否需要自定義,還是直接使用瀏覽器或操作系統(tǒng)的默認(rèn)樣式。
(2)另一方面,從技術(shù)角度了解元素及其基本屬性,就可以減少產(chǎn)品方案中對細(xì)節(jié)描述的遺漏。
CSS翻譯作“層疊樣式表”,和HTML一樣,CSS也是我們用來創(chuàng)建網(wǎng)頁的語言,HTML定義頁面的內(nèi)容和結(jié)構(gòu),而CSS定義了web頁面的樣式和表現(xiàn)。
具體來說,通過在HTML的<link>或<style>中鏈接到CSS樣式表,CSS就可以通過其眾多的樣式屬性,控制HTML中元素的外觀表現(xiàn)。
CSS的樣式屬性,例如color、border、font-style等,可以控制HTML元素的字體顏色、邊框、字體樣式等等。此外,CSS將每個HTML元素看作一個盒子(這也就是“盒模型”),以控制其內(nèi)外邊距、邊框等。
還可以使用CSS靈活的對頁面進(jìn)行布局:
(1)流體布局,擴(kuò)展瀏覽器窗口時,頁面中的內(nèi)容會根據(jù)一定規(guī)則自動擴(kuò)展以適應(yīng)頁面
(2)凍結(jié)布局,通過設(shè)定頁面寬度,所有頁面元素都將固定在頁面上,不隨瀏覽器窗口大小而改變布局
(3)凝膠布局,鎖定頁面內(nèi)容區(qū)域的寬度,但外邊距會根據(jù)窗口大小進(jìn)行擴(kuò)展收縮,從而使得頁面在瀏覽器中居中
(4)絕對定位,使得元素相對于頁面固定
(5)固定定位,使元素相對于瀏覽器窗口固定不動
(6)相對定位
(7)表格顯示
(8)浮動布局
CSS可以靈活適配,可以為一個HTML頁面設(shè)置多個樣式表(或者在CSS中設(shè)置@media規(guī)則),用于不同的場景展示、匹配不同的設(shè)備或者適應(yīng)不同的窗口寬度。
(1)在詳細(xì)的產(chǎn)品設(shè)計實現(xiàn)方案中,不僅要定義頁面具有哪些元素,也要定義這些元素的樣式、外觀、動效等。
在技術(shù)實現(xiàn)上,這是兩個不同的層面,由不同的語言來實現(xiàn);因而在文檔寫作中,也應(yīng)該將元素與其樣式區(qū)分開來進(jìn)行描述,而不是將所有說明混雜在一起(雖然這個工作往往需要和設(shè)計進(jìn)行配合)。
例如在描述按鈕時,不僅要指明按鈕元素的基本屬性(例如按鈕文本、按鈕類別),也應(yīng)該指出按鈕在不同狀態(tài)的不同樣式。
(2)在宏觀層面,隨著設(shè)備形態(tài)的多樣化,樣式適配也變成了一個很大的主題,也應(yīng)是產(chǎn)品設(shè)計中應(yīng)該考慮的重要方面。
以網(wǎng)站設(shè)計而言,同樣的內(nèi)容元素,在手機(jī)和PC上往往需要定義不同的樣式,對此有很多技術(shù)實現(xiàn)方案,產(chǎn)品經(jīng)理應(yīng)對樣式適配有基本認(rèn)知,才能與技術(shù)共同商定適配方案。
前面提到,使用HTML標(biāo)記和CSS可以幫助搭建web頁面,而JavaScript的使用,可以為這些頁面增加行為和功能(比如對用戶行為作出響應(yīng)、處理事件、更新頁面、與服務(wù)端通信),從而成為真正意義上的web應(yīng)用。
HTML5是HTML的最新版本,但實際上當(dāng)我們談?wù)揌TML5時,不僅僅指代標(biāo)記,而是HTML標(biāo)記、CSS樣式、JavaScript腳本這些技術(shù)的結(jié)合,這些技術(shù)共同幫助構(gòu)件web應(yīng)用。
通過在代碼中引用JavaScript文件或者直接將腳本放在<script>元素中,就可以在web頁面中增加JavaScript。
瀏覽器在加載頁面時,對于HTML中每一個元素,會創(chuàng)建一個表示該元素的對象,把它與所有其他元素一起放在一個類似樹的結(jié)構(gòu)中,這個樹即為DOM(文檔對象模型 Document Object Model),DOM是瀏覽器對于頁面結(jié)構(gòu)的內(nèi)部表示。
JavaScript可以通過DOM對頁面元素進(jìn)行訪問、修改、增刪。例如,可以使用document.getElementById在DOM中查找元素,使用元素的innderHTML屬性修改其內(nèi)容,然后瀏覽器會近乎實時的對DOM以及頁面進(jìn)行更新。
瀏覽器在顯示頁面時,會有許多事件發(fā)生,例如按鈕點擊事件、數(shù)據(jù)到達(dá)事件、時間到期事件等,使用JavaScript編寫事件處理程序,可以對這些事件進(jìn)行處理。
以表單的按鈕點擊事件為例進(jìn)行說明:用戶在輸入框輸入信息,點擊按鈕提交信息后,可以在當(dāng)前頁查看已經(jīng)提交的信息。
實現(xiàn)思路如下:首先通過DOM獲取按鈕元素,并為按鈕的onclick屬性設(shè)置一個處理程序。
該處理程序需要通過DOM訪問輸入元素,并通過輸入元素的value屬性獲取用戶輸入值,最后再通過DOM增加到頁面中。這樣在用戶點擊按鈕時,就會執(zhí)行該處理程序,獲取用戶輸入并增加到頁面中,以此實現(xiàn)用戶與系統(tǒng)的交互。
通過使用JavaScript API(API即應(yīng)用編程接口,提供一組對象、方法和屬性,可以用來訪問這些技術(shù)的所有功能;對象可以理解為是屬性的集合)可以為頁面增加更多新的功能,如視頻播放、本地存儲、地理定位等。
以地理定位為例,通過調(diào)用地理定位API,使用其getCurrentPosition方法可以獲取當(dāng)前位置信息(瀏覽器可以通過IP定位、GPS定位等方式獲取位置信息)并進(jìn)行處理和顯示;使用watchPosition方法可以對位置更新進(jìn)行實時監(jiān)控和報告;使用clearWatch以停止監(jiān)視位置。
(1)Ajax(XMLHttpRequest)
前端(比如瀏覽器)向服務(wù)器請求頁面或者數(shù)據(jù)資源,服務(wù)端接受請求并執(zhí)行服務(wù)端程序,捕獲程序的輸出作為響應(yīng)發(fā)回前端,前端監(jiān)測到數(shù)據(jù)到達(dá)后,再執(zhí)行處理程序?qū)@些數(shù)據(jù)進(jìn)行處理,最終更新DOM和頁面。
這種獲取數(shù)據(jù)的模式稱為Ajax,在應(yīng)用中可以使用這種方式更新內(nèi)容,而無需重新加載頁面。
那么如何使用JavaScript發(fā)送請求?
首先創(chuàng)建一個請求對象,指明請求方法和資源地址,同時提供一個處理程序,然后發(fā)出請求,等待數(shù)據(jù)到達(dá)。數(shù)據(jù)到達(dá)時,就會調(diào)用這個處理程序,在請求對象的responseText屬性中獲取傳回的數(shù)據(jù)并進(jìn)行處理。
瀏覽器主要使用兩種方法發(fā)送請求:GET和POST
使用POST和GET方法都可以向服務(wù)端發(fā)送請求,不過采用了不同的方式。POST會打包要傳遞到服務(wù)端的數(shù)據(jù),并在后臺把他們發(fā)送到服務(wù)器;GET也會打包數(shù)據(jù),但會把這些數(shù)據(jù)追加到URL的最后,然后向服務(wù)器發(fā)送請求。
如果要傳遞的數(shù)據(jù)應(yīng)當(dāng)是私有的,或者數(shù)據(jù)很多,就應(yīng)當(dāng)使用POST;如果返回的頁面要支持添加書簽,就需要使用GET方法。
(2)JSONP
除了提供HTML和JavaScript的服務(wù)器外,瀏覽器不允許從其他不同的服務(wù)器獲取數(shù)據(jù),這是瀏覽器的安全策略。如果頁面和要請求的數(shù)據(jù)同在一個服務(wù)器上,可以使用Ajax請求數(shù)據(jù),如果頁面和要請求的數(shù)據(jù)不在同一服務(wù)器上,則可以使用JSONP請求數(shù)據(jù)。
JSONP是一種使用<script>標(biāo)記獲取數(shù)據(jù)的方法,通過在請求URL后指定回調(diào)函數(shù),將返回的數(shù)據(jù)包裝在一個函數(shù)調(diào)用中進(jìn)行處理。
例如,我們的網(wǎng)站從微博獲取用戶最近動態(tài),就是一個跨域(服務(wù)器)請求數(shù)據(jù)的應(yīng)用。
(3)Web Socket
Ajax和JSONP都使用了一個基于HTTP的請求/響應(yīng)模型。也就是說,每次需要從服務(wù)端獲取資源時,都要使用瀏覽器作出請求得到相關(guān)頁面和數(shù)據(jù)。
對于一些數(shù)據(jù)更新比較頻繁的應(yīng)用,瀏覽器需要不斷的發(fā)送請求詢問服務(wù)端是否有新數(shù)據(jù),在這種應(yīng)用場景下,Web Socket或許是一個更佳的通信方案。
Web Socket是一個新增的API,允許與一個服務(wù)器的連接保持打開,這樣在有新數(shù)據(jù)時,服務(wù)器就會主動把數(shù)據(jù)發(fā)給前端,就像瀏覽器與服務(wù)器之間的一個接通的電話線。
Web Socket提供了實時交互通信的能力,允許服務(wù)器主動發(fā)送信息給客戶端,是一種區(qū)別于HTTP的全新雙向數(shù)據(jù)流協(xié)議。
上面提及頁面從服務(wù)器獲取數(shù)據(jù),除此之外,還可以使用本地存儲獲取數(shù)據(jù),從而減少與服務(wù)端的通信,打造更好的用戶體驗與更廣泛的應(yīng)用場景。
(1)瀏覽器cookie
服務(wù)器隨響應(yīng)發(fā)送一個cookie給瀏覽器,cookie中以鍵值對形式存儲一些信息。瀏覽器在本地存儲cookie,下次發(fā)出請求時會將cookie發(fā)回服務(wù)器。cookie是按域存儲的,而且只能發(fā)送給這個域,因而不同域之間的cookie無法互相訪問。使用cookie可以實現(xiàn)個性化的體驗、維護(hù)游戲狀態(tài)、存儲用戶數(shù)據(jù)等等。
(2)web存儲/local storage(本地存儲)、session storage(會話存儲)
使用JS的 web storage API,可以在瀏覽器中更好的存儲數(shù)據(jù)。
每個瀏覽器都有一些本地存儲空間(每個域都有超過5M的空間,存儲在一個域的數(shù)據(jù)對另一個域是不可見的),使用setItem方法可以在localstorage中存儲一個鍵值對,使用getItem方法可以從本地存儲中獲取某個鍵對應(yīng)的值,使用remove方法可以刪除本地存儲中某一鍵對應(yīng)的數(shù)據(jù)項。
session storage與local storage非常相似,唯一的區(qū)別在于:在用戶與瀏覽器會話期間,session storage可以在本地存儲信息,一旦會話結(jié)束(即關(guān)閉瀏覽器窗口),這些信息將刪除;而local storage將永久存儲信息,除非用戶手動刪除緩存。
JavaScript是單線程的,如果要執(zhí)行復(fù)雜的運算,可以會花費太多時間以至于無法及時作出頁面響應(yīng)。這時可以使用web工作線程,他在一個單獨的線程處理耗時的任務(wù),所以主代碼可以繼續(xù)運行以提高頁面的響應(yīng)性。每個工作線程在他自己的線程中運行,如果你的計算機(jī)有一個多核處理器,工作線程會并行運行,這回提高運算速度。
下面簡單介紹一下web工作線程的工作方式。
在主代碼中使用構(gòu)造函數(shù)創(chuàng)建一個或者多個工作線程對象,并指定工作線程要執(zhí)行的JavaScript文件。
主代碼和工作線程代碼通過消息通信,使用postmessage發(fā)送信息,使用onmessage接收信息。
主代碼通過發(fā)送一個消息讓工作線程開始工作,即執(zhí)行其JavaScript文件;工作線程完成工作后,會發(fā)回消息,并傳入其運算結(jié)果。主代碼得到這些結(jié)果,執(zhí)行相應(yīng)的處理程序,將結(jié)果展現(xiàn)在頁面中。
軟件產(chǎn)品的設(shè)計可以分為內(nèi)容、形態(tài)、行為三個領(lǐng)域。前兩者是HTML和CSS的主要工作,而軟件行為的設(shè)計就是JS(JavaScript)的主要工作。
在內(nèi)容層面,通過JS進(jìn)行前后端通信,可以從服務(wù)端獲取最新的數(shù)據(jù)并動態(tài)的為頁面增加內(nèi)容。
因而在產(chǎn)品設(shè)計中,我們應(yīng)該考慮頁面信息的來源,是寫死在前端,還是請求服務(wù)端數(shù)據(jù)接口,是否需要搭建相應(yīng)的管理后臺。通過JS進(jìn)行前后端通信,還可以將用戶輸入等信息上報服務(wù)端并進(jìn)行存儲,這亦是產(chǎn)品方案中應(yīng)該考慮的地方。
在功能或行為層面,使用JS進(jìn)行前后端通信,一些功能入口也可以通過服務(wù)端進(jìn)行靈活的配置,例如為不同的用戶角色提供不同的功能入口或功能限制。
頁面對用戶的響應(yīng)也都是通過JS來實現(xiàn),例如對表單輸入進(jìn)行校驗并反饋,這些邏輯判斷和運算都是通過JS完成。對此,產(chǎn)品經(jīng)理往往需要通過流程圖、狀態(tài)圖等,進(jìn)行邏輯與運算規(guī)則的說明。
上面分別提及在內(nèi)容、形態(tài)、行為三個設(shè)計領(lǐng)域,技術(shù)在產(chǎn)品工作中的具體應(yīng)用。在更加抽象的層面,技術(shù)的學(xué)習(xí)讓我更加了解互聯(lián)網(wǎng)產(chǎn)品的本質(zhì)–信息。在團(tuán)隊配合、項目流程上,技術(shù)與設(shè)計的結(jié)合同樣具有價值:
(1)幫助從技術(shù)的角度初步評估產(chǎn)品方案,用于評估方案的成本和性價比,排列優(yōu)先級和進(jìn)行版本規(guī)劃
(2)與技術(shù)團(tuán)隊更高效的配合,隨著技術(shù)發(fā)展和不斷成熟,有越來越多的工具可以幫助快速開發(fā),懂得技術(shù)的開發(fā)原理和開發(fā)手段,才可以采用更加靈活的方式與技術(shù)配合。
本文主要探討了:
下一次的分享中將會對后端,以及前后端交互的技術(shù)知識和應(yīng)用進(jìn)行總結(jié),感興趣的小伙伴可以關(guān)注我或者我的專欄。
寫作本文著實花了不少時間,一方面在于其中涉及很多技術(shù)語言;但更主要的原因在于寫作過程中涉及到技術(shù)思維、設(shè)計思維的來回切換和融合,自認(rèn)為本文在這一方面仍然有待提高。
學(xué)習(xí)技術(shù)給我?guī)砗芏鄬用娴氖斋@,讓我得以層層深入,庖丁解牛似的看清產(chǎn)品的內(nèi)部構(gòu)造;也讓我可以從不同角度看待技術(shù)、設(shè)計、產(chǎn)品在軟件產(chǎn)業(yè)不同階段中扮演的角色,對團(tuán)隊組織構(gòu)成有了新的理解。
最后以一句我的名言結(jié)尾:對于設(shè)計的追問會帶你走向技術(shù),對于技術(shù)的追問會帶你走向設(shè)計。
《Head First HTML與CSS》
《Head First HTML5 Programming》
寫了30+產(chǎn)品需求文檔后,我對PRD有了新的認(rèn)知
WebSocket簡介及應(yīng)用實例
本文由 @lemon 原創(chuàng)發(fā)布于人人都是產(chǎn)品經(jīng)理。未經(jīng)許可,禁止轉(zhuǎn)載
題圖來自Unsplash,基于CC0協(xié)議
*請認(rèn)真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。