首頁

實(shí)戰(zhàn)經(jīng)驗(yàn)!可視化大屏設(shè)計(jì)案例全方位復(fù)盤!

資深UI設(shè)計(jì)者

隨著大數(shù)據(jù)產(chǎn)業(yè)的蓬勃發(fā)展,很多企業(yè)都開始應(yīng)用數(shù)據(jù)可視化。所以數(shù)據(jù)可視化設(shè)計(jì),絕對(duì)是熱門的設(shè)計(jì)之一。很多 UI 設(shè)計(jì)師突然會(huì)接到公司數(shù)據(jù)可視化設(shè)計(jì)的需求,如果不了解數(shù)據(jù)可視化設(shè)計(jì),肯定是一頭霧水,不知從何入手。本文結(jié)合最近設(shè)計(jì)案例,分享大屏可視化設(shè)計(jì)過程中遇到的一些問題以及設(shè)計(jì)思路,供大家一起交流與學(xué)習(xí)。

>

△ 最終動(dòng)態(tài)效果圖

案例分解

首先放的是項(xiàng)目改版前的頁面:

1. 需求介紹

某某應(yīng)用云,分為五大云平臺(tái)模塊:云端綜合調(diào)度、數(shù)據(jù)查詢通道、數(shù)據(jù)應(yīng)用處理、數(shù)據(jù)存儲(chǔ)管理、管理運(yùn)行維護(hù)。每個(gè)大模塊下?有若干個(gè)子系統(tǒng)。

可視化?屏首頁需要展示的內(nèi)容包括:

  • 全局?jǐn)?shù)據(jù):云平臺(tái)的數(shù)據(jù)總量,以及 4 個(gè)重要關(guān)注數(shù)據(jù)項(xiàng),2 個(gè)次要關(guān)注數(shù)據(jù)項(xiàng);
  • 云平臺(tái)的五?大模塊:云端綜合調(diào)度、數(shù)據(jù)查詢通道、數(shù)據(jù)應(yīng)用處理理、數(shù)據(jù)存儲(chǔ)管理、管理運(yùn)行維護(hù)(只有兩個(gè) tab 切換鏈接);
  • 搜索功能:搜索類型分為 6 個(gè),默認(rèn)選中「綜合」類型進(jìn)行搜索;
  • 重點(diǎn)關(guān)注數(shù)據(jù)信息按指標(biāo)分多維度展示:原始圖上的維度包括指標(biāo)、地圖、地域排名、部?排名、類別;
  • 云導(dǎo)航:展示場(chǎng)景在公司展廳,材質(zhì)為 Led 拼接屏,設(shè)計(jì)尺寸為 1920×1080。

2. 需求分析

分析大屏可視化的一些共性:

  • 屏幕大:大屏一般都是多屏拼接,整體屏幕面積大。
  • 觀距遠(yuǎn):用戶需要遠(yuǎn)處觀看屏幕,要保證數(shù)據(jù)文字清晰可見。
  • 交互弱:通過電腦已經(jīng)無法滿足大屏交互需求,現(xiàn)在也有部分開始采用 ipad、手機(jī)、激光筆等方式。
  • 視覺強(qiáng):背景色多采用重色,襯托凸顯數(shù)據(jù),更好地為觀者傳達(dá)數(shù)據(jù)信息。
  • 一屏一內(nèi)容:一屏內(nèi)容,說明一件主要事情,統(tǒng)計(jì)好它的相關(guān)數(shù)據(jù),避免其他的干擾。

結(jié)合大屏的一些共性特點(diǎn)針對(duì)看到的線上舊版本設(shè)計(jì),分析存在的問題。

  • 布局混亂,導(dǎo)致視覺不平衡,看不出頁面層次。
  • 藍(lán)色為主色調(diào),黃色點(diǎn)綴顏色顯得比較單一沒有規(guī)則。
  • 圖表比較單一,不能有效傳達(dá)數(shù)據(jù)信息。

3. 布局

整合數(shù)據(jù),分析出主要數(shù)據(jù)、次要數(shù)據(jù)、總量數(shù)據(jù)、細(xì)分?jǐn)?shù)據(jù)、各數(shù)據(jù)的維度等等。首先優(yōu)化頁面布局,可以先在紙上畫一畫,然后腦子里有大概思路以后再用電腦繪制,如下圖:

采用柵格化對(duì)稱布局,讓整體視覺左右平衡。

4. 風(fēng)格

一提到數(shù)據(jù)可視化大家往往能想到科技、數(shù)據(jù)、藍(lán)色等一些普遍關(guān)鍵詞。

了解到客戶是想做一個(gè)科技感強(qiáng)、炫酷的視覺效果??梢栽诰W(wǎng)上找一些效果圖或是自己曾經(jīng)做過的案例供客戶選擇,確定一個(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),讓視覺更好聚焦,內(nèi)容部分采用比較透亮的藍(lán)色系,保證內(nèi)容與背景有一定的對(duì)比關(guān)系,便于業(yè)務(wù)信息傳達(dá)。

6. 主體地圖

地圖為大屏的主要展示內(nèi)容,首先分析展示的目的是為了看清各個(gè)城市間的不同分布情況,和城市數(shù)據(jù)的匯集效果。

如圖:

改版前:地圖過于單薄,沒有立體感,太平缺乏層次,顏色黃色不符合產(chǎn)品調(diào)性。

改版后:主色調(diào)改為科技藍(lán),在原有地圖上增加外發(fā)光和多層陰影疊加,增加地圖的立體感,地圖上增加科技線條上升的效果代表每個(gè)城市數(shù)據(jù)變化的提升,地圖背景采用比較弱化的轉(zhuǎn)動(dòng)線條圓形,襯托地圖主體,使得畫面更加豐富。

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)整,就是下面的這種效果了,地圖的體積厚度感也就出來了。這里只是提供一個(gè)大概的思路,大家可以多去嘗試。

整體地圖效果調(diào)整完成后,就是給地圖增加些紋理,和上升線條這些細(xì)節(jié)上的效果了。紋理可以用圖案疊加,或者找一張紋理圖剪切蒙版實(shí)現(xiàn),最后再添加上升線條的效果,地圖的效果就完成了。

最后加上線條上升的動(dòng)態(tài)效果:

8. 數(shù)據(jù)圖表拆分

在選定數(shù)據(jù)圖表之前,首先要確定圖表之間的關(guān)系,可以從以下四個(gè)維度進(jìn)行思考分析:

  • 聯(lián)系:數(shù)據(jù)之間的相關(guān)性;
  • 分布:指標(biāo)里的數(shù)據(jù)主要集中在什么范圍、表現(xiàn)出怎樣的規(guī)律;
  • 比較:數(shù)據(jù)之間存在何種差異、差異主要體現(xiàn)在哪些方面;
  • 構(gòu)成:指標(biāo)里的數(shù)據(jù)都由哪幾部分組成、每部分占比如何。

可以參照下面這個(gè)圖:

△ 圖片來自于網(wǎng)絡(luò),侵刪

當(dāng)確定好分析維度后,事實(shí)上我們所能選用的圖表類型也就基本確定了。接下來我們只需要從少數(shù)幾個(gè)圖表里篩選出最能體現(xiàn)我們?cè)O(shè)計(jì)意圖的那個(gè)就好了。

傳統(tǒng)的圖表比如 echarts 圖表在視覺上展示可能不是很美觀好看,可根據(jù)選擇的圖表在其基礎(chǔ)之上進(jìn)行美化設(shè)計(jì),總之選定圖表最重要的兩個(gè)點(diǎn)就是:易理解、可實(shí)現(xiàn)。

易理解:就是要考慮最終用戶,可視化結(jié)果應(yīng)該是一看就懂,不需要思考和過度理解,因而選定圖表時(shí)要理性,避免為了視覺上的效果而選擇一些對(duì)用戶不太友好的圖形及元素。

可實(shí)現(xiàn):主要是跟開發(fā)前期溝通好實(shí)現(xiàn)方式,一般都采用開源組件庫(kù)的居多,比如 echarts 組件庫(kù),我們?cè)O(shè)計(jì)的圖形圖表,要開發(fā)能夠?qū)崿F(xiàn)。實(shí)際工作中,一些可視化效果開發(fā)用代碼寫很容易實(shí)現(xiàn),效果也不錯(cuò),但這些效果設(shè)計(jì)師用 Ps/Ai/Ae 這些工具模擬時(shí)會(huì)發(fā)現(xiàn)比較困難。同樣的,某些效果設(shè)計(jì)師用設(shè)計(jì)工具可以輕易實(shí)現(xiàn),但開發(fā)要用代碼落地卻非常困難,所以大屏設(shè)計(jì)中跟開發(fā)常溝通非常重要,我們需要明確哪些地方設(shè)計(jì)師可以盡情發(fā)揮,哪些地方需要謹(jǐn)慎設(shè)計(jì)。一個(gè)項(xiàng)目總有周期與預(yù)算限制,不會(huì)無限期的修改迭代,所以設(shè)計(jì)師在這里需要抓住重點(diǎn),有取舍,不鉆牛角尖、死磕不放。

案例中在圖表選擇上,強(qiáng)化科技感元素,條形圖打破傳統(tǒng)條形圖的展示形式,采用電池晶格的展示形式,在保持圖表功能的同時(shí)更加凸顯科技感。

從頁面的整體看,已經(jīng)有兩處用到了條形圖、柱狀圖,如果這里還是條形圖,那么頁面看起來會(huì)很單調(diào),圖表也沒有表現(xiàn)出多樣性,所以現(xiàn)在設(shè)計(jì)要體現(xiàn)圖表的多樣性也能夠有排名的直觀呈現(xiàn)。以下圖表采用科技圓盤的形式,運(yùn)用科技線條的上升狀態(tài)代表排名的先后順序,所有圖表都采用數(shù)據(jù)降序來展示排名更加直觀。

改版前的圖標(biāo)樣式比較單一,改版后針對(duì)每組數(shù)據(jù)不同的對(duì)比形式,采用比較貼合的圖表進(jìn)行展示,篇幅原因就不一一做展示了。

附上最終視覺效果圖:

總結(jié)

大屏設(shè)計(jì)需要注意的點(diǎn):

  • 需求分析:對(duì)需求進(jìn)行梳理,分清數(shù)據(jù)間的主次關(guān)系以及對(duì)比指標(biāo),跟客戶溝通清楚細(xì)節(jié)。
  • 確認(rèn)布局:布局上根據(jù)數(shù)據(jù)主次關(guān)系,采用柵格化布局(可以在紙上畫一下理清思路)。
  • 情緒板定義設(shè)計(jì)關(guān)鍵詞,確定風(fēng)格。找參考圖給客戶確認(rèn)大致的設(shè)計(jì)方向。
  • 顏色貼合業(yè)務(wù)。
  • 圖表易理解可實(shí)現(xiàn)。

以上是我對(duì)數(shù)據(jù)可視化大屏的案例總結(jié),希望能幫助到你。除此之外還有很多地方?jīng)]有涉及到,包括具體設(shè)計(jì)的操作方式、選取圖形元素的具體方法,以及在各種大屏中所需要的相對(duì)應(yīng)的組件等,在龐大的可視化大屏設(shè)計(jì)系統(tǒng)中,還有很多值得學(xué)習(xí)參考和優(yōu)化的知識(shí),歡迎溝通交流,大家一起努力。

文章來源:優(yōu)設(shè)   

如何用好設(shè)計(jì)中的線條?來看高手的總結(jié)!

資深UI設(shè)計(jì)者

有句話叫:「設(shè)計(jì)無小事」,很多看似不起眼的東西卻起著至關(guān)重要的作用,比如這期要說的線條,很多人對(duì)于線條的理解有局限性,比如:線條的形態(tài)可以是曲線、直線、折線、粗線、細(xì)線、實(shí)線、虛線等等。其實(shí)已經(jīng)牽扯到了點(diǎn)、線、面的知識(shí),這也是很多科班生在學(xué)校必學(xué)的知識(shí)點(diǎn),但是這期所說的線與點(diǎn)線面中的線還是有所不同的,點(diǎn)線面中的線可以是線條、可以是文字或者是看不到的視線,而是今天著重說的是設(shè)計(jì)中很直觀的線條。下面我們還是通過實(shí)際的案例逐一分析:

線條可以引導(dǎo)視覺

設(shè)計(jì)類的知識(shí)很多都和日常生活息息相關(guān),嘗試著把設(shè)計(jì)類的知識(shí)點(diǎn)與日常生活想結(jié)合,對(duì)于記憶和理解來說會(huì)更加得心應(yīng)手,例如:

圖中的閃電可以視作為設(shè)計(jì)中的線條,給人的視覺感受是通過閃電把天與地連接為一個(gè)整體,而閃電在圖中的作用就是串聯(lián)整體,那么回到這里的正題:線條有引導(dǎo)視覺的作用該怎么理解呢?再舉一個(gè)現(xiàn)實(shí)生活中的案例:

我們選擇從北京到拉薩開車去,出發(fā)之前可能需要在地圖上看下路線,知道途徑哪些省市,規(guī)劃好行程路線,這里綠色的虛線就起到了引導(dǎo)視覺的作用。回歸設(shè)計(jì)中道理是一樣的,線條可以引導(dǎo)用戶把原本雜亂無章的視覺點(diǎn)規(guī)整為有次序的視覺元素,例如:

當(dāng)看到左側(cè)這張海報(bào)時(shí)我們視覺次序會(huì)出現(xiàn)很多變化,比如:1>A>3>B>4>C>2 或者 A>2>C>4>B>3>1 等等 N 多種順序,這時(shí)給人的感覺就是雜亂無章的,毫無視覺次序而言;而看右側(cè)的海報(bào)給人的感覺卻是條例清晰的,相比而言只是多了兩條線,但是卻在整個(gè)海報(bào)中起到了引導(dǎo)視覺的作用,它可以給與用戶閱讀海報(bào)時(shí)視覺輔助的作用,讓用戶以右>左>右的視覺次序欣賞、閱讀,看似很不起眼,其作用卻至關(guān)重要。

前面也說了,線的形態(tài)可以有很多種,例如:

這里是以真實(shí)的可口可樂吸管作為設(shè)計(jì)中的線條,同樣起到了視覺引導(dǎo)的作用,但是我們不難發(fā)現(xiàn),這里的線條不僅僅只有一個(gè)作用,也牽扯到另一個(gè)作用:線條有串聯(lián)整體的作用。

線條能夠串聯(lián)整體

在排版時(shí)我們有分組原則,即把互想關(guān)聯(lián)的元素彼此靠近,無關(guān)聯(lián)的相互疏遠(yuǎn)。在頁面中我們會(huì)把同一色塊上的元素視作為一個(gè)整體;下面我們說下線條所帶來的串聯(lián)整體的作用是如何體現(xiàn)的,比如:

△ 圖一

△ 圖二

圖一因?yàn)榇竺娣e的留白能使得用戶很容易分辨出自行車與文案是一個(gè)整體,但是相較于圖二而言,其整體性略顯不足,而且給人的感覺太過單薄、重心不穩(wěn);圖二的整體性更強(qiáng),這里的矩形線條就起到了串聯(lián)主題的作用,類似的還有:

不難看出,這些案例中的線條都有串聯(lián)主題的作用,線條使得主題元素整體感更強(qiáng)、畫面板式更加嚴(yán)謹(jǐn);對(duì)于整體的視覺傳達(dá)也起到了串聯(lián)、引導(dǎo)的作用;在文字排版中,也有類似的線條,比如:

同樣是通過線條把文案更加整體化,也起到了串聯(lián)的作用。

線條可以突出主題

突出主題的方式有很多種,像我們之前所說到的留白、對(duì)比。接下來繼續(xù)說下另一個(gè)可以突出主題的方式—線條,下面看個(gè)案例:

通過對(duì)比觀察我們發(fā)現(xiàn),右側(cè)海報(bào)整體感更強(qiáng),主題文案信息更加清晰,主體更明確。其中的原理可以理解為:因?yàn)榫€條的存在,使得主題信息有了一定的范圍,在視覺上等于是在海報(bào)中劃定了視覺焦點(diǎn),從而起到了突出主題的作用。當(dāng)然還有其他的表現(xiàn)形式,比如:

很好的詮釋了線條的作用——突出標(biāo)題序號(hào)。在進(jìn)行創(chuàng)作時(shí),作品的每個(gè)元素都要做到有理有據(jù),否則只是一味的抄襲,到再創(chuàng)作時(shí)腦袋里還是一片空白,只有明白了其中的設(shè)計(jì)原理,才能做到活學(xué)活用。再看幾個(gè)案例:

突出主題也許一個(gè)線條就可以表現(xiàn)的淋漓盡致,因設(shè)計(jì)目的的不同,線條所發(fā)揮的作用也不盡相同。下面繼續(xù)分析:

線條可以分割整體

前面說了線條有串聯(lián)整體的作用,而這里又說可以分割整體,是否存在矛盾呢?下面舉個(gè)簡(jiǎn)單的例子:

在小文案的區(qū)域中間我加了兩個(gè)線條,看似很小的改變,其目的是把文案很準(zhǔn)確、嚴(yán)謹(jǐn)?shù)胤指顬槿齻€(gè)小整體,希望能給用戶帶來識(shí)別性更強(qiáng)的閱讀性,再舉個(gè)例子:

這里的線條把月份和日期分割、英文和中文分割開,使得用戶對(duì)于信息的捕捉能力以及可辨識(shí)性都提升了很多,而線條的作用就起到了分割的作用。

線條有修飾的作用

線條也能起到修飾、襯托的作用,很多小伙伴會(huì)忽視這一點(diǎn),其實(shí)線條也可以成為海報(bào)中襯托畫面、修飾主題的元素,例如:

海報(bào)中的線條起到了襯托、修飾主題的作用,假如把這些線條都刪除,畫面整體會(huì)顯得相對(duì)單薄。

更多設(shè)計(jì)中線條的應(yīng)用:

總結(jié)

線條的作用我們分為四個(gè)逐一分析,其實(shí)它們之間也存在著相輔相成的作用,不能以一概全,線條所起到的作用可以是一種,也可以是多種,比如:我們前面「可口可樂」的案例,即有串聯(lián)整體的作用,又有引導(dǎo)視覺的作用。只要我們?cè)谑褂玫臅r(shí)候能明確目的,而不是機(jī)械式的抄襲,那么最終一定會(huì)得心應(yīng)手。

文章來源:優(yōu)設(shè)    作者:美工美邦

免費(fèi)商用字體「未來熒黑」開放下載!5種字寬+9種字重超好用!

資深UI設(shè)計(jì)者

未來熒黑是一個(gè)基于思源黑體、Fira Sans和Raleway的開源字體項(xiàng)目,支持簡(jiǎn)體中文、繁體中文與日文。

思源黑體的7種字重被擴(kuò)展為9種字重,并增加了5種字體寬度,全家族共包含44款字體。

與思源黑體有何不同?

相比于思源黑體,未來熒黑的造型更加簡(jiǎn)明現(xiàn)代,版面效果清新輕快。未來熒黑的中宮與字面更加收斂,重心在字重之間經(jīng)過了重新配置;筆畫細(xì)節(jié)上處理得更加干練,簡(jiǎn)潔而平直化。

開發(fā)者:Celestial Phineas

字體文件以SIL Open Font License 1.1發(fā)布,構(gòu)建字體開發(fā)的代碼以MIT License發(fā)布。

發(fā)布地址:github.com/welai/glow-sans

如何下載?

網(wǎng)盤地址:https://pan.baidu.com/s/1f2UuFO8ZxWa8v5XXYUEmig 提取碼 2e8w

備份下載鏈接:https://pan.baidu.com/s/1E1woRsZX91bCrq5FT1SAzg 提取碼: 92t2

文章來源:優(yōu)設(shè)    作者:GrayDesign

如何評(píng)價(jià)微信新上線的深色模式?

資深UI設(shè)計(jì)者

微信在3.23號(hào)上線了傳聞已久的 「暗黑版」,用來適配 iOS 的深色模式,相信不少同學(xué)已經(jīng)安裝并體驗(yàn)上了,如果還沒安裝的可以看看下面圖例。

微信每有一次大動(dòng)作都會(huì)引發(fā)全網(wǎng)性的討論,而針對(duì)設(shè)計(jì)上的調(diào)整,往往只會(huì)迎來一片罵聲。比如我們看看知乎中討論的內(nèi)容,感覺民憤都快壓抑不住了。

但我們先別急著參與網(wǎng)上的聲討,現(xiàn)在站在設(shè)計(jì)師的角度,來評(píng)價(jià)一下遲到的微信深色模式。

深色和夜間模式傻傻分不清

很多人會(huì)把深色模式和夜間模式搞混,這里必須強(qiáng)調(diào)這是兩種不同的模式,所以我們要對(duì)還沒搞清楚狀況的同學(xué)先做一個(gè)掃盲(最近掃盲好像搞的比較多…)。

先說夜間模式,是一個(gè)專門針對(duì)夜晚環(huán)境適配的設(shè)計(jì)版本。騰訊的 ISUX 團(tuán)隊(duì)之前做過調(diào)研,有 71.1% 的用戶習(xí)慣在夜間不開燈看手機(jī)。

如果在深夜漆黑的房間中看手機(jī)屏幕,對(duì)我們的健康有非常大的損害,不僅表現(xiàn)在對(duì)視力的傷害上面,視網(wǎng)膜和神經(jīng)元容易受損,同時(shí)主流的研究項(xiàng)目還表明會(huì)這會(huì)抑制我們褪黑素的分泌造成失眠。

所以,夜間模式,是一個(gè)用來降低屏幕對(duì)用戶傷害,提升夜間使用體驗(yàn)的特殊模式。

通常,夜間模式會(huì)采取 降低尼特值、減少對(duì)比度、降低色彩明度、增加深色遮罩的方法,比如之前 QQ 官方的夜間模式示意圖,大家感受一下,是不是有內(nèi)味兒了?

這里提一下尼特這個(gè)概念,尼特是用來說明亮度的術(shù)語,1nit=1坎德拉/平方米。是現(xiàn)在各大手機(jī)發(fā)布會(huì)中介紹屏幕的時(shí)候都要強(qiáng)調(diào)的參數(shù)之一,因?yàn)槟崽刂翟礁?,證明在戶外大白天的環(huán)境中屏幕內(nèi)容可以越清晰,而夜間模式做的就是用來降低顯示亮度尼特值的。

然后再解釋一下蘋果的深色模式,蘋果的 DarkMode,并不是一個(gè)專門面向深夜環(huán)境的模式。官方對(duì)此版本的解釋,詳見我們翻譯的 iOS 官方文檔中 112 頁。

這是一個(gè)面向全天候的視覺風(fēng)格,同時(shí)通過深色背景的對(duì)比,來加凸顯圖片、文字內(nèi)容。包括上面那種官方的配圖,大家應(yīng)該就能感覺到主體元素比白色模式下更凸出,更激烈。

所以了解了這兩個(gè)模式的區(qū)別,我們才能好好展開對(duì)微信深色模式的討論。

微信深色模式專業(yè)總結(jié)

接下來,我們先來總結(jié)一遍微信的深色模式。首先是分析一遍它使用的背景色。

背景色純灰色,有3個(gè)等級(jí)的灰度。熟悉我的都了解,看色彩的奧秘,靠16進(jìn)制代碼和 RGB 那是分析不出個(gè)什么所以然的。如果我們把它們轉(zhuǎn)化成 HSB 一看,規(guī)律性就來了。

背景色從深到淺色的灰度值 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)色色相的色彩通過降低透明度來呈現(xiàn)的。

對(duì)中性色增加藍(lán)色色值是一個(gè)常規(guī)操作,可以讓這種灰色看起來有一點(diǎn)活力,不會(huì)像純灰色那么單調(diào),不過這次微信明顯在文字的應(yīng)用上更保守,一點(diǎn)色相也沒有給。

為什么微信色彩另辟蹊徑

這次微信被大面積吐槽的,就是顏色的應(yīng)用上和官方的規(guī)范不一致,作為從業(yè)人員直接開噴是相當(dāng)不專業(yè)的(最起碼要先走個(gè)形式),這就是我要分析的重點(diǎn)了。

要說微信的 UED 團(tuán)隊(duì),專業(yè)性肯定是國(guó)內(nèi)最頂尖的,你們網(wǎng)上所有看過有關(guān)交互的方法論、可用性測(cè)試的分享, 他們肯定都有做過,而且執(zhí)行得更專業(yè)。

直接用官方規(guī)范的黑底白字模式,微信團(tuán)隊(duì)不可能沒有出過這樣的方案。但很明顯,這個(gè)方案最后被斃了,上了我們看見的這個(gè)版本,雖然不知道以后會(huì)不會(huì)變。

再看看下面官方發(fā)的一條微博。

其中已經(jīng)提及了,是和蘋果 「共同探索」 出來的方案,這是非常耐人尋味的。也就是說,這個(gè)不用官方的模式是蘋果團(tuán)隊(duì)也通過的。

那么為什么要做的不一樣呢?沒有內(nèi)幕消息,就根據(jù)自己的經(jīng)驗(yàn)來判斷一下。

我自己認(rèn)為的一個(gè)非常重要的原因,就是對(duì)于 「夜間模式「 的兼容。前面不是講暗黑模式和夜間模式不一樣嘛?為什么微信的暗黑模式又去兼容夜間模式了。

這里面有幾個(gè)小彩蛋,首先就是官方對(duì)深色模式的態(tài)度,在我看來越來越曖昧了。比如在顯示與亮度設(shè)置頁面里,有一個(gè)自動(dòng)設(shè)置外觀 —— 日出前保持深色外觀的功能。這樣,就等于默認(rèn)將深色模式和夜間模式掛鉤。

還有,如果過去我們沒有整理 iOS13 的翻譯,就不會(huì)發(fā)現(xiàn),上面我們展示的那句專注于內(nèi)容的解釋,現(xiàn)在在官網(wǎng)已經(jīng)被刪掉了(你品,你細(xì)品)。

再說關(guān)于用戶認(rèn)知的問題上面,在 UI 群體中,能了解暗黑模式和夜間模式是不一樣的可能就只占 10 分之一,那么對(duì)于普通用戶來說,這個(gè)情況就更不樂觀,能有 1% 的用戶了解這個(gè)概念就不錯(cuò)了。所以,絕大多數(shù)用戶會(huì)直接認(rèn)為暗黑模式就是夜間模式。

如果暗黑模式直接當(dāng)成夜間模式用,在深夜的環(huán)境里,觀感會(huì)特別差,因?yàn)?—— 明暗對(duì)比度過高。

如果在黑底中直接用白字,那么可以說屏幕的文字和背景的對(duì)比度就是 100(HSB中 B 的差值),在一個(gè)漆黑的環(huán)境中會(huì)非常應(yīng)驗(yàn) 「讓內(nèi)容脫穎而出」 的效果,刺激性會(huì)非常強(qiáng)烈,文字將變得非常尖銳,比如 QQ 暗黑模式,大家可以在被窩里打開下面這張圖感受一下。

新的深色模式版本中,文字和背景的對(duì)比度基本控制在 50 左右,降低了一半。

并且,中英文字形在正負(fù)形上的差異(簡(jiǎn)單理解就是中文筆劃更復(fù)雜),這個(gè)感覺會(huì)更強(qiáng)烈。比如我們用一個(gè)公眾號(hào)頁面舉例,使用純黑底白字,采用相同字號(hào)的中英文,看看顯示的效果。

還有,純黑背景色和白色的對(duì)比度,會(huì)根據(jù)屏幕的類型和參數(shù)不同而有不一樣的感受,比如蘋果從 X 后旗艦機(jī)型使用 OLED 屏幕,純黑色區(qū)域?qū)⒉粫?huì)發(fā)光,進(jìn)一步擴(kuò)大對(duì)比度,使得文字變得更尖銳,更讓人難以接受。

如果不是使用 OLED 屏幕的同學(xué)光看一個(gè)案例可能很難受,所以我們用純黑的案例來對(duì)比一下現(xiàn)在的狀態(tài)。

是不是發(fā)現(xiàn)明顯在夜間的情況下黑白模式并不如另一個(gè)模式看起來舒適?而這種不舒適的差別并不會(huì)隨著屏幕亮度降低而變化。

所以色彩并沒有符合官方的原因,我的判斷就在大環(huán)境中無法割裂夜間和深色模式的區(qū)別,同時(shí)也要讓深色模式更適應(yīng)夜間環(huán)境,做出了調(diào)整。而又因?yàn)樗皇钦嬲囊归g模式,所以對(duì)比度也不能像 QQ 之前的夜間版本一樣將整體環(huán)境完全壓暗。

你看,真是一個(gè)讓人矛盾的過程……

為什么深色模式姍姍來遲

最后再簡(jiǎn)單討論一個(gè)問題,為什么深色模式來得這么晚。很多用戶一直嘲諷,不就是換一套皮膚的事嘛,為什么就是不上線。

外行可以看熱鬧,但是如果是從業(yè)人員就應(yīng)該知道,微信這種體量的應(yīng)用,上線深色模式絕對(duì)不是一個(gè)非常容易的事情。

適配黑暗模式首先需要使用蘋果新的 iOS 13 SDK(開發(fā)者工具)進(jìn)行編譯,等于應(yīng)用中有大量的代碼需要調(diào)整,而這種升級(jí)調(diào)整的結(jié)果還會(huì)導(dǎo)致沉重的測(cè)試壓力。有經(jīng)歷過 Darkmode 開發(fā)的團(tuán)隊(duì)?wèi)?yīng)該都知道這絕對(duì)不是改改顏色就能上線的皮膚。

再看到知乎另一個(gè)回答中提到的:

另一方面點(diǎn)大家隨便聽聽。使用 iOS 13 SDK 之后,Apple 要求 VoIP 推送必須使用 CallKit,否則應(yīng)用程序會(huì)被終止。而由于眾所周知的原因,CallKit 在中國(guó)大陸是無法使用的,這樣的改動(dòng)會(huì)降低微信語音電話的體驗(yàn)。

原文地址:https://www.zhihu.com/question/378027349/answer/1069072154

再者,拋去大量用戶體驗(yàn)調(diào)研相關(guān)的工作,微信整個(gè)生態(tài)對(duì)于暗黑模式的不友好可以說是無解的。比如說公眾號(hào),有大量公眾號(hào)內(nèi)部的標(biāo)題、分割線、引用語句是用圖片做上去的,而圖片還用的是白底(透明底黑字的也有),于是現(xiàn)在就產(chǎn)生了災(zāi)難性的閱讀體驗(yàn)。

比如我的公眾號(hào):超人的電話亭,其中文章展示的截圖。

而且因?yàn)楣娞?hào)發(fā)出去是不能修改的,只能刪除,那么這部分存量文章將無法更改,體驗(yàn)也無法扭轉(zhuǎn)。而且公眾號(hào)還支持文字色彩等自定義,那么你在白色背景下添加的顏色,可不會(huì)直接適配深色模式,尤其是官方也不可能輕易直接給你們 「適配」 掉。

而在夜間模式,正常訪問的文章網(wǎng)頁,也和公眾號(hào)會(huì)很像,但是打開以后是白色背景的話,統(tǒng)一的體驗(yàn)在哪里?

再者還有小程序,小程序雖然也可以通過微信官方提供小程序的深色模式適配文檔,對(duì)應(yīng)的 SDK,但是小程序不是 APP,其中有大量小程序開發(fā)后是缺少維護(hù)的。

因?yàn)榫€下門店通過外包方做好一個(gè)小程序上線以后,沒特殊的原因不會(huì)直接去更新它,那么這部分小程序的升級(jí)適配無從談起,會(huì)出現(xiàn)打開小程序一個(gè)白一個(gè)黑的窘境。

最后,再講一個(gè)微信里最高頻使用的功能 —— 發(fā)表情。深色模式直接造成大量自定義表情報(bào)廢,無法正常顯示的問題,比如看看下面我自己發(fā)的表情。

前面提到的,都是不能解決的問題,這就是做深色模式的挑戰(zhàn),因?yàn)橛脩?UGC 內(nèi)容是不可控的,官方不可能通過算法直接幫用戶強(qiáng)行 「適配」。

而這些,就是做深色版的難點(diǎn)。

總結(jié)

以上總結(jié)內(nèi)容多數(shù)為主觀分析,純粹站在 UI 設(shè)計(jì)師角度進(jìn)行專業(yè)解讀,不帶入個(gè)人立場(chǎng)。而一定要我自己評(píng)價(jià)的話,那就是 :趕緊把這模式給我移除!!

再順便提一點(diǎn)小感想,一個(gè)有數(shù)億用戶的產(chǎn)品,每一個(gè)小調(diào)整分量都不輕,都要慎之又慎。同時(shí),你做的每一個(gè)決策,都意味著要站在其中一部分用戶的對(duì)立面,因?yàn)槟銤M足不了所有用戶的需求。所以,這就是設(shè)計(jì)師的壓力與挑戰(zhàn)。

文章來源:優(yōu)設(shè)    作者:超人的電話亭

你不知道的--save-dev和--save的區(qū)別

seo達(dá)人

網(wǎng)上對(duì)于這兩個(gè)的區(qū)別解釋都是統(tǒng)一口徑的,一個(gè)是開發(fā)依賴,一個(gè)是線上依賴,打包發(fā)布需要用到的要添加到線上依賴,一模一樣的回答,誤導(dǎo)了很多人。今天自己測(cè)試一下這兩個(gè)命令,記錄一下。



–save-dev,會(huì)在devDependencies里面添加依賴



-D,會(huì)在devDependencies里面添加依賴



–save,會(huì)在dependencies里面添加依賴



-S,會(huì)在dependencies里面添加依賴



devDependencies和dependencies可以同時(shí)存在同一個(gè)包的依賴。



如果npm install xxx后面沒有輸入要保存到哪個(gè)里面,devDependencies和dependencies都沒有。



我這邊直接npm install jquery,node_modules下有jQuery。然后我刪除node_modules,執(zhí)行npm install,node_modules下并沒有下載jQuery。



所以,安裝依賴的時(shí)候如果沒有加上要依賴到開發(fā)還是線上,只是臨時(shí)的在node_modules里面幫你下載,而devDependencies和dependencies的依賴都會(huì)幫你下載。



然后我在devDependencies下安裝依賴:



"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下安裝依賴:



"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é)果就是,無論是–save還是–save-dev,對(duì)于打包都沒有任何影響。devDependencies和dependencies兩種情況,打包出來的main.js都把jQuery打包進(jìn)去。這兩種情況,如果都沒有引用jQuery的情況下,也都不會(huì)把jQuery打包。



接著在一個(gè)空白的項(xiàng)目里面下載axios,npm install axios -S,打開node_modules文件夾:







發(fā)現(xiàn)多出了另外三個(gè)依賴,查看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沒有dependencies。



而這幾個(gè)包的devDependencies依賴的包沒有一個(gè)下載。



接著我在node_modules把follow-redirects、debugs、ms都刪了,把a(bǔ)xios里面的package.js的dependencies給刪了,然后執(zhí)行npm install,發(fā)現(xiàn)沒有下載follow-redirects、debugs、ms這幾個(gè),也證明了如果node_modules里面有下載的包,是不會(huì)重新去下載的。我把node_modules刪除,執(zhí)行npm install,這幾個(gè)包又都下載下來了。



最后得出 的結(jié)論是,–save-dev和–save在平時(shí)開發(fā)的時(shí)候,對(duì)于打包部署上線是沒有任何影響的。如果你是發(fā)布一個(gè)包給別人用,而你開發(fā)的包依賴第三方的包,那么你如果是–save,那么別人安裝你開發(fā)的包,會(huì)默認(rèn)下載你依賴的包,如果你是–save-dev,那么別人安裝你開發(fā)的包,是不會(huì)默認(rèn)幫忙下載你依賴的包。



其實(shí)發(fā)布的包如果沒有必要,很少會(huì)默認(rèn)幫你下載,比如bootstrap,依賴jQuery,怕你原本就下載了引起沖突,也不會(huì)在dependencies里面安裝jQuery而是:



"peerDependencies": {



    "jquery": "1.9.1 - 3",



    "popper.js": "^1.16.0"



}



表示bootstrap依賴于這兩個(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é)果。


視覺設(shè)計(jì)師與用戶體驗(yàn)地圖

前端達(dá)人

點(diǎn)擊查看原圖


用戶體驗(yàn)地圖(Customer Journey Map)是什么?

用戶體驗(yàn)地圖是從用戶的視角出發(fā),去理解用戶、產(chǎn)品或者服務(wù)交互的一個(gè)重要的設(shè)計(jì)工具。

也可以說是以可視化的形式,來表現(xiàn)一個(gè)用戶使用產(chǎn)品或者接受服務(wù)的體驗(yàn)情況,從體驗(yàn)的過程中來發(fā)現(xiàn)用戶在整個(gè)體驗(yàn)過程中的問題點(diǎn)與情緒點(diǎn),以此來從中提取出產(chǎn)品的優(yōu)化點(diǎn),方便對(duì)產(chǎn)品進(jìn)行迭代,從而保證良好的用戶體驗(yàn)。

經(jīng)典案例

Chris Risdon繪制的歐洲鐵路購(gòu)票的體驗(yàn)地圖

點(diǎn)擊查看原圖



上圖中是歐洲鐵路公司整個(gè)體驗(yàn)地圖的一部分。歐洲鐵路公司是一家美國(guó)經(jīng)銷商,為北美旅客提供一個(gè)獨(dú)立預(yù)訂火車票去歐洲各地的平臺(tái),而無需用戶去網(wǎng)站預(yù)定。他們已經(jīng)擁有了一個(gè)良好體驗(yàn)的網(wǎng)站和一個(gè)屢獲殊榮的咨詢中心,但他們希望通過所有接觸點(diǎn)來優(yōu)化用戶使用過程,這樣可以讓他們更全面地了解,他們應(yīng)該專注的投資,設(shè)計(jì)和技術(shù)資源。整體的“診斷”評(píng)價(jià)系統(tǒng),包含一系列的重點(diǎn)舉措,體驗(yàn)地圖只是其中派生的一部分。體驗(yàn)地圖幫助建立同理心圖,來理解隨著時(shí)間和空間的推移,用戶與歐洲鐵路公司服務(wù)系統(tǒng)交互時(shí)接觸點(diǎn)的變化。

在這張?bào)w驗(yàn)地圖中采用了五個(gè)關(guān)鍵組成一個(gè)體驗(yàn)地圖,一個(gè)體驗(yàn)地圖可以直觀的表示用戶操作流、期望、特定的目標(biāo)、用戶情緒狀態(tài)和整體的體驗(yàn)點(diǎn),做到整體把控和評(píng)估產(chǎn)品體驗(yàn)。

作用 :

點(diǎn)擊查看原圖



用戶體驗(yàn)地圖能幫助我們創(chuàng)造出一個(gè)有大局觀的用戶體驗(yàn),更好的幫助我們理解用戶的痛點(diǎn)和需求,幫助Team達(dá)成共識(shí),非常有利于跨團(tuán)隊(duì)合作。

用戶體驗(yàn)地圖包含的內(nèi)容 :

01b5465c17ca39a8012092526f5b27.jpg



其中包括,人群(產(chǎn)品的用戶是哪一類人)、 用戶的需求(用戶想得到什么)、 路徑(在某特定的場(chǎng)景下體驗(yàn)的整體過程) 、接觸點(diǎn)  (產(chǎn)品與人或人與服務(wù)接觸的關(guān)鍵點(diǎn))、行為(用戶的行為是什么樣的?)、情緒  (體驗(yàn)過程中的感受心情) 、機(jī)會(huì)點(diǎn)  (過程中可以突破的點(diǎn),可以成為特色的地方)、 解決方案  (解決用戶在體驗(yàn)過程的痛點(diǎn))、 問題  (解決用戶在體驗(yàn)過程的痛點(diǎn))。

用戶畫像 :

01c6935c17ca72a80121ab5d78d1c9.jpg


015b475c17ca8aa80120925274b13f.jpg


在準(zhǔn)備開始繪制用戶體驗(yàn)地圖的時(shí)候,我們應(yīng)該要確立用戶群體 / 確定產(chǎn)品目標(biāo) / 了解用戶目標(biāo),并作出用戶畫像。

視覺設(shè)計(jì)師怎么使用

舉例(一):

01e1835c17cabfa80121ab5d29eade.jpg



那我們看看作為一名視覺設(shè)計(jì)師應(yīng)該關(guān)注哪部分的流程。

視覺設(shè)計(jì)師的用戶體驗(yàn)地圖 :

01e7375c17cae4a801209252612bf2.jpg


我們的聚焦點(diǎn)應(yīng)在上圖的這幾個(gè)部分。

0147a05c17caf9a80121ab5d287c70.jpg


所以當(dāng)繪制完用戶體驗(yàn)地圖后,應(yīng)該再繪制一份視覺設(shè)計(jì)師看的版本,我們?cè)O(shè)計(jì)師主要關(guān)注的視覺的觸點(diǎn)。

定量方法(產(chǎn)品方向):

我們?cè)谛袨楹颓榫w上一般會(huì)使用問卷法、后臺(tái)數(shù)據(jù)分析法;而在需求和問題上一般會(huì)使用焦點(diǎn)小組、訪談法、觀察法、日志法和田野調(diào)查,下面就為大家來解釋下這些方法。

焦點(diǎn)小組:是指從研究產(chǎn)品中所確定的全部用戶群(總體)中抽取一定數(shù)量的用戶來組成樣本,根據(jù)樣本信息推斷用戶群總體特征的一種調(diào)查方法。

訪談法:訪談,就是以口頭形式向用戶進(jìn)行詢問,根據(jù)被詢用戶的答復(fù)搜集客觀的、不帶偏見的事實(shí)信息,以準(zhǔn)確地說明樣本所要代表的總體的一種方式。

觀察法:觀察法是指研究者根據(jù)一定的研究目的、研究提綱或觀察表,用自己的感官和輔助工具去直接觀察用戶,從而獲得資料的一種方法。

日志法:是由用研人員按時(shí)間順序,詳細(xì)記錄自己在一段時(shí)間內(nèi)使用產(chǎn)品的過程,經(jīng)過歸納、分析,達(dá)到分析產(chǎn)品目的的一種工作分析方法。

田野調(diào)查:在日常生活中,在一個(gè)有一個(gè)嚴(yán)格定義的空間和時(shí)間的范圍內(nèi),體驗(yàn)特定用戶群的日常生活與思想境界,通過記錄自己的生活的方方面面,來展示不同階段用戶群的基本需求。

注意事項(xiàng)(5要點(diǎn))

1. 在制作地圖前,應(yīng)理清楚產(chǎn)品的前期規(guī)劃和需求,并且與同事達(dá)成共識(shí)。

2. 避免以自己的經(jīng)驗(yàn)或者認(rèn)知來確定用戶體驗(yàn)地圖中的接觸點(diǎn),應(yīng)當(dāng)真正的從用戶的行為中去提取。

3. 不要將一些落后的信息加入到用戶體驗(yàn)地圖中。

4. 最好先在Team內(nèi)部腦暴一份地圖,再去與所制作的地圖進(jìn)行對(duì)比。

5. 用戶體驗(yàn)地圖不會(huì)涉及到實(shí)現(xiàn)方案和現(xiàn)實(shí)機(jī)制,只涉及用戶的體驗(yàn)。

團(tuán)隊(duì)人員的合理搭配 :

將公司或者團(tuán)隊(duì)的PM、RD、運(yùn)營(yíng)、Leader等過來,詳細(xì)的描述這一份用戶體驗(yàn)地圖,聆聽他們的反饋。

在分析用戶問題上 :

分為四個(gè)等級(jí):ABCD,在對(duì)優(yōu)先級(jí)進(jìn)行排列的同時(shí)應(yīng)該,考慮到產(chǎn)品在每個(gè)階段的側(cè)重點(diǎn),根據(jù)不同的進(jìn)度和情況,來對(duì)優(yōu)先級(jí)進(jìn)行排列,幫助我們整理問題和提煉最核心的一些體驗(yàn)問題,區(qū)分問題還能幫助我們更好的把握產(chǎn)品的優(yōu)化方向。

視覺設(shè)計(jì)師應(yīng)該關(guān)注的點(diǎn) :

01c9015c17cbf7a801209252f6af88.jpg



視覺設(shè)計(jì)師的任務(wù)是什么?是有效的傳達(dá)出產(chǎn)品的信息、簡(jiǎn)潔并且優(yōu)雅的傳達(dá)、通過視覺設(shè)計(jì)制造出愉悅的用戶體驗(yàn)。用戶在很多的場(chǎng)景下都可能接觸到企業(yè)的產(chǎn)品或者是服務(wù),這個(gè)服務(wù)接觸帶給用戶的感受更多是偏向于視覺感知方面的。所以我們需要盡可能的列舉出企業(yè)的產(chǎn)品或者服務(wù)與用戶可能產(chǎn)生接觸的場(chǎng)景、服務(wù)觸點(diǎn),再根據(jù)服務(wù)觸點(diǎn)延伸出相關(guān)的“視覺觸點(diǎn)”,用來梳理出我們需要輸出的視覺產(chǎn)物,做出相對(duì)應(yīng)的查漏補(bǔ)缺和優(yōu)化,輸出指導(dǎo)企業(yè)的品牌建設(shè)工作。而用戶體驗(yàn)地圖就很適合作為這樣的工具。

“體驗(yàn)地圖”對(duì)于優(yōu)化視覺體驗(yàn)的意義 :

01fea65c17cc21a80121ab5d431aa7.jpg


整體性:系統(tǒng)性地規(guī)劃品牌的視覺統(tǒng)一化工作,提升品牌建設(shè)工作的全面性和完整度。也可以避免未來工作中不同的品牌 / UI / 運(yùn)營(yíng)設(shè)計(jì)師對(duì)于品牌概念的理解不同而帶來的設(shè)計(jì)出入。

01d6455c17cc3ba80121ab5d399fc3.jpg


品牌設(shè)計(jì),是用戶對(duì)于公司產(chǎn)品的直接印象,所以在品牌設(shè)計(jì)的要求就是:建立特征、保持特征、推廣特征、美化特征、對(duì)于以上的要求,來提供完整且匹配的設(shè)計(jì)方案。

運(yùn)營(yíng)設(shè)計(jì),運(yùn)營(yíng)設(shè)計(jì)的目標(biāo)就是讓用戶盡可能的感知到產(chǎn)品的好,把產(chǎn)品的特點(diǎn)通過設(shè)計(jì)包裝傳遞給用戶,一個(gè)好的運(yùn)營(yíng)設(shè)計(jì),應(yīng)該是在用戶看到你的設(shè)計(jì)作品后,會(huì)產(chǎn)生足夠好的興趣和好感,并愿意去關(guān)注你的產(chǎn)品。

UI設(shè)計(jì),這是產(chǎn)品與用戶接觸過程中,頻率最高、最直觀的部分,目的是為了讓用戶認(rèn)識(shí)到產(chǎn)品的相貌和氣質(zhì),UI設(shè)計(jì)需要注意界面視覺層次的強(qiáng)弱、信息劃分、用戶的視線軌跡、色彩的表達(dá)、質(zhì)感、舒適度等,來讓用戶覺得這個(gè)產(chǎn)品設(shè)計(jì)真好。

例如 :

OFO,以年輕人為主的共享騎行產(chǎn)品,無論是在品牌/運(yùn)營(yíng)/UI的設(shè)計(jì)上,都能讓人感覺時(shí)尚、年輕、陽光、且有親和力。

品牌設(shè)計(jì) :


0151145c17cc66a80121ab5ddb966a.jpg

UI設(shè)計(jì) :

01386c5c17cc9da80121ab5dd27af4.jpg


運(yùn)營(yíng)設(shè)計(jì) :

01c5c35c17ccbea8012092520ea55d.jpg

UI設(shè)計(jì) :

0105375c17cd2aa801209252066123.jpg


運(yùn)營(yíng)設(shè)計(jì) :

0105375c17cd2aa801209252066123.jpg

設(shè)計(jì)師的進(jìn)階 :

01ff0e5c17cd5aa801209252db7ebb.jpg

在一開始的初級(jí)設(shè)計(jì)師階段(也就是1.0階段),我們需要從交互設(shè)計(jì)師手中接過交互設(shè)計(jì)稿,來對(duì)它進(jìn)行氣質(zhì)進(jìn)行改造,做出獨(dú)特的視覺設(shè)計(jì),也就是將其翻譯為高保真稿,然后再與開發(fā)同學(xué)進(jìn)行對(duì)接,也要保持視覺走查,以防實(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。

0172935c17cd76a80121ab5d42695b.jpg

在高級(jí)設(shè)計(jì)階段(即2.0階段),這時(shí)候你就需要擁有更好的產(chǎn)品思維和邏輯能力,不僅僅只是從交互設(shè)計(jì)師拿到交互設(shè)計(jì)稿,直接上手開做,在這之前,你需要開始了解產(chǎn)品的業(yè)務(wù)定位、用戶人群、產(chǎn)品目標(biāo)、當(dāng)前的問題、未來的迭代等,需求方這時(shí)候就成你的主要對(duì)接對(duì)象,需要你具備拆解需求、采集用戶的需求、擴(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ì)師不是畫師,而是解決問題的,我們?cè)谧隽四硞€(gè)設(shè)計(jì)后,就要去關(guān)注它的變化了,看看用戶的反饋、商業(yè)轉(zhuǎn)化率等等,這都是為你的下一次設(shè)計(jì)迭代做的參考。



站酷

分享到脈脈


轉(zhuǎn)自:脈脈

原文鏈接:https://maimai.cn/article/detail?fid=988630001&efid=N-uHKNnf7vXGBmaFd3lZHA&use_rn=1

vue實(shí)現(xiàn)移動(dòng)端懸浮窗效果

前端達(dá)人

本文講述,在使用VUE的移動(dòng)端實(shí)現(xiàn)類似于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ì)取消當(dāng)前的touch操作,即觸發(fā)touchcancel。一般會(huì)在touchcancel時(shí)暫停游戲、存檔等操作。

效果圖

實(shí)現(xiàn)步驟

1.html

總結(jié)了一下評(píng)論,好像發(fā)現(xiàn)大家都碰到了滑動(dòng)的問題。就在這里提醒一下吧??蓪⒃搼腋?DIV 同你的 scroller web 同級(jí)。 —- (log: 2018-08-21)

html結(jié)構(gòu): <template> <div>你的web頁面</div> <div>懸浮DIV</div> </template>

<template>
 <div id="webId">
 ...
 <div>你的web頁面</div>
 <!-- 如果碰到滑動(dòng)問題,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";
  //阻止頁面的滑動(dòng)默認(rèn)事件;如果碰到滑動(dòng)問題,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)問題,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邏輯,基本上,問題不大。

本文鏈接 http://www.luyixian.cn/javascript_show_166242.aspx



再加一點(diǎn)

css之display:inline-block布局

1.解釋一下display的幾個(gè)常用的屬性值,inline , block, inline-block

  • inline(行內(nèi)元素):
    1. 使元素變成行內(nèi)元素,擁有行內(nèi)元素的特性,即可以與其他行內(nèi)元素共享一行,不會(huì)獨(dú)占一行. 
    2. 不能更改元素的height,width的值,大小由內(nèi)容撐開. 
    3. 可以使用padding上下左右都有效,margin只有l(wèi)eft和right產(chǎn)生邊距效果,但是top和bottom就不行.
  • block(塊級(jí)元素):
    1. 使元素變成塊級(jí)元素,獨(dú)占一行,在不設(shè)置自己的寬度的情況下,塊級(jí)元素會(huì)默認(rèn)填滿父級(jí)元素的寬度. 
    2. 能夠改變?cè)氐膆eight,width的值. 
    3. 可以設(shè)置padding,margin的各個(gè)屬性值,top,left,bottom,right都能夠產(chǎn)生邊距效果.
  •  inline-block(融合行內(nèi)于塊級(jí)):
    1. 結(jié)合了inline與block的一些特點(diǎn),結(jié)合了上述inline的第1個(gè)特點(diǎn)和block的第2,3個(gè)特點(diǎn).
    2. 用通俗的話講,就是不獨(dú)占一行的塊級(jí)元素。如圖:

圖一:1.png

圖二:

2.png

兩個(gè)圖可以看出,display:inline-block后塊級(jí)元素能夠在同一行顯示,有人這說不就像浮動(dòng)一樣嗎。沒錯(cuò),display:inline-block的效果幾乎和浮動(dòng)一樣,但也有不同,接下來講一下inline-block和浮動(dòng)的比較。

 

2.inline-block布局 vs 浮動(dòng)布局

    a.不同之處:對(duì)元素設(shè)置display:inline-block ,元素不會(huì)脫離文本流,而float就會(huì)使得元素脫離文本流,且還有父元素高度坍塌的效果

    b.相同之處:能在某程度上達(dá)到一樣的效果

我們先來看看這兩種布局:
圖一:display:inline-block3.png

圖二:4.png

對(duì)兩個(gè)孩子使用float:left,我在上一篇浮動(dòng)布局講過,這是父元素會(huì)高度坍塌,所以要閉合浮動(dòng),對(duì)box使用overflow:hidden,效果如下:

>>乍一看兩個(gè)都能做到幾乎相同的效果,(仔細(xì)看看display:inline-block中有間隙問題,這個(gè)留到下面再講)

c.浮動(dòng)布局不太好的地方:參差不齊的現(xiàn)象,我們看一個(gè)效果:
圖三:

圖四:

>>從圖3,4可以看出浮動(dòng)的局限性在于,若要元素排滿一行,換行后還要整齊排列,就要子元素的高度一致才行,不然就會(huì)出現(xiàn)圖三的效果,而inline-block就不會(huì)。

 

3.inline-block存在的小問題:

a.上面可以看到用了display:inline-block后,存在間隙問題,間隙為4像素,這個(gè)問題產(chǎn)生的原因是換行引起的,因?yàn)槲覀儗憳?biāo)簽時(shí)通常會(huì)在標(biāo)簽結(jié)束符后順手打個(gè)回車,而回車會(huì)產(chǎn)生回車符,回車符相當(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;}5.png
對(duì)于塊級(jí)元素:需添加{display:inline;zoom:1;}

 6.png

4.總結(jié):

display:inline-block的布局方式和浮動(dòng)的布局方式,究竟使用哪個(gè),我覺得應(yīng)該根據(jù)實(shí)際情況來決定的:
a.對(duì)于橫向排列東西來說,我更傾向與使用inline-block來布局,因?yàn)檫@樣清晰,也不用再像浮動(dòng)那樣清除浮動(dòng),害怕布局混亂等等。
b.對(duì)于浮動(dòng)布局就用于需要文字環(huán)繞的時(shí)候,畢竟這才是浮動(dòng)真正的用武之地,水平排列的是就交給inline-block了。



node 模塊簡(jiǎn)述

seo達(dá)人

Node 的os模塊是操作系統(tǒng)的

Node 的內(nèi)置模塊 fs


內(nèi)置模塊在下載node的時(shí)候就自帶的,使用 require()方法來導(dǎo)入

語法 :require(‘模塊fs’)



在內(nèi)置模塊中的方法

1 fs.readFile() —》用來專門 異步 讀取文件的方法 三個(gè)參數(shù)

語法 :fs.readFile(‘要讀取的文件’,讀取文件的格式,讀取成功的回調(diào)函數(shù))

Eg : fs.readFIle(‘a(chǎn)’,’utf8’,’function(err,data){ })



2 fs.readFileSync()-– 專門用來 同步 讀取的方法, 兩個(gè)參數(shù)

語法: fs.readFileSync(‘要讀取的文件’,讀取格式)



3 fs.writeFIle() —>用來寫入 異步 文件的方法 三個(gè)參數(shù)

語法: fs.writeFile(‘寫入到哪個(gè)文件’,寫入的內(nèi)容,成功的回調(diào)函數(shù))

Eg: fs.writeFile(‘./text.tex’,”內(nèi)容”, function(){ })

注意:再次寫入的內(nèi)容會(huì)完全覆蓋 。如果文件夾沒有 會(huì)自動(dòng)創(chuàng)建一個(gè)文件夾



4 fs.writeFileSync() --> 同步寫入的方法

語法: fs.writeFileSync(‘寫入到文件’,“寫入的內(nèi)容”)



Node的http模塊

這個(gè)模塊專門用來創(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)聽端口號(hào)的時(shí)候,就變成了服務(wù)器。

2 監(jiān)聽端口號(hào)

創(chuàng)建的服務(wù).listen(監(jiān)聽的端口號(hào),監(jiān)聽成功的回調(diào)函數(shù)(選填))

server.listen(8080,function(){ 端口號(hào)0-65535 建議0-1023不使用 })

此時(shí)瀏覽器就可以執(zhí)行l(wèi)ocalhost進(jìn)行訪問了



自定義模塊

每一個(gè)js文件都是一個(gè)獨(dú)立的模塊,他們都自帶一個(gè) module 是一個(gè)對(duì)象,

其中 module里面的 exports,是一個(gè)對(duì)象 這個(gè) module.exports 就是這個(gè)文件向外導(dǎo)出的內(nèi)容,也就是說,只有導(dǎo)出,才能導(dǎo)入



Eg: function fn1(){console.log() }

Module.exports.fn1=fn1

這樣,才能是另一個(gè)js文件到入這個(gè)文件 同樣也是require(‘./js’)方法


教你用面向?qū)ο缶幊虒懸粋€(gè)煙花爆炸的

前端達(dá)人

點(diǎn)擊查看原圖



想要學(xué)會(huì)這個(gè)漂亮的煙花嗎?快來跟著學(xué)習(xí)吧~

結(jié)構(gòu)

<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òu)造函數(shù)Firework

需要用到一個(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)
    }



封裝一個(gè)運(yùn)動(dòng)的方法
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服務(wù)總是崩潰的解決辦法

seo達(dá)人

許多人都有這樣一種映像,NodeJS比較快; 但是因?yà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í)上呢?

 


事實(shí)上NodeJS里程確實(shí)有“脆弱”的一面,單線程的某處產(chǎn)生了“未處理的”異常確實(shí)會(huì)導(dǎo)致整個(gè)Node.JS的崩潰退出,來看個(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è)問題都解決不了,那估計(jì)早就沒人用了。 

 

使用uncaughtException


我們可以u(píng)ncaughtException來全局捕獲未捕獲的Error,同時(shí)你還可以將此函數(shù)的調(diào)用棧打印出來,捕獲之后可以有效防止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ù), 但這種方法很多人都是不提倡的,說明你還不能完全掌控Node.JS的異常。 

 

使用 try/catch


我們還可以在回調(diào)前加try/catch,同樣確保線程的安全。 

 

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)頁上。 

 

集成到框架中


標(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ì)完美地解決異常問題,而且不會(huì)影響其它用戶的請(qǐng)求request。 

事實(shí)上現(xiàn)在的NodeJS WEB框架幾乎都是這么做的,如 OurJS開源博客所基于的 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程序根本就起不起來。 

此外node自帶的 cluster 也有一定的容錯(cuò)能力,它跟nginx的worker很類似,但消耗資源(內(nèi)存)略大,編程也不是很方便,OurJS并沒有采用此種設(shè)計(jì)。 

 

守護(hù)NodeJS進(jìn)程和記錄錯(cuò)誤日志


現(xiàn) 在已經(jīng)基本上解決了Node.JS因異常而崩潰的問題,不過任何平臺(tái)都不是100%可靠的,還有一些錯(cuò)誤是從Node底層拋出的,有些異常 try/catch和uncaughtException都無法捕獲。之前在運(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來守護(hù)node


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


 

使用shell啟動(dòng)腳本守護(hù)node


使用node來守護(hù)的話資源開銷可能會(huì)有點(diǎn)大,而且也會(huì)略顯復(fù)雜,OurJS直接在開機(jī)啟動(dòng)腳本來進(jìn)程線程守護(hù)。 

如在debian中放置的 ourjs 開機(jī)啟動(dòng)文件: /etc/init.d/ourjs 

這個(gè)文件非常簡(jiǎn)單,只有啟動(dòng)的選項(xiàng),守護(hù)的核心功能是由一個(gè)無限循環(huán) while true; 來實(shí)現(xiàn)的,為了防止過于密集的錯(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。

日歷

鏈接

個(gè)人資料

存檔