隨著大數(shù)據(jù)產(chǎn)業(yè)的蓬勃發(fā)展,很多企業(yè)都開(kāi)始應(yīng)用數(shù)據(jù)可視化。所以數(shù)據(jù)可視化設(shè)計(jì),絕對(duì)是熱門(mén)的設(shè)計(jì)之一。很多 UI 設(shè)計(jì)師突然會(huì)接到公司數(shù)據(jù)可視化設(shè)計(jì)的需求,如果不了解數(shù)據(jù)可視化設(shè)計(jì),肯定是一頭霧水,不知從何入手。本文結(jié)合最近設(shè)計(jì)案例,分享大屏可視化設(shè)計(jì)過(guò)程中遇到的一些問(wèn)題以及設(shè)計(jì)思路,供大家一起交流與學(xué)習(xí)。
△ 最終動(dòng)態(tài)效果圖
首先放的是項(xiàng)目改版前的頁(yè)面:
1. 需求介紹
某某應(yīng)用云,分為五大云平臺(tái)模塊:云端綜合調(diào)度、數(shù)據(jù)查詢(xún)通道、數(shù)據(jù)應(yīng)用處理、數(shù)據(jù)存儲(chǔ)管理、管理運(yùn)行維護(hù)。每個(gè)大模塊下?有若干個(gè)子系統(tǒng)。
可視化?屏首頁(yè)需要展示的內(nèi)容包括:
2. 需求分析
分析大屏可視化的一些共性:
結(jié)合大屏的一些共性特點(diǎn)針對(duì)看到的線(xiàn)上舊版本設(shè)計(jì),分析存在的問(wèn)題。
3. 布局
整合數(shù)據(jù),分析出主要數(shù)據(jù)、次要數(shù)據(jù)、總量數(shù)據(jù)、細(xì)分?jǐn)?shù)據(jù)、各數(shù)據(jù)的維度等等。首先優(yōu)化頁(yè)面布局,可以先在紙上畫(huà)一畫(huà),然后腦子里有大概思路以后再用電腦繪制,如下圖:
采用柵格化對(duì)稱(chēng)布局,讓整體視覺(jué)左右平衡。
4. 風(fēng)格
一提到數(shù)據(jù)可視化大家往往能想到科技、數(shù)據(jù)、藍(lán)色等一些普遍關(guān)鍵詞。
了解到客戶(hù)是想做一個(gè)科技感強(qiáng)、炫酷的視覺(jué)效果??梢栽诰W(wǎng)上找一些效果圖或是自己曾經(jīng)做過(guò)的案例供客戶(hù)選擇,確定一個(gè)大致的風(fēng)格,然后結(jié)合具體的業(yè)務(wù)場(chǎng)景進(jìn)行設(shè)計(jì)。
5. 顏色
顏色上結(jié)合產(chǎn)品使用場(chǎng)景,以及整個(gè)產(chǎn)品調(diào)性還是以藍(lán)色為主,背景選用深色調(diào),讓視覺(jué)更好聚焦,內(nèi)容部分采用比較透亮的藍(lán)色系,保證內(nèi)容與背景有一定的對(duì)比關(guān)系,便于業(yè)務(wù)信息傳達(dá)。
6. 主體地圖
地圖為大屏的主要展示內(nèi)容,首先分析展示的目的是為了看清各個(gè)城市間的不同分布情況,和城市數(shù)據(jù)的匯集效果。
如圖:
改版前:地圖過(guò)于單薄,沒(méi)有立體感,太平缺乏層次,顏色黃色不符合產(chǎn)品調(diào)性。
改版后:主色調(diào)改為科技藍(lán),在原有地圖上增加外發(fā)光和多層陰影疊加,增加地圖的立體感,地圖上增加科技線(xiàn)條上升的效果代表每個(gè)城市數(shù)據(jù)變化的提升,地圖背景采用比較弱化的轉(zhuǎn)動(dòng)線(xiàn)條圓形,襯托地圖主體,使得畫(huà)面更加豐富。
7. 地圖效果的實(shí)現(xiàn)方法
首先用 ps 拉框助手新建一個(gè)山東的地圖(拉框助手的使用獲取方法可以參照上篇文章)。
完成后會(huì)得到一個(gè)叫 map 文件夾的地圖分層文件,如圖所示。這里需要對(duì)每個(gè)城市的顏色進(jìn)行調(diào)整,為了區(qū)分每個(gè)城市之間的數(shù)據(jù)不同關(guān)系。
調(diào)整完塊之間的顏色后,就需要給地圖整體增加立體效果。
首先,是整體給地圖加了一個(gè)描邊和外發(fā)光。描邊是為了強(qiáng)化地圖邊緣,外發(fā)光是為了地圖與背景有一個(gè)區(qū)分。
其次為了增加地圖立體感,需要給地圖增加多層陰影疊加的效果。復(fù)制現(xiàn)有形狀層,拼合成一個(gè)山東省的地圖,如下圖:
最后,把拼合好的圖層移動(dòng)到 map 文件夾下面,陰影可以添加多層,這里針對(duì)每一層進(jìn)行不同顏色大小的調(diào)整,就是下面的這種效果了,地圖的體積厚度感也就出來(lái)了。這里只是提供一個(gè)大概的思路,大家可以多去嘗試。
整體地圖效果調(diào)整完成后,就是給地圖增加些紋理,和上升線(xiàn)條這些細(xì)節(jié)上的效果了。紋理可以用圖案疊加,或者找一張紋理圖剪切蒙版實(shí)現(xiàn),最后再添加上升線(xiàn)條的效果,地圖的效果就完成了。
最后加上線(xiàn)條上升的動(dòng)態(tài)效果:
8. 數(shù)據(jù)圖表拆分
在選定數(shù)據(jù)圖表之前,首先要確定圖表之間的關(guān)系,可以從以下四個(gè)維度進(jìn)行思考分析:
可以參照下面這個(gè)圖:
△ 圖片來(lái)自于網(wǎng)絡(luò),侵刪
當(dāng)確定好分析維度后,事實(shí)上我們所能選用的圖表類(lèi)型也就基本確定了。接下來(lái)我們只需要從少數(shù)幾個(gè)圖表里篩選出最能體現(xiàn)我們?cè)O(shè)計(jì)意圖的那個(gè)就好了。
傳統(tǒng)的圖表比如 echarts 圖表在視覺(jué)上展示可能不是很美觀(guān)好看,可根據(jù)選擇的圖表在其基礎(chǔ)之上進(jìn)行美化設(shè)計(jì),總之選定圖表最重要的兩個(gè)點(diǎn)就是:易理解、可實(shí)現(xiàn)。
易理解:就是要考慮最終用戶(hù),可視化結(jié)果應(yīng)該是一看就懂,不需要思考和過(guò)度理解,因而選定圖表時(shí)要理性,避免為了視覺(jué)上的效果而選擇一些對(duì)用戶(hù)不太友好的圖形及元素。
可實(shí)現(xiàn):主要是跟開(kāi)發(fā)前期溝通好實(shí)現(xiàn)方式,一般都采用開(kāi)源組件庫(kù)的居多,比如 echarts 組件庫(kù),我們?cè)O(shè)計(jì)的圖形圖表,要開(kāi)發(fā)能夠?qū)崿F(xiàn)。實(shí)際工作中,一些可視化效果開(kāi)發(fā)用代碼寫(xiě)很容易實(shí)現(xiàn),效果也不錯(cuò),但這些效果設(shè)計(jì)師用 Ps/Ai/Ae 這些工具模擬時(shí)會(huì)發(fā)現(xiàn)比較困難。同樣的,某些效果設(shè)計(jì)師用設(shè)計(jì)工具可以輕易實(shí)現(xiàn),但開(kāi)發(fā)要用代碼落地卻非常困難,所以大屏設(shè)計(jì)中跟開(kāi)發(fā)常溝通非常重要,我們需要明確哪些地方設(shè)計(jì)師可以盡情發(fā)揮,哪些地方需要謹(jǐn)慎設(shè)計(jì)。一個(gè)項(xiàng)目總有周期與預(yù)算限制,不會(huì)無(wú)限期的修改迭代,所以設(shè)計(jì)師在這里需要抓住重點(diǎn),有取舍,不鉆牛角尖、死磕不放。
案例中在圖表選擇上,強(qiáng)化科技感元素,條形圖打破傳統(tǒng)條形圖的展示形式,采用電池晶格的展示形式,在保持圖表功能的同時(shí)更加凸顯科技感。
從頁(yè)面的整體看,已經(jīng)有兩處用到了條形圖、柱狀圖,如果這里還是條形圖,那么頁(yè)面看起來(lái)會(huì)很單調(diào),圖表也沒(méi)有表現(xiàn)出多樣性,所以現(xiàn)在設(shè)計(jì)要體現(xiàn)圖表的多樣性也能夠有排名的直觀(guān)呈現(xiàn)。以下圖表采用科技圓盤(pán)的形式,運(yùn)用科技線(xiàn)條的上升狀態(tài)代表排名的先后順序,所有圖表都采用數(shù)據(jù)降序來(lái)展示排名更加直觀(guān)。
改版前的圖標(biāo)樣式比較單一,改版后針對(duì)每組數(shù)據(jù)不同的對(duì)比形式,采用比較貼合的圖表進(jìn)行展示,篇幅原因就不一一做展示了。
附上最終視覺(jué)效果圖:
大屏設(shè)計(jì)需要注意的點(diǎn):
以上是我對(duì)數(shù)據(jù)可視化大屏的案例總結(jié),希望能幫助到你。除此之外還有很多地方?jīng)]有涉及到,包括具體設(shè)計(jì)的操作方式、選取圖形元素的具體方法,以及在各種大屏中所需要的相對(duì)應(yīng)的組件等,在龐大的可視化大屏設(shè)計(jì)系統(tǒng)中,還有很多值得學(xué)習(xí)參考和優(yōu)化的知識(shí),歡迎溝通交流,大家一起努力。
文章來(lái)源:優(yōu)設(shè)
有句話(huà)叫:「設(shè)計(jì)無(wú)小事」,很多看似不起眼的東西卻起著至關(guān)重要的作用,比如這期要說(shuō)的線(xiàn)條,很多人對(duì)于線(xiàn)條的理解有局限性,比如:線(xiàn)條的形態(tài)可以是曲線(xiàn)、直線(xiàn)、折線(xiàn)、粗線(xiàn)、細(xì)線(xiàn)、實(shí)線(xiàn)、虛線(xiàn)等等。其實(shí)已經(jīng)牽扯到了點(diǎn)、線(xiàn)、面的知識(shí),這也是很多科班生在學(xué)校必學(xué)的知識(shí)點(diǎn),但是這期所說(shuō)的線(xiàn)與點(diǎn)線(xiàn)面中的線(xiàn)還是有所不同的,點(diǎn)線(xiàn)面中的線(xiàn)可以是線(xiàn)條、可以是文字或者是看不到的視線(xiàn),而是今天著重說(shuō)的是設(shè)計(jì)中很直觀(guān)的線(xiàn)條。下面我們還是通過(guò)實(shí)際的案例逐一分析:
設(shè)計(jì)類(lèi)的知識(shí)很多都和日常生活息息相關(guān),嘗試著把設(shè)計(jì)類(lèi)的知識(shí)點(diǎn)與日常生活想結(jié)合,對(duì)于記憶和理解來(lái)說(shuō)會(huì)更加得心應(yīng)手,例如:
圖中的閃電可以視作為設(shè)計(jì)中的線(xiàn)條,給人的視覺(jué)感受是通過(guò)閃電把天與地連接為一個(gè)整體,而閃電在圖中的作用就是串聯(lián)整體,那么回到這里的正題:線(xiàn)條有引導(dǎo)視覺(jué)的作用該怎么理解呢?再舉一個(gè)現(xiàn)實(shí)生活中的案例:
我們選擇從北京到拉薩開(kāi)車(chē)去,出發(fā)之前可能需要在地圖上看下路線(xiàn),知道途徑哪些省市,規(guī)劃好行程路線(xiàn),這里綠色的虛線(xiàn)就起到了引導(dǎo)視覺(jué)的作用?;貧w設(shè)計(jì)中道理是一樣的,線(xiàn)條可以引導(dǎo)用戶(hù)把原本雜亂無(wú)章的視覺(jué)點(diǎn)規(guī)整為有次序的視覺(jué)元素,例如:
當(dāng)看到左側(cè)這張海報(bào)時(shí)我們視覺(jué)次序會(huì)出現(xiàn)很多變化,比如:1>A>3>B>4>C>2 或者 A>2>C>4>B>3>1 等等 N 多種順序,這時(shí)給人的感覺(jué)就是雜亂無(wú)章的,毫無(wú)視覺(jué)次序而言;而看右側(cè)的海報(bào)給人的感覺(jué)卻是條例清晰的,相比而言只是多了兩條線(xiàn),但是卻在整個(gè)海報(bào)中起到了引導(dǎo)視覺(jué)的作用,它可以給與用戶(hù)閱讀海報(bào)時(shí)視覺(jué)輔助的作用,讓用戶(hù)以右>左>右的視覺(jué)次序欣賞、閱讀,看似很不起眼,其作用卻至關(guān)重要。
前面也說(shuō)了,線(xiàn)的形態(tài)可以有很多種,例如:
這里是以真實(shí)的可口可樂(lè)吸管作為設(shè)計(jì)中的線(xiàn)條,同樣起到了視覺(jué)引導(dǎo)的作用,但是我們不難發(fā)現(xiàn),這里的線(xiàn)條不僅僅只有一個(gè)作用,也牽扯到另一個(gè)作用:線(xiàn)條有串聯(lián)整體的作用。
在排版時(shí)我們有分組原則,即把互想關(guān)聯(lián)的元素彼此靠近,無(wú)關(guān)聯(lián)的相互疏遠(yuǎn)。在頁(yè)面中我們會(huì)把同一色塊上的元素視作為一個(gè)整體;下面我們說(shuō)下線(xiàn)條所帶來(lái)的串聯(lián)整體的作用是如何體現(xiàn)的,比如:
△ 圖一
△ 圖二
圖一因?yàn)榇竺娣e的留白能使得用戶(hù)很容易分辨出自行車(chē)與文案是一個(gè)整體,但是相較于圖二而言,其整體性略顯不足,而且給人的感覺(jué)太過(guò)單薄、重心不穩(wěn);圖二的整體性更強(qiáng),這里的矩形線(xiàn)條就起到了串聯(lián)主題的作用,類(lèi)似的還有:
不難看出,這些案例中的線(xiàn)條都有串聯(lián)主題的作用,線(xiàn)條使得主題元素整體感更強(qiáng)、畫(huà)面板式更加嚴(yán)謹(jǐn);對(duì)于整體的視覺(jué)傳達(dá)也起到了串聯(lián)、引導(dǎo)的作用;在文字排版中,也有類(lèi)似的線(xiàn)條,比如:
同樣是通過(guò)線(xiàn)條把文案更加整體化,也起到了串聯(lián)的作用。
突出主題的方式有很多種,像我們之前所說(shuō)到的留白、對(duì)比。接下來(lái)繼續(xù)說(shuō)下另一個(gè)可以突出主題的方式—線(xiàn)條,下面看個(gè)案例:
通過(guò)對(duì)比觀(guān)察我們發(fā)現(xiàn),右側(cè)海報(bào)整體感更強(qiáng),主題文案信息更加清晰,主體更明確。其中的原理可以理解為:因?yàn)榫€(xiàn)條的存在,使得主題信息有了一定的范圍,在視覺(jué)上等于是在海報(bào)中劃定了視覺(jué)焦點(diǎn),從而起到了突出主題的作用。當(dāng)然還有其他的表現(xiàn)形式,比如:
很好的詮釋了線(xiàn)條的作用——突出標(biāo)題序號(hào)。在進(jìn)行創(chuàng)作時(shí),作品的每個(gè)元素都要做到有理有據(jù),否則只是一味的抄襲,到再創(chuàng)作時(shí)腦袋里還是一片空白,只有明白了其中的設(shè)計(jì)原理,才能做到活學(xué)活用。再看幾個(gè)案例:
突出主題也許一個(gè)線(xiàn)條就可以表現(xiàn)的淋漓盡致,因設(shè)計(jì)目的的不同,線(xiàn)條所發(fā)揮的作用也不盡相同。下面繼續(xù)分析:
前面說(shuō)了線(xiàn)條有串聯(lián)整體的作用,而這里又說(shuō)可以分割整體,是否存在矛盾呢?下面舉個(gè)簡(jiǎn)單的例子:
在小文案的區(qū)域中間我加了兩個(gè)線(xiàn)條,看似很小的改變,其目的是把文案很準(zhǔn)確、嚴(yán)謹(jǐn)?shù)胤指顬槿齻€(gè)小整體,希望能給用戶(hù)帶來(lái)識(shí)別性更強(qiáng)的閱讀性,再舉個(gè)例子:
這里的線(xiàn)條把月份和日期分割、英文和中文分割開(kāi),使得用戶(hù)對(duì)于信息的捕捉能力以及可辨識(shí)性都提升了很多,而線(xiàn)條的作用就起到了分割的作用。
線(xiàn)條也能起到修飾、襯托的作用,很多小伙伴會(huì)忽視這一點(diǎn),其實(shí)線(xiàn)條也可以成為海報(bào)中襯托畫(huà)面、修飾主題的元素,例如:
海報(bào)中的線(xiàn)條起到了襯托、修飾主題的作用,假如把這些線(xiàn)條都刪除,畫(huà)面整體會(huì)顯得相對(duì)單薄。
更多設(shè)計(jì)中線(xiàn)條的應(yīng)用:
線(xiàn)條的作用我們分為四個(gè)逐一分析,其實(shí)它們之間也存在著相輔相成的作用,不能以一概全,線(xiàn)條所起到的作用可以是一種,也可以是多種,比如:我們前面「可口可樂(lè)」的案例,即有串聯(lián)整體的作用,又有引導(dǎo)視覺(jué)的作用。只要我們?cè)谑褂玫臅r(shí)候能明確目的,而不是機(jī)械式的抄襲,那么最終一定會(huì)得心應(yīng)手。
文章來(lái)源:優(yōu)設(shè) 作者:美工美邦
未來(lái)熒黑是一個(gè)基于思源黑體、Fira Sans和Raleway的開(kāi)源字體項(xiàng)目,支持簡(jiǎn)體中文、繁體中文與日文。
思源黑體的7種字重被擴(kuò)展為9種字重,并增加了5種字體寬度,全家族共包含44款字體。
相比于思源黑體,未來(lái)熒黑的造型更加簡(jiǎn)明現(xiàn)代,版面效果清新輕快。未來(lái)熒黑的中宮與字面更加收斂,重心在字重之間經(jīng)過(guò)了重新配置;筆畫(huà)細(xì)節(jié)上處理得更加干練,簡(jiǎn)潔而平直化。
開(kāi)發(fā)者:Celestial Phineas
字體文件以SIL Open Font License 1.1發(fā)布,構(gòu)建字體開(kāi)發(fā)的代碼以MIT License發(fā)布。
發(fā)布地址:github.com/welai/glow-sans
網(wǎng)盤(pán)地址:https://pan.baidu.com/s/1f2UuFO8ZxWa8v5XXYUEmig 提取碼 2e8w
備份下載鏈接:https://pan.baidu.com/s/1E1woRsZX91bCrq5FT1SAzg 提取碼: 92t2
文章來(lái)源:優(yōu)設(shè) 作者:GrayDesign
微信在3.23號(hào)上線(xiàn)了傳聞已久的 「暗黑版」,用來(lái)適配 iOS 的深色模式,相信不少同學(xué)已經(jīng)安裝并體驗(yàn)上了,如果還沒(méi)安裝的可以看看下面圖例。
微信每有一次大動(dòng)作都會(huì)引發(fā)全網(wǎng)性的討論,而針對(duì)設(shè)計(jì)上的調(diào)整,往往只會(huì)迎來(lái)一片罵聲。比如我們看看知乎中討論的內(nèi)容,感覺(jué)民憤都快壓抑不住了。
但我們先別急著參與網(wǎng)上的聲討,現(xiàn)在站在設(shè)計(jì)師的角度,來(lái)評(píng)價(jià)一下遲到的微信深色模式。
很多人會(huì)把深色模式和夜間模式搞混,這里必須強(qiáng)調(diào)這是兩種不同的模式,所以我們要對(duì)還沒(méi)搞清楚狀況的同學(xué)先做一個(gè)掃盲(最近掃盲好像搞的比較多…)。
先說(shuō)夜間模式,是一個(gè)專(zhuān)門(mén)針對(duì)夜晚環(huán)境適配的設(shè)計(jì)版本。騰訊的 ISUX 團(tuán)隊(duì)之前做過(guò)調(diào)研,有 71.1% 的用戶(hù)習(xí)慣在夜間不開(kāi)燈看手機(jī)。
如果在深夜漆黑的房間中看手機(jī)屏幕,對(duì)我們的健康有非常大的損害,不僅表現(xiàn)在對(duì)視力的傷害上面,視網(wǎng)膜和神經(jīng)元容易受損,同時(shí)主流的研究項(xiàng)目還表明會(huì)這會(huì)抑制我們褪黑素的分泌造成失眠。
所以,夜間模式,是一個(gè)用來(lái)降低屏幕對(duì)用戶(hù)傷害,提升夜間使用體驗(yàn)的特殊模式。
通常,夜間模式會(huì)采取 降低尼特值、減少對(duì)比度、降低色彩明度、增加深色遮罩的方法,比如之前 QQ 官方的夜間模式示意圖,大家感受一下,是不是有內(nèi)味兒了?
這里提一下尼特這個(gè)概念,尼特是用來(lái)說(shuō)明亮度的術(shù)語(yǔ),1nit=1坎德拉/平方米。是現(xiàn)在各大手機(jī)發(fā)布會(huì)中介紹屏幕的時(shí)候都要強(qiáng)調(diào)的參數(shù)之一,因?yàn)槟崽刂翟礁撸C明在戶(hù)外大白天的環(huán)境中屏幕內(nèi)容可以越清晰,而夜間模式做的就是用來(lái)降低顯示亮度尼特值的。
然后再解釋一下蘋(píng)果的深色模式,蘋(píng)果的 DarkMode,并不是一個(gè)專(zhuān)門(mén)面向深夜環(huán)境的模式。官方對(duì)此版本的解釋?zhuān)斠?jiàn)我們翻譯的 iOS 官方文檔中 112 頁(yè)。
這是一個(gè)面向全天候的視覺(jué)風(fēng)格,同時(shí)通過(guò)深色背景的對(duì)比,來(lái)加凸顯圖片、文字內(nèi)容。包括上面那種官方的配圖,大家應(yīng)該就能感覺(jué)到主體元素比白色模式下更凸出,更激烈。
所以了解了這兩個(gè)模式的區(qū)別,我們才能好好展開(kāi)對(duì)微信深色模式的討論。
接下來(lái),我們先來(lái)總結(jié)一遍微信的深色模式。首先是分析一遍它使用的背景色。
背景色純灰色,有3個(gè)等級(jí)的灰度。熟悉我的都了解,看色彩的奧秘,靠16進(jìn)制代碼和 RGB 那是分析不出個(gè)什么所以然的。如果我們把它們轉(zhuǎn)化成 HSB 一看,規(guī)律性就來(lái)了。
背景色從深到淺色的灰度值 B 值分為 10、14、18,是不是朗朗上口。應(yīng)用的層級(jí)雖然和官方規(guī)范一樣使用了三級(jí),但是設(shè)置和官方的不同。
然后再看看其中使用的其它配色,其中主色保持了不變,其它應(yīng)用到圖標(biāo)色彩,都進(jìn)行了明度的調(diào)整,比如下圖案例。
再看看文字的用色,也是純灰色,標(biāo)題使用 B:85,正文使用 B:65,注釋使用 B:35(主要用色)。
而官方使用的文字色彩中,卻并不是純灰色,除了第一級(jí)的白色以外,其它灰階的文字是由帶有藍(lán)色色相的色彩通過(guò)降低透明度來(lái)呈現(xiàn)的。
對(duì)中性色增加藍(lán)色色值是一個(gè)常規(guī)操作,可以讓這種灰色看起來(lái)有一點(diǎn)活力,不會(huì)像純灰色那么單調(diào),不過(guò)這次微信明顯在文字的應(yīng)用上更保守,一點(diǎn)色相也沒(méi)有給。
這次微信被大面積吐槽的,就是顏色的應(yīng)用上和官方的規(guī)范不一致,作為從業(yè)人員直接開(kāi)噴是相當(dāng)不專(zhuān)業(yè)的(最起碼要先走個(gè)形式),這就是我要分析的重點(diǎn)了。
要說(shuō)微信的 UED 團(tuán)隊(duì),專(zhuān)業(yè)性肯定是國(guó)內(nèi)最頂尖的,你們網(wǎng)上所有看過(guò)有關(guān)交互的方法論、可用性測(cè)試的分享, 他們肯定都有做過(guò),而且執(zhí)行得更專(zhuān)業(yè)。
直接用官方規(guī)范的黑底白字模式,微信團(tuán)隊(duì)不可能沒(méi)有出過(guò)這樣的方案。但很明顯,這個(gè)方案最后被斃了,上了我們看見(jiàn)的這個(gè)版本,雖然不知道以后會(huì)不會(huì)變。
再看看下面官方發(fā)的一條微博。
其中已經(jīng)提及了,是和蘋(píng)果 「共同探索」 出來(lái)的方案,這是非常耐人尋味的。也就是說(shuō),這個(gè)不用官方的模式是蘋(píng)果團(tuán)隊(duì)也通過(guò)的。
那么為什么要做的不一樣呢?沒(méi)有內(nèi)幕消息,就根據(jù)自己的經(jīng)驗(yàn)來(lái)判斷一下。
我自己認(rèn)為的一個(gè)非常重要的原因,就是對(duì)于 「夜間模式「 的兼容。前面不是講暗黑模式和夜間模式不一樣嘛?為什么微信的暗黑模式又去兼容夜間模式了。
這里面有幾個(gè)小彩蛋,首先就是官方對(duì)深色模式的態(tài)度,在我看來(lái)越來(lái)越曖昧了。比如在顯示與亮度設(shè)置頁(yè)面里,有一個(gè)自動(dòng)設(shè)置外觀(guān) —— 日出前保持深色外觀(guān)的功能。這樣,就等于默認(rèn)將深色模式和夜間模式掛鉤。
還有,如果過(guò)去我們沒(méi)有整理 iOS13 的翻譯,就不會(huì)發(fā)現(xiàn),上面我們展示的那句專(zhuān)注于內(nèi)容的解釋?zhuān)F(xiàn)在在官網(wǎng)已經(jīng)被刪掉了(你品,你細(xì)品)。
再說(shuō)關(guān)于用戶(hù)認(rèn)知的問(wèn)題上面,在 UI 群體中,能了解暗黑模式和夜間模式是不一樣的可能就只占 10 分之一,那么對(duì)于普通用戶(hù)來(lái)說(shuō),這個(gè)情況就更不樂(lè)觀(guān),能有 1% 的用戶(hù)了解這個(gè)概念就不錯(cuò)了。所以,絕大多數(shù)用戶(hù)會(huì)直接認(rèn)為暗黑模式就是夜間模式。
如果暗黑模式直接當(dāng)成夜間模式用,在深夜的環(huán)境里,觀(guān)感會(huì)特別差,因?yàn)?—— 明暗對(duì)比度過(guò)高。
如果在黑底中直接用白字,那么可以說(shuō)屏幕的文字和背景的對(duì)比度就是 100(HSB中 B 的差值),在一個(gè)漆黑的環(huán)境中會(huì)非常應(yīng)驗(yàn) 「讓內(nèi)容脫穎而出」 的效果,刺激性會(huì)非常強(qiáng)烈,文字將變得非常尖銳,比如 QQ 暗黑模式,大家可以在被窩里打開(kāi)下面這張圖感受一下。
新的深色模式版本中,文字和背景的對(duì)比度基本控制在 50 左右,降低了一半。
并且,中英文字形在正負(fù)形上的差異(簡(jiǎn)單理解就是中文筆劃更復(fù)雜),這個(gè)感覺(jué)會(huì)更強(qiáng)烈。比如我們用一個(gè)公眾號(hào)頁(yè)面舉例,使用純黑底白字,采用相同字號(hào)的中英文,看看顯示的效果。
還有,純黑背景色和白色的對(duì)比度,會(huì)根據(jù)屏幕的類(lèi)型和參數(shù)不同而有不一樣的感受,比如蘋(píng)果從 X 后旗艦機(jī)型使用 OLED 屏幕,純黑色區(qū)域?qū)⒉粫?huì)發(fā)光,進(jìn)一步擴(kuò)大對(duì)比度,使得文字變得更尖銳,更讓人難以接受。
如果不是使用 OLED 屏幕的同學(xué)光看一個(gè)案例可能很難受,所以我們用純黑的案例來(lái)對(duì)比一下現(xiàn)在的狀態(tài)。
是不是發(fā)現(xiàn)明顯在夜間的情況下黑白模式并不如另一個(gè)模式看起來(lái)舒適?而這種不舒適的差別并不會(huì)隨著屏幕亮度降低而變化。
所以色彩并沒(méi)有符合官方的原因,我的判斷就在大環(huán)境中無(wú)法割裂夜間和深色模式的區(qū)別,同時(shí)也要讓深色模式更適應(yīng)夜間環(huán)境,做出了調(diào)整。而又因?yàn)樗皇钦嬲囊归g模式,所以對(duì)比度也不能像 QQ 之前的夜間版本一樣將整體環(huán)境完全壓暗。
你看,真是一個(gè)讓人矛盾的過(guò)程……
最后再簡(jiǎn)單討論一個(gè)問(wèn)題,為什么深色模式來(lái)得這么晚。很多用戶(hù)一直嘲諷,不就是換一套皮膚的事嘛,為什么就是不上線(xiàn)。
外行可以看熱鬧,但是如果是從業(yè)人員就應(yīng)該知道,微信這種體量的應(yīng)用,上線(xiàn)深色模式絕對(duì)不是一個(gè)非常容易的事情。
適配黑暗模式首先需要使用蘋(píng)果新的 iOS 13 SDK(開(kāi)發(fā)者工具)進(jìn)行編譯,等于應(yīng)用中有大量的代碼需要調(diào)整,而這種升級(jí)調(diào)整的結(jié)果還會(huì)導(dǎo)致沉重的測(cè)試壓力。有經(jīng)歷過(guò) Darkmode 開(kāi)發(fā)的團(tuán)隊(duì)?wèi)?yīng)該都知道這絕對(duì)不是改改顏色就能上線(xiàn)的皮膚。
再看到知乎另一個(gè)回答中提到的:
另一方面點(diǎn)大家隨便聽(tīng)聽(tīng)。使用 iOS 13 SDK 之后,Apple 要求 VoIP 推送必須使用 CallKit,否則應(yīng)用程序會(huì)被終止。而由于眾所周知的原因,CallKit 在中國(guó)大陸是無(wú)法使用的,這樣的改動(dòng)會(huì)降低微信語(yǔ)音電話(huà)的體驗(yàn)。
原文地址:https://www.zhihu.com/question/378027349/answer/1069072154
再者,拋去大量用戶(hù)體驗(yàn)調(diào)研相關(guān)的工作,微信整個(gè)生態(tài)對(duì)于暗黑模式的不友好可以說(shuō)是無(wú)解的。比如說(shuō)公眾號(hào),有大量公眾號(hào)內(nèi)部的標(biāo)題、分割線(xiàn)、引用語(yǔ)句是用圖片做上去的,而圖片還用的是白底(透明底黑字的也有),于是現(xiàn)在就產(chǎn)生了災(zāi)難性的閱讀體驗(yàn)。
比如我的公眾號(hào):超人的電話(huà)亭,其中文章展示的截圖。
而且因?yàn)楣娞?hào)發(fā)出去是不能修改的,只能刪除,那么這部分存量文章將無(wú)法更改,體驗(yàn)也無(wú)法扭轉(zhuǎn)。而且公眾號(hào)還支持文字色彩等自定義,那么你在白色背景下添加的顏色,可不會(huì)直接適配深色模式,尤其是官方也不可能輕易直接給你們 「適配」 掉。
而在夜間模式,正常訪(fǎng)問(wèn)的文章網(wǎng)頁(yè),也和公眾號(hào)會(huì)很像,但是打開(kāi)以后是白色背景的話(huà),統(tǒng)一的體驗(yàn)在哪里?
再者還有小程序,小程序雖然也可以通過(guò)微信官方提供小程序的深色模式適配文檔,對(duì)應(yīng)的 SDK,但是小程序不是 APP,其中有大量小程序開(kāi)發(fā)后是缺少維護(hù)的。
因?yàn)榫€(xiàn)下門(mén)店通過(guò)外包方做好一個(gè)小程序上線(xiàn)以后,沒(méi)特殊的原因不會(huì)直接去更新它,那么這部分小程序的升級(jí)適配無(wú)從談起,會(huì)出現(xiàn)打開(kāi)小程序一個(gè)白一個(gè)黑的窘境。
最后,再講一個(gè)微信里最高頻使用的功能 —— 發(fā)表情。深色模式直接造成大量自定義表情報(bào)廢,無(wú)法正常顯示的問(wèn)題,比如看看下面我自己發(fā)的表情。
前面提到的,都是不能解決的問(wèn)題,這就是做深色模式的挑戰(zhàn),因?yàn)橛脩?hù) UGC 內(nèi)容是不可控的,官方不可能通過(guò)算法直接幫用戶(hù)強(qiáng)行 「適配」。
而這些,就是做深色版的難點(diǎn)。
以上總結(jié)內(nèi)容多數(shù)為主觀(guān)分析,純粹站在 UI 設(shè)計(jì)師角度進(jìn)行專(zhuān)業(yè)解讀,不帶入個(gè)人立場(chǎng)。而一定要我自己評(píng)價(jià)的話(huà),那就是 :趕緊把這模式給我移除??!
再順便提一點(diǎn)小感想,一個(gè)有數(shù)億用戶(hù)的產(chǎn)品,每一個(gè)小調(diào)整分量都不輕,都要慎之又慎。同時(shí),你做的每一個(gè)決策,都意味著要站在其中一部分用戶(hù)的對(duì)立面,因?yàn)槟銤M(mǎn)足不了所有用戶(hù)的需求。所以,這就是設(shè)計(jì)師的壓力與挑戰(zhàn)。
文章來(lái)源:優(yōu)設(shè) 作者:超人的電話(huà)亭
網(wǎng)上對(duì)于這兩個(gè)的區(qū)別解釋都是統(tǒng)一口徑的,一個(gè)是開(kāi)發(fā)依賴(lài),一個(gè)是線(xiàn)上依賴(lài),打包發(fā)布需要用到的要添加到線(xiàn)上依賴(lài),一模一樣的回答,誤導(dǎo)了很多人。今天自己測(cè)試一下這兩個(gè)命令,記錄一下。
–save-dev,會(huì)在devDependencies里面添加依賴(lài)
-D,會(huì)在devDependencies里面添加依賴(lài)
–save,會(huì)在dependencies里面添加依賴(lài)
-S,會(huì)在dependencies里面添加依賴(lài)
devDependencies和dependencies可以同時(shí)存在同一個(gè)包的依賴(lài)。
如果npm install xxx后面沒(méi)有輸入要保存到哪個(gè)里面,devDependencies和dependencies都沒(méi)有。
我這邊直接npm install jquery,node_modules下有jQuery。然后我刪除node_modules,執(zhí)行npm install,node_modules下并沒(méi)有下載jQuery。
所以,安裝依賴(lài)的時(shí)候如果沒(méi)有加上要依賴(lài)到開(kāi)發(fā)還是線(xiàn)上,只是臨時(shí)的在node_modules里面幫你下載,而devDependencies和dependencies的依賴(lài)都會(huì)幫你下載。
然后我在devDependencies下安裝依賴(lài):
"devDependencies": {
"html-webpack-plugin": "^4.0.3",
"jquery": "^3.4.1",
"webpack": "^4.42.1",
"webpack-cli": "^3.3.11"
}
在入口文件引用和打印jQuery:
import $ from 'jquery'
console.log($)
打包之后,可以使用jQuery。
然后我在dependencies下安裝依賴(lài):
"dependencies": {
"html-webpack-plugin": "^4.0.3",
"jquery": "^3.4.1",
"webpack": "^4.42.1",
"webpack-cli": "^3.3.11"
}
在入口文件引用和打印jQuery:
import $ from 'jquery'
console.log($)
打包之后,可以使用jQuery。
測(cè)試的結(jié)果就是,無(wú)論是–save還是–save-dev,對(duì)于打包都沒(méi)有任何影響。devDependencies和dependencies兩種情況,打包出來(lái)的main.js都把jQuery打包進(jìn)去。這兩種情況,如果都沒(méi)有引用jQuery的情況下,也都不會(huì)把jQuery打包。
接著在一個(gè)空白的項(xiàng)目里面下載axios,npm install axios -S,打開(kāi)node_modules文件夾:
發(fā)現(xiàn)多出了另外三個(gè)依賴(lài),查看axios下的package.json:
"dependencies": {
"follow-redirects": "1.5.10"
}
查看follow-redirects下的package.json:
"dependencies": {
"debug": "=3.1.0"
}
查看debugs下的package.json:
"dependencies": {
"ms": "2.0.0"
}
最后ms的package.json沒(méi)有dependencies。
而這幾個(gè)包的devDependencies依賴(lài)的包沒(méi)有一個(gè)下載。
接著我在node_modules把follow-redirects、debugs、ms都刪了,把a(bǔ)xios里面的package.js的dependencies給刪了,然后執(zhí)行npm install,發(fā)現(xiàn)沒(méi)有下載follow-redirects、debugs、ms這幾個(gè),也證明了如果node_modules里面有下載的包,是不會(huì)重新去下載的。我把node_modules刪除,執(zhí)行npm install,這幾個(gè)包又都下載下來(lái)了。
最后得出 的結(jié)論是,–save-dev和–save在平時(shí)開(kāi)發(fā)的時(shí)候,對(duì)于打包部署上線(xiàn)是沒(méi)有任何影響的。如果你是發(fā)布一個(gè)包給別人用,而你開(kāi)發(fā)的包依賴(lài)第三方的包,那么你如果是–save,那么別人安裝你開(kāi)發(fā)的包,會(huì)默認(rèn)下載你依賴(lài)的包,如果你是–save-dev,那么別人安裝你開(kāi)發(fā)的包,是不會(huì)默認(rèn)幫忙下載你依賴(lài)的包。
其實(shí)發(fā)布的包如果沒(méi)有必要,很少會(huì)默認(rèn)幫你下載,比如bootstrap,依賴(lài)jQuery,怕你原本就下載了引起沖突,也不會(huì)在dependencies里面安裝jQuery而是:
"peerDependencies": {
"jquery": "1.9.1 - 3",
"popper.js": "^1.16.0"
}
表示bootstrap依賴(lài)于這兩個(gè)包,你必須安裝,版本不固定,但是一定要安裝這兩個(gè)包,安裝的時(shí)候會(huì)有警告:
peerDependencies WARNING bootstrap@ requires a peer of jquery@1.9.1 - 3 but none was installed
peerDependencies WARNING bootstrap@ requires a peer of popper.js@^1.16.0 but none was installed
當(dāng)你引用了然后打包,報(bào)錯(cuò):
ERROR in ./node_modules/_bootstrap@4.4.1@bootstrap/dist/js/bootstrap.js
Module not found: Error: Can't resolve 'jquery' in 'C:\Users\wade\Desktop\savedev\node_modules_bootstrap@4.4.1@bootstrap\dist\js'
@ ./node_modules/_bootstrap@4.4.1@bootstrap/dist/js/bootstrap.js 7:82-99
@ ./src/index.js
ERROR in ./node_modules/_bootstrap@4.4.1@bootstrap/dist/js/bootstrap.js
Module not found: Error: Can't resolve 'popper.js' in 'C:\Users\wade\Desktop\savedev\node_modules_bootstrap@4.4.1@bootstrap\dist\js'
@ ./node_modules/_bootstrap@4.4.1@bootstrap/dist/js/bootstrap.js 7:101-121
@ ./src/index.js
以上就是對(duì)–save和–save-dev的一些測(cè)試,想更快的得出結(jié)論其實(shí)是自己發(fā)布一個(gè)包。至于本人的答案是不是存在錯(cuò)誤,歡迎指出,因?yàn)橹皇亲约汉?jiǎn)單測(cè)試的結(jié)果。
用戶(hù)體驗(yàn)地圖(Customer Journey Map)是什么?
用戶(hù)體驗(yàn)地圖是從用戶(hù)的視角出發(fā),去理解用戶(hù)、產(chǎn)品或者服務(wù)交互的一個(gè)重要的設(shè)計(jì)工具。
也可以說(shuō)是以可視化的形式,來(lái)表現(xiàn)一個(gè)用戶(hù)使用產(chǎn)品或者接受服務(wù)的體驗(yàn)情況,從體驗(yàn)的過(guò)程中來(lái)發(fā)現(xiàn)用戶(hù)在整個(gè)體驗(yàn)過(guò)程中的問(wèn)題點(diǎn)與情緒點(diǎn),以此來(lái)從中提取出產(chǎn)品的優(yōu)化點(diǎn),方便對(duì)產(chǎn)品進(jìn)行迭代,從而保證良好的用戶(hù)體驗(yàn)。
經(jīng)典案例
Chris Risdon繪制的歐洲鐵路購(gòu)票的體驗(yàn)地圖
上圖中是歐洲鐵路公司整個(gè)體驗(yàn)地圖的一部分。歐洲鐵路公司是一家美國(guó)經(jīng)銷(xiāo)商,為北美旅客提供一個(gè)獨(dú)立預(yù)訂火車(chē)票去歐洲各地的平臺(tái),而無(wú)需用戶(hù)去網(wǎng)站預(yù)定。他們已經(jīng)擁有了一個(gè)良好體驗(yàn)的網(wǎng)站和一個(gè)屢獲殊榮的咨詢(xún)中心,但他們希望通過(guò)所有接觸點(diǎn)來(lái)優(yōu)化用戶(hù)使用過(guò)程,這樣可以讓他們更全面地了解,他們應(yīng)該專(zhuān)注的投資,設(shè)計(jì)和技術(shù)資源。整體的“診斷”評(píng)價(jià)系統(tǒng),包含一系列的重點(diǎn)舉措,體驗(yàn)地圖只是其中派生的一部分。體驗(yàn)地圖幫助建立同理心圖,來(lái)理解隨著時(shí)間和空間的推移,用戶(hù)與歐洲鐵路公司服務(wù)系統(tǒng)交互時(shí)接觸點(diǎn)的變化。
在這張?bào)w驗(yàn)地圖中采用了五個(gè)關(guān)鍵組成一個(gè)體驗(yàn)地圖,一個(gè)體驗(yàn)地圖可以直觀(guān)的表示用戶(hù)操作流、期望、特定的目標(biāo)、用戶(hù)情緒狀態(tài)和整體的體驗(yàn)點(diǎn),做到整體把控和評(píng)估產(chǎn)品體驗(yàn)。
作用 :
用戶(hù)體驗(yàn)地圖能幫助我們創(chuàng)造出一個(gè)有大局觀(guān)的用戶(hù)體驗(yàn),更好的幫助我們理解用戶(hù)的痛點(diǎn)和需求,幫助Team達(dá)成共識(shí),非常有利于跨團(tuán)隊(duì)合作。
用戶(hù)體驗(yàn)地圖包含的內(nèi)容 :
其中包括,人群(產(chǎn)品的用戶(hù)是哪一類(lèi)人)、 用戶(hù)的需求(用戶(hù)想得到什么)、 路徑(在某特定的場(chǎng)景下體驗(yàn)的整體過(guò)程) 、接觸點(diǎn) (產(chǎn)品與人或人與服務(wù)接觸的關(guān)鍵點(diǎn))、行為(用戶(hù)的行為是什么樣的?)、情緒 (體驗(yàn)過(guò)程中的感受心情) 、機(jī)會(huì)點(diǎn) (過(guò)程中可以突破的點(diǎn),可以成為特色的地方)、 解決方案 (解決用戶(hù)在體驗(yàn)過(guò)程的痛點(diǎn))、 問(wèn)題 (解決用戶(hù)在體驗(yàn)過(guò)程的痛點(diǎn))。
用戶(hù)畫(huà)像 :
在準(zhǔn)備開(kāi)始繪制用戶(hù)體驗(yàn)地圖的時(shí)候,我們應(yīng)該要確立用戶(hù)群體 / 確定產(chǎn)品目標(biāo) / 了解用戶(hù)目標(biāo),并作出用戶(hù)畫(huà)像。
視覺(jué)設(shè)計(jì)師怎么使用
舉例(一):
那我們看看作為一名視覺(jué)設(shè)計(jì)師應(yīng)該關(guān)注哪部分的流程。
視覺(jué)設(shè)計(jì)師的用戶(hù)體驗(yàn)地圖 :
我們的聚焦點(diǎn)應(yīng)在上圖的這幾個(gè)部分。
所以當(dāng)繪制完用戶(hù)體驗(yàn)地圖后,應(yīng)該再繪制一份視覺(jué)設(shè)計(jì)師看的版本,我們?cè)O(shè)計(jì)師主要關(guān)注的視覺(jué)的觸點(diǎn)。
定量方法(產(chǎn)品方向):
我們?cè)谛袨楹颓榫w上一般會(huì)使用問(wèn)卷法、后臺(tái)數(shù)據(jù)分析法;而在需求和問(wèn)題上一般會(huì)使用焦點(diǎn)小組、訪(fǎng)談法、觀(guān)察法、日志法和田野調(diào)查,下面就為大家來(lái)解釋下這些方法。
焦點(diǎn)小組:是指從研究產(chǎn)品中所確定的全部用戶(hù)群(總體)中抽取一定數(shù)量的用戶(hù)來(lái)組成樣本,根據(jù)樣本信息推斷用戶(hù)群總體特征的一種調(diào)查方法。
訪(fǎng)談法:訪(fǎng)談,就是以口頭形式向用戶(hù)進(jìn)行詢(xún)問(wèn),根據(jù)被詢(xún)用戶(hù)的答復(fù)搜集客觀(guān)的、不帶偏見(jiàn)的事實(shí)信息,以準(zhǔn)確地說(shuō)明樣本所要代表的總體的一種方式。
觀(guān)察法:觀(guān)察法是指研究者根據(jù)一定的研究目的、研究提綱或觀(guān)察表,用自己的感官和輔助工具去直接觀(guān)察用戶(hù),從而獲得資料的一種方法。
日志法:是由用研人員按時(shí)間順序,詳細(xì)記錄自己在一段時(shí)間內(nèi)使用產(chǎn)品的過(guò)程,經(jīng)過(guò)歸納、分析,達(dá)到分析產(chǎn)品目的的一種工作分析方法。
田野調(diào)查:在日常生活中,在一個(gè)有一個(gè)嚴(yán)格定義的空間和時(shí)間的范圍內(nèi),體驗(yàn)特定用戶(hù)群的日常生活與思想境界,通過(guò)記錄自己的生活的方方面面,來(lái)展示不同階段用戶(hù)群的基本需求。
注意事項(xiàng)(5要點(diǎn))
1. 在制作地圖前,應(yīng)理清楚產(chǎn)品的前期規(guī)劃和需求,并且與同事達(dá)成共識(shí)。
2. 避免以自己的經(jīng)驗(yàn)或者認(rèn)知來(lái)確定用戶(hù)體驗(yàn)地圖中的接觸點(diǎn),應(yīng)當(dāng)真正的從用戶(hù)的行為中去提取。
3. 不要將一些落后的信息加入到用戶(hù)體驗(yàn)地圖中。
4. 最好先在Team內(nèi)部腦暴一份地圖,再去與所制作的地圖進(jìn)行對(duì)比。
5. 用戶(hù)體驗(yàn)地圖不會(huì)涉及到實(shí)現(xiàn)方案和現(xiàn)實(shí)機(jī)制,只涉及用戶(hù)的體驗(yàn)。
團(tuán)隊(duì)人員的合理搭配 :
將公司或者團(tuán)隊(duì)的PM、RD、運(yùn)營(yíng)、Leader等過(guò)來(lái),詳細(xì)的描述這一份用戶(hù)體驗(yàn)地圖,聆聽(tīng)他們的反饋。
在分析用戶(hù)問(wèn)題上 :
分為四個(gè)等級(jí):ABCD,在對(duì)優(yōu)先級(jí)進(jìn)行排列的同時(shí)應(yīng)該,考慮到產(chǎn)品在每個(gè)階段的側(cè)重點(diǎn),根據(jù)不同的進(jìn)度和情況,來(lái)對(duì)優(yōu)先級(jí)進(jìn)行排列,幫助我們整理問(wèn)題和提煉最核心的一些體驗(yàn)問(wèn)題,區(qū)分問(wèn)題還能幫助我們更好的把握產(chǎn)品的優(yōu)化方向。
視覺(jué)設(shè)計(jì)師應(yīng)該關(guān)注的點(diǎn) :
視覺(jué)設(shè)計(jì)師的任務(wù)是什么?是有效的傳達(dá)出產(chǎn)品的信息、簡(jiǎn)潔并且優(yōu)雅的傳達(dá)、通過(guò)視覺(jué)設(shè)計(jì)制造出愉悅的用戶(hù)體驗(yàn)。用戶(hù)在很多的場(chǎng)景下都可能接觸到企業(yè)的產(chǎn)品或者是服務(wù),這個(gè)服務(wù)接觸帶給用戶(hù)的感受更多是偏向于視覺(jué)感知方面的。所以我們需要盡可能的列舉出企業(yè)的產(chǎn)品或者服務(wù)與用戶(hù)可能產(chǎn)生接觸的場(chǎng)景、服務(wù)觸點(diǎn),再根據(jù)服務(wù)觸點(diǎn)延伸出相關(guān)的“視覺(jué)觸點(diǎn)”,用來(lái)梳理出我們需要輸出的視覺(jué)產(chǎn)物,做出相對(duì)應(yīng)的查漏補(bǔ)缺和優(yōu)化,輸出指導(dǎo)企業(yè)的品牌建設(shè)工作。而用戶(hù)體驗(yàn)地圖就很適合作為這樣的工具。
“體驗(yàn)地圖”對(duì)于優(yōu)化視覺(jué)體驗(yàn)的意義 :
整體性:系統(tǒng)性地規(guī)劃品牌的視覺(jué)統(tǒng)一化工作,提升品牌建設(shè)工作的全面性和完整度。也可以避免未來(lái)工作中不同的品牌 / UI / 運(yùn)營(yíng)設(shè)計(jì)師對(duì)于品牌概念的理解不同而帶來(lái)的設(shè)計(jì)出入。
品牌設(shè)計(jì),是用戶(hù)對(duì)于公司產(chǎn)品的直接印象,所以在品牌設(shè)計(jì)的要求就是:建立特征、保持特征、推廣特征、美化特征、對(duì)于以上的要求,來(lái)提供完整且匹配的設(shè)計(jì)方案。
運(yùn)營(yíng)設(shè)計(jì),運(yùn)營(yíng)設(shè)計(jì)的目標(biāo)就是讓用戶(hù)盡可能的感知到產(chǎn)品的好,把產(chǎn)品的特點(diǎn)通過(guò)設(shè)計(jì)包裝傳遞給用戶(hù),一個(gè)好的運(yùn)營(yíng)設(shè)計(jì),應(yīng)該是在用戶(hù)看到你的設(shè)計(jì)作品后,會(huì)產(chǎn)生足夠好的興趣和好感,并愿意去關(guān)注你的產(chǎn)品。
UI設(shè)計(jì),這是產(chǎn)品與用戶(hù)接觸過(guò)程中,頻率最高、最直觀(guān)的部分,目的是為了讓用戶(hù)認(rèn)識(shí)到產(chǎn)品的相貌和氣質(zhì),UI設(shè)計(jì)需要注意界面視覺(jué)層次的強(qiáng)弱、信息劃分、用戶(hù)的視線(xiàn)軌跡、色彩的表達(dá)、質(zhì)感、舒適度等,來(lái)讓用戶(hù)覺(jué)得這個(gè)產(chǎn)品設(shè)計(jì)真好。
例如 :
OFO,以年輕人為主的共享騎行產(chǎn)品,無(wú)論是在品牌/運(yùn)營(yíng)/UI的設(shè)計(jì)上,都能讓人感覺(jué)時(shí)尚、年輕、陽(yáng)光、且有親和力。
品牌設(shè)計(jì) :
UI設(shè)計(jì) :
運(yùn)營(yíng)設(shè)計(jì) :
UI設(shè)計(jì) :
運(yùn)營(yíng)設(shè)計(jì) :
設(shè)計(jì)師的進(jìn)階 :
在一開(kāi)始的初級(jí)設(shè)計(jì)師階段(也就是1.0階段),我們需要從交互設(shè)計(jì)師手中接過(guò)交互設(shè)計(jì)稿,來(lái)對(duì)它進(jìn)行氣質(zhì)進(jìn)行改造,做出獨(dú)特的視覺(jué)設(shè)計(jì),也就是將其翻譯為高保真稿,然后再與開(kāi)發(fā)同學(xué)進(jìn)行對(duì)接,也要保持視覺(jué)走查,以防實(shí)際效果與預(yù)期效果的不符;在這個(gè)1.0階段我們的表現(xiàn)力和創(chuàng)造力,是最為主要的,如何去做出差異化?這是這個(gè)階段的設(shè)計(jì)師需要考慮的,在這個(gè)APP設(shè)計(jì)趨同的大浪潮下,你如果能夠做出不一樣的設(shè)計(jì),那么你則可以一鳴驚人,從眾多水平相當(dāng)?shù)脑O(shè)計(jì)師中脫穎而出,這時(shí)你便可以考慮進(jìn)入下一個(gè)階段,也就是2.0。
在高級(jí)設(shè)計(jì)階段(即2.0階段),這時(shí)候你就需要擁有更好的產(chǎn)品思維和邏輯能力,不僅僅只是從交互設(shè)計(jì)師拿到交互設(shè)計(jì)稿,直接上手開(kāi)做,在這之前,你需要開(kāi)始了解產(chǎn)品的業(yè)務(wù)定位、用戶(hù)人群、產(chǎn)品目標(biāo)、當(dāng)前的問(wèn)題、未來(lái)的迭代等,需求方這時(shí)候就成你的主要對(duì)接對(duì)象,需要你具備拆解需求、采集用戶(hù)的需求、擴(kuò)展業(yè)務(wù)、能進(jìn)行設(shè)計(jì)驗(yàn)證的能力,能將產(chǎn)品的氣質(zhì)和品牌貫穿于整個(gè)產(chǎn)品(UI/運(yùn)營(yíng)/品牌),設(shè)計(jì)是怎么推導(dǎo)的,現(xiàn)在就不是僅僅只在停留在好看的層面上了,畢竟設(shè)計(jì)師不是畫(huà)師,而是解決問(wèn)題的,我們?cè)谧隽四硞€(gè)設(shè)計(jì)后,就要去關(guān)注它的變化了,看看用戶(hù)的反饋、商業(yè)轉(zhuǎn)化率等等,這都是為你的下一次設(shè)計(jì)迭代做的參考。
從
分享到脈脈
轉(zhuǎn)自:脈脈
原文鏈接:https://maimai.cn/article/detail?fid=988630001&efid=N-uHKNnf7vXGBmaFd3lZHA&use_rn=1
本文講述,在使用VUE的移動(dòng)端實(shí)現(xiàn)類(lèi)似于iPhone的懸浮窗的效果。
相關(guān)知識(shí)點(diǎn)
touchstart 當(dāng)在屏幕上按下手指時(shí)觸發(fā)
touchmove 當(dāng)在屏幕上移動(dòng)手指時(shí)觸發(fā)
touchend 當(dāng)在屏幕上抬起手指時(shí)觸發(fā)
mousedown mousemove mouseup對(duì)應(yīng)的是PC端的事件
touchcancel 當(dāng)一些更高級(jí)別的事件發(fā)生的時(shí)候(如電話(huà)接入或者彈出信息)會(huì)取消當(dāng)前的touch操作,即觸發(fā)touchcancel。一般會(huì)在touchcancel時(shí)暫停游戲、存檔等操作。
效果圖
實(shí)現(xiàn)步驟
1.html
總結(jié)了一下評(píng)論,好像發(fā)現(xiàn)大家都碰到了滑動(dòng)的問(wèn)題。就在這里提醒一下吧??蓪⒃搼腋?DIV 同你的 scroller web 同級(jí)。 —- (log: 2018-08-21)
html結(jié)構(gòu): <template> <div>你的web頁(yè)面</div> <div>懸浮DIV</div> </template>
<template> <div id="webId"> ... <div>你的web頁(yè)面</div> <!-- 如果碰到滑動(dòng)問(wèn)題,1.1 請(qǐng)檢查這里是否屬于同一點(diǎn)。 --> <!-- 懸浮的HTML --> <div v-if="!isShow" class="xuanfu" id="moveDiv" @mousedown="down" @touchstart="down" @mousemove="move" @touchmove="move" @mouseup="end" @touchend="end" > <div class="yuanqiu"> {{pageInfo.totalPage}} </div> </div> ... </div> </template>
2.JS
<script> data() { return { flags: false, position: { x: 0, y: 0 }, nx: '', ny: '', dx: '', dy: '', xPum: '', yPum: '', } } methods: { // 實(shí)現(xiàn)移動(dòng)端拖拽 down(){ this.flags = true; var touch; if(event.touches){ touch = event.touches[0]; }else { touch = event; } this.position.x = touch.clientX; this.position.y = touch.clientY; this.dx = moveDiv.offsetLeft; this.dy = moveDiv.offsetTop; }, move(){ if(this.flags){ var touch ; if(event.touches){ touch = event.touches[0]; }else { touch = event; } this.nx = touch.clientX - this.position.x; this.ny = touch.clientY - this.position.y; this.xPum = this.dx+this.nx; this.yPum = this.dy+this.ny; moveDiv.style.left = this.xPum+"px"; moveDiv.style.top = this.yPum +"px"; //阻止頁(yè)面的滑動(dòng)默認(rèn)事件;如果碰到滑動(dòng)問(wèn)題,1.2 請(qǐng)注意是否獲取到 touchmove document.addEventListener("touchmove",function(){ event.preventDefault(); },false); } }, //鼠標(biāo)釋放時(shí)候的函數(shù) end(){ this.flags = false; }, } </script>
3.CSS
<style> .xuanfu { height: 4.5rem; width: 4.5rem; /* 如果碰到滑動(dòng)問(wèn)題,1.3 請(qǐng)檢查 z-index。z-index需比web大一級(jí)*/ z-index: 999; position: fixed; top: 4.2rem; right: 3.2rem; border-radius: 0.8rem; background-color: rgba(0, 0, 0, 0.55); } .yuanqiu { height: 2.7rem; width: 2.7rem; border: 0.3rem solid rgba(140, 136, 136, 0.5); margin: 0.65rem auto; color: #000000; font-size: 1.6rem; line-height: 2.7rem; text-align: center; border-radius: 100%; background-color: #ffffff; } </style>
實(shí)現(xiàn)好JS邏輯,基本上,問(wèn)題不大。
本文鏈接 http://www.luyixian.cn/javascript_show_166242.aspx
再加一點(diǎn)
css之display:inline-block布局
1.解釋一下display的幾個(gè)常用的屬性值,inline , block, inline-block
兩個(gè)圖可以看出,display:inline-block后塊級(jí)元素能夠在同一行顯示,有人這說(shuō)不就像浮動(dòng)一樣嗎。沒(méi)錯(cuò),display:inline-block的效果幾乎和浮動(dòng)一樣,但也有不同,接下來(lái)講一下inline-block和浮動(dòng)的比較。
2.inline-block布局 vs 浮動(dòng)布局
a.不同之處:對(duì)元素設(shè)置display:inline-block ,元素不會(huì)脫離文本流,而float就會(huì)使得元素脫離文本流,且還有父元素高度坍塌的效果
b.相同之處:能在某程度上達(dá)到一樣的效果
我們先來(lái)看看這兩種布局:
圖一:display:inline-block
圖二:
對(duì)兩個(gè)孩子使用float:left,我在上一篇浮動(dòng)布局講過(guò),這是父元素會(huì)高度坍塌,所以要閉合浮動(dòng),對(duì)box使用overflow:hidden,效果如下:
>>乍一看兩個(gè)都能做到幾乎相同的效果,(仔細(xì)看看display:inline-block中有間隙問(wèn)題,這個(gè)留到下面再講)
c.浮動(dòng)布局不太好的地方:參差不齊的現(xiàn)象,我們看一個(gè)效果:
圖三:
圖四:
>>從圖3,4可以看出浮動(dòng)的局限性在于,若要元素排滿(mǎn)一行,換行后還要整齊排列,就要子元素的高度一致才行,不然就會(huì)出現(xiàn)圖三的效果,而inline-block就不會(huì)。
3.inline-block存在的小問(wèn)題:
a.上面可以看到用了display:inline-block后,存在間隙問(wèn)題,間隙為4像素,這個(gè)問(wèn)題產(chǎn)生的原因是換行引起的,因?yàn)槲覀儗?xiě)標(biāo)簽時(shí)通常會(huì)在標(biāo)簽結(jié)束符后順手打個(gè)回車(chē),而回車(chē)會(huì)產(chǎn)生回車(chē)符,回車(chē)符相當(dāng)于空白符,通常情況下,多個(gè)連續(xù)的空白符會(huì)合并成一個(gè)空白符,而產(chǎn)生“空白間隙”的真正原因就是這個(gè)讓我們并不怎么注意的空白符。
b.去除空隙的方法:
1.對(duì)父元素添加,{font-size:0},即將字體大小設(shè)為0,那么那個(gè)空白符也變成0px,從而消除空隙
現(xiàn)在這種方法已經(jīng)可以兼容各種瀏覽器,以前chrome瀏覽器是不兼容的
圖一:
c.瀏覽器兼容性:ie6/7是不兼容 display:inline-block的所以要額外處理一下:
在ie6/7下:
對(duì)于行內(nèi)元素直接使用{dislplay:inline-block;}
對(duì)于塊級(jí)元素:需添加{display:inline;zoom:1;}
4.總結(jié):
display:inline-block的布局方式和浮動(dòng)的布局方式,究竟使用哪個(gè),我覺(jué)得應(yīng)該根據(jù)實(shí)際情況來(lái)決定的:
a.對(duì)于橫向排列東西來(lái)說(shuō),我更傾向與使用inline-block來(lái)布局,因?yàn)檫@樣清晰,也不用再像浮動(dòng)那樣清除浮動(dòng),害怕布局混亂等等。
b.對(duì)于浮動(dòng)布局就用于需要文字環(huán)繞的時(shí)候,畢竟這才是浮動(dòng)真正的用武之地,水平排列的是就交給inline-block了。
Node 的os模塊是操作系統(tǒng)的
Node 的內(nèi)置模塊 fs
內(nèi)置模塊在下載node的時(shí)候就自帶的,使用 require()方法來(lái)導(dǎo)入
語(yǔ)法 :require(‘模塊fs’)
在內(nèi)置模塊中的方法
1 fs.readFile() —》用來(lái)專(zhuān)門(mén) 異步 讀取文件的方法 三個(gè)參數(shù)
語(yǔ)法 :fs.readFile(‘要讀取的文件’,讀取文件的格式,讀取成功的回調(diào)函數(shù))
Eg : fs.readFIle(‘a(chǎn)’,’utf8’,’function(err,data){ })
2 fs.readFileSync()-– 專(zhuān)門(mén)用來(lái) 同步 讀取的方法, 兩個(gè)參數(shù)
語(yǔ)法: fs.readFileSync(‘要讀取的文件’,讀取格式)
3 fs.writeFIle() —>用來(lái)寫(xiě)入 異步 文件的方法 三個(gè)參數(shù)
語(yǔ)法: fs.writeFile(‘寫(xiě)入到哪個(gè)文件’,寫(xiě)入的內(nèi)容,成功的回調(diào)函數(shù))
Eg: fs.writeFile(‘./text.tex’,”內(nèi)容”, function(){ })
注意:再次寫(xiě)入的內(nèi)容會(huì)完全覆蓋 。如果文件夾沒(méi)有 會(huì)自動(dòng)創(chuàng)建一個(gè)文件夾
4 fs.writeFileSync() --> 同步寫(xiě)入的方法
語(yǔ)法: fs.writeFileSync(‘寫(xiě)入到文件’,“寫(xiě)入的內(nèi)容”)
Node的http模塊
這個(gè)模塊專(zhuān)門(mén)用來(lái)創(chuàng)建服務(wù)的
只能支持http協(xié)議。
也是使用require()方法
Const http= require(“http”)
方法
1 http.createServer(function(req,res){ }) 兩個(gè)形參
Req=request 代表每次的請(qǐng)求信息
Res=response 代表每次請(qǐng)求的響應(yīng)
返回值是一個(gè)服務(wù),當(dāng)服務(wù)監(jiān)聽(tīng)端口號(hào)的時(shí)候,就變成了服務(wù)器。
2 監(jiān)聽(tīng)端口號(hào)
創(chuàng)建的服務(wù).listen(監(jiān)聽(tīng)的端口號(hào),監(jiān)聽(tīng)成功的回調(diào)函數(shù)(選填))
server.listen(8080,function(){ 端口號(hào)0-65535 建議0-1023不使用 })
此時(shí)瀏覽器就可以執(zhí)行l(wèi)ocalhost進(jìn)行訪(fǎng)問(wèn)了
自定義模塊
每一個(gè)js文件都是一個(gè)獨(dú)立的模塊,他們都自帶一個(gè) module 是一個(gè)對(duì)象,
其中 module里面的 exports,是一個(gè)對(duì)象 這個(gè) module.exports 就是這個(gè)文件向外導(dǎo)出的內(nèi)容,也就是說(shuō),只有導(dǎo)出,才能導(dǎo)入
Eg: function fn1(){console.log() }
Module.exports.fn1=fn1
這樣,才能是另一個(gè)js文件到入這個(gè)文件 同樣也是require(‘./js’)方法
想要學(xué)會(huì)這個(gè)漂亮的煙花嗎?快來(lái)跟著學(xué)習(xí)吧~
<div class="container"></div>
我們只需要一個(gè)盒子表示煙花爆炸范圍就可以了
fire是煙花 注意添加絕對(duì)定位
<style> .container{ margin: 0 auto; height: 500px; width: 1200px; background: black; position: relative; overflow: hidden; } .fire{ width: 10px; background: white; height: 10px; /* border-radius: 50%; */ position: absolute; bottom: 0; } </style>
需要用到一個(gè)鼠標(biāo)點(diǎn)擊的位置,一個(gè)div選擇器,一個(gè)爆炸樣式
function Firework(x,y,selector,type){ //此處獲取對(duì)象的方式為單例的思想,避免重復(fù)獲取相同的元素 if(Firework.box && selector === Firework.box.selector){ this.box = Firework.box.ele; }else{ Firework.box = { ele:document.querySelector(selector), selector:selector } this.box = Firework.box.ele; } this.type = type; this.init(x,y) }
function animation(ele,attroptions,callback){ for(var attr in attroptions){ attroptions[attr] ={ target:attroptions[attr], inow:parseInt(getComputedStyle(ele)[attr]) } } clearInterval(ele.timer); ele.timer = setInterval(function(){ for(var attr in attroptions ){ var item = attroptions[attr] var target = item.target; var inow = item.inow; var speed = (target - inow)/10; speed = speed>0?Math.ceil(speed):Math.floor(speed); if(Math.abs(target - inow) <= Math.abs(speed)){ ele.style[attr] = target+"px"; delete attroptions[attr]; for(var num in attroptions){ return false; } clearTimeout(ele.timer); if(typeof callback === "function")callback(); }else{ attroptions[attr].inow += speed; ele.style[attr] = attroptions[attr].inow+"px"; } } },30) }
Firework.prototype = { constructor:Firework, //初始化 init:function(x,y){ //創(chuàng)建一個(gè)煙花 this.ele = this.createFirework(); //xy為鼠標(biāo)落點(diǎn) this.x = x ; this.y = y; //maxXy為最大運(yùn)動(dòng)范圍 this.maxX = this.box.offsetWidth - this.ele.offsetWidth; this.maxY = this.box.offsetHeight - this.ele.offsetHeight; //初始化結(jié)束后 煙花隨機(jī)顏色 this.randomColor(this.ele); //煙花升空 this.fireworkUp(this.ele); }, //創(chuàng)造煙花 createFirework:function(){ var ele = document.createElement("div"); ele.className = "fire"; this.box.appendChild(ele); return ele; }, //煙花升空 fireworkUp:function(ele){ ele.style.left = this.x + "px"; //此處用到剛剛封裝的運(yùn)動(dòng)方法 animation(ele,{top:this.y},function(){ ele.remove(); this.fireworkBlast() }.bind(this)); }, //煙花爆炸 fireworkBlast:function(){ for(var i = 0 ; i < 20; i++){ var ele = document.createElement("div"); ele.className = "fire"; ele.style.left = this.x + "px"; ele.style.top = this.y + "px"; this.box.appendChild(ele); ele.style.borderRadius = "50%"; this.randomColor(ele); //判定一下輸入的爆炸方式是原型煙花 還是散落煙花 由此更改獲取的煙花位置 animation(ele,this.type === "circle"?this.circleBlast(i,20): this.randomPosition(),function(cale){ cale.remove(); }.bind(this,ele)) } }, //圓形爆炸位置 circleBlast:function(i,total){ var r = 200; var reg = 360 / total *i; var deg = Math.PI / 180 *reg; return { left:r * Math.cos(deg) + this.x , top:r * Math.sin(deg) + this.y } }, //隨機(jī)顏色 randomPosition:function(){ return { left : Math.random()*this.maxX, top : Math.random()*this.maxY } }, randomColor:function(ele){ var color = "#" + parseInt(parseInt("ffffff",16)*Math.random()).toString(16).padStart(6,0); return ele.style.backgroundColor = color; } }
document.querySelector(".container").addEventListener("click",function(evt){ var e = evt||event; new Firework(e.offsetX,e.offsetY,".container","circle") new Firework(e.offsetX,e.offsetY,".container") })
全部代碼
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <style> .container{ margin: 0 auto; height: 500px; width: 1200px; background: black; position: relative; overflow: hidden; } .fire{ width: 10px; background: white; height: 10px; /* border-radius: 50%; */ position: absolute; bottom: 0; } </style> </head> <body> <div class="container"></div> <script src="./utils.js"></script> <script> function animation(ele,attroptions,callback){ for(var attr in attroptions){ attroptions[attr] ={ target:attroptions[attr], inow:parseInt(getComputedStyle(ele)[attr]) } } clearInterval(ele.timer); ele.timer = setInterval(function(){ for(var attr in attroptions ){ var item = attroptions[attr] var target = item.target; var inow = item.inow; var speed = (target - inow)/10; speed = speed>0?Math.ceil(speed):Math.floor(speed); if(Math.abs(target - inow) <= Math.abs(speed)){ ele.style[attr] = target+"px"; delete attroptions[attr]; for(var num in attroptions){ return false; } clearTimeout(ele.timer); if(typeof callback === "function")callback(); }else{ attroptions[attr].inow += speed; ele.style[attr] = attroptions[attr].inow+"px"; } } },30) } function Firework(x,y,selector,type){ if(Firework.box && selector === Firework.box.selector){ this.box = Firework.box.ele; }else{ Firework.box = { ele:document.querySelector(selector), selector:selector } this.box = Firework.box.ele; } this.type = type; this.init(x,y) } Firework.prototype = { constructor:Firework, //初始化 init:function(x,y){ this.ele = this.createFirework(); this.x = x ; this.y = y; this.maxX = this.box.offsetWidth - this.ele.offsetWidth; this.maxY = this.box.offsetHeight - this.ele.offsetHeight; this.randomColor(this.ele); this.fireworkUp(this.ele); }, //創(chuàng)造煙花 createFirework:function(){ var ele = document.createElement("div"); ele.className = "fire"; this.box.appendChild(ele); return ele; }, fireworkUp:function(ele){ ele.style.left = this.x + "px"; animation(ele,{top:this.y},function(){ ele.remove(); this.fireworkBlast() }.bind(this)); }, //煙花爆炸 fireworkBlast:function(){ for(var i = 0 ; i < 20; i++){ var ele = document.createElement("div"); ele.className = "fire"; ele.style.left = this.x + "px"; ele.style.top = this.y + "px"; this.box.appendChild(ele); ele.style.borderRadius = "50%"; this.randomColor(ele); animation(ele,this.type === "circle"?this.circleBlast(i,20): this.randomPosition(),function(cale){ cale.remove(); }.bind(this,ele)) } }, circleBlast:function(i,total){ var r = 200; var reg = 360 / total *i; var deg = Math.PI / 180 *reg; return { left:r * Math.cos(deg) + this.x , top:r * Math.sin(deg) + this.y } }, randomPosition:function(){ return { left : Math.random()*this.maxX, top : Math.random()*this.maxY } }, randomColor:function(ele){ var color = "#" + parseInt(parseInt("ffffff",16)*Math.random()).toString(16).padStart(6,0); return ele.style.backgroundColor = color; } } document.querySelector(".container").addEventListener("click",function(evt){ var e = evt||event; new Firework(e.offsetX,e.offsetY,".container","circle") new Firework(e.offsetX,e.offsetY,".container") }) </script> </body> </html>
———————————————— 版權(quán)聲明:本文為CSDN博主「SpongeBooob」的原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明。 原文鏈接:https://blog.csdn.net/qq_41383900/article/details/105026768
許多人都有這樣一種映像,NodeJS比較快; 但是因?yàn)槠涫菃尉€(xiàn)程,所以它不穩(wěn)定,有點(diǎn)不安全,不適合處理復(fù)雜業(yè)務(wù); 它比較適合對(duì)并發(fā)要求比較高,而且簡(jiǎn)單的業(yè)務(wù)場(chǎng)景。
在Express的作者的TJ Holowaychuk的 告別Node.js一文中列舉了以下罪狀:
Farewell NodeJS (TJ Holowaychuk)
? you may get duplicate callbacks
? you may not get a callback at all (lost in limbo)
? you may get out-of-band errors
? emitters may get multiple “error” events
? missing “error” events sends everything to hell
? often unsure what requires “error” handlers
? “error” handlers are very verbose
? callbacks suck
其實(shí)這幾條主要吐嘈了兩點(diǎn): node.js錯(cuò)誤處理很扯蛋,node.js的回調(diào)也很扯蛋。
事實(shí)上NodeJS里程確實(shí)有“脆弱”的一面,單線(xiàn)程的某處產(chǎn)生了“未處理的”異常確實(shí)會(huì)導(dǎo)致整個(gè)Node.JS的崩潰退出,來(lái)看個(gè)例子, 這里有一個(gè)node-error.js的文件:
var http = require('http'); var server = http.createServer(function (req, res) { //這里有個(gè)錯(cuò)誤,params 是 undefined var ok = req.params.ok; res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World '); }); server.listen(8080, '127.0.0.1'); console.log('Server running at http://127.0.0.1:8080/');
啟動(dòng)服務(wù),并在地址欄測(cè)試一下發(fā)現(xiàn) http://127.0.0.1:8080/ 不出所料,node崩潰了
$ node node-error Server running at http://127.0.0.1:8080/ c:githubscript ode-error.js:5 var ok = req.params.ok; ^ TypeError: Cannot read property 'ok' of undefined at Server.<anonymous> (c:githubscript ode-error.js:5:22) at Server.EventEmitter.emit (events.js:98:17) at HTTPParser.parser.onIncoming (http.js:2108:12) at HTTPParser.parserOnHeadersComplete [as onHeadersComplete] (http.js:121:23) at Socket.socket.ondata (http.js:1966:22) at TCP.onread (net.js:525:27)
其實(shí)Node.JS發(fā)展到今天,如果連這個(gè)問(wèn)題都解決不了,那估計(jì)早就沒(méi)人用了。
我們可以u(píng)ncaughtException來(lái)全局捕獲未捕獲的Error,同時(shí)你還可以將此函數(shù)的調(diào)用棧打印出來(lái),捕獲之后可以有效防止node進(jìn)程退出,如:
process.on('uncaughtException', function (err) { //打印出錯(cuò)誤 console.log(err); //打印出錯(cuò)誤的調(diào)用棧方便調(diào)試 console.log(err.stack); });
這相當(dāng)于在node進(jìn)程內(nèi)部進(jìn)行守護(hù), 但這種方法很多人都是不提倡的,說(shuō)明你還不能完全掌控Node.JS的異常。
我們還可以在回調(diào)前加try/catch,同樣確保線(xiàn)程的安全。
var http = require('http'); http.createServer(function(req, res) { try { handler(req, res); } catch(e) { console.log(' ', e, ' ', e.stack); try { res.end(e.stack); } catch(e) { } } }).listen(8080, '127.0.0.1'); console.log('Server running at http://127.0.0.1:8080/'); var handler = function (req, res) { //Error Popuped var name = req.params.name; res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello ' + name); };
這種方案的好處是,可以將錯(cuò)誤和調(diào)用棧直接輸出到當(dāng)前發(fā)生的網(wǎng)頁(yè)上。
標(biāo)準(zhǔn)的HTTP響應(yīng)處理會(huì)經(jīng)歷一系列的Middleware(HttpModule),最終到達(dá)Handler,如下圖所示:
這 些Middleware和Handler在NodeJS中都有一個(gè)特點(diǎn),他們都是回調(diào)函數(shù),而回調(diào)函數(shù)中是唯一會(huì)讓Node在運(yùn)行時(shí)崩潰的地方。根據(jù)這個(gè) 特點(diǎn),我們只需要在框架中集成一處try/catch就可以相對(duì)完美地解決異常問(wèn)題,而且不會(huì)影響其它用戶(hù)的請(qǐng)求request。
事實(shí)上現(xiàn)在的NodeJS WEB框架幾乎都是這么做的,如 OurJS開(kāi)源博客所基于的 WebSvr
就有這么一處異常處理代碼:
Line: 207 try { handler(req, res); } catch(err) { var errorMsg = ' ' + 'Error ' + new Date().toISOString() + ' ' + req.url + ' ' + err.stack || err.message || 'unknow error' + ' ' ; console.error(errorMsg); Settings.showError ? res.end('<pre>' + errorMsg + '</pre>') : res.end(); }
那么不在回調(diào)中產(chǎn)生的錯(cuò)誤怎么辦?不必?fù)?dān)心,其實(shí)這樣的node程序根本就起不起來(lái)。
此外node自帶的 cluster 也有一定的容錯(cuò)能力,它跟nginx的worker很類(lèi)似,但消耗資源(內(nèi)存)略大,編程也不是很方便,OurJS并沒(méi)有采用此種設(shè)計(jì)。
現(xiàn) 在已經(jīng)基本上解決了Node.JS因異常而崩潰的問(wèn)題,不過(guò)任何平臺(tái)都不是100%可靠的,還有一些錯(cuò)誤是從Node底層拋出的,有些異常 try/catch和uncaughtException都無(wú)法捕獲。之前在運(yùn)行ourjs的時(shí)侯,會(huì)偶爾碰到底層拋出的文件流讀取異常,這就是一個(gè)底層 libuv的BUG,node.js在0.10.21中進(jìn)行了修復(fù)。
面對(duì)這種情況,我們就應(yīng)該為nodejs應(yīng)用添加守護(hù)進(jìn)程,讓NodeJS遭遇異常崩潰以后能馬上復(fù)活。
另外,還應(yīng)該把這些產(chǎn)生的異常記錄到日志中,并讓異常永遠(yuǎn)不再發(fā)生。
node-forever 提供了守護(hù)的功能和LOG日志記錄功能。
安裝非常容易
[sudo] npm install forever
使用也很簡(jiǎn)單
$ forever start simple-server.js $ forever list [0] simple-server.js [ 24597, 24596 ]
還可以看日志
forever -o out.log -e err.log my-script.js
使用node來(lái)守護(hù)的話(huà)資源開(kāi)銷(xiāo)可能會(huì)有點(diǎn)大,而且也會(huì)略顯復(fù)雜,OurJS直接在開(kāi)機(jī)啟動(dòng)腳本來(lái)進(jìn)程線(xiàn)程守護(hù)。
如在debian中放置的 ourjs 開(kāi)機(jī)啟動(dòng)文件: /etc/init.d/ourjs
這個(gè)文件非常簡(jiǎn)單,只有啟動(dòng)的選項(xiàng),守護(hù)的核心功能是由一個(gè)無(wú)限循環(huán) while true; 來(lái)實(shí)現(xiàn)的,為了防止過(guò)于密集的錯(cuò)誤阻塞進(jìn)程,每次錯(cuò)誤后間隔1秒重啟服務(wù)
WEB_DIR='/var/www/ourjs' WEB_APP='svr/ourjs.js' #location of node you want to use NODE_EXE=/root/local/bin/node while true; do { $NODE_EXE $WEB_DIR/$WEB_APP config.magazine.js echo "Stopped unexpected, restarting " } 2>> $WEB_DIR/error.log sleep 1 done
錯(cuò)誤日志記錄也非常簡(jiǎn)單,直接將此進(jìn)程控制臺(tái)當(dāng)中的錯(cuò)誤輸出到error.log文件即可: 2>> $WEB_DIR/error.log 這一行, 2 代表 Error。
藍(lán)藍(lán)設(shè)計(jì)的小編 http://www.yvirxh.cn