隨著大數據產業的蓬勃發展,很多企業都開始應用數據可視化。所以數據可視化設計,絕對是熱門的設計之一。很多 UI 設計師突然會接到公司數據可視化設計的需求,如果不了解數據可視化設計,肯定是一頭霧水,不知從何入手。本文結合最近設計案例,分享大屏可視化設計過程中遇到的一些問題以及設計思路,供大家一起交流與學習。
△ 最終動態效果圖
首先放的是項目改版前的頁面:
1. 需求介紹
某某應用云,分為五大云平臺模塊:云端綜合調度、數據查詢通道、數據應用處理、數據存儲管理、管理運行維護。每個大模塊下?有若干個子系統。
可視化?屏首頁需要展示的內容包括:
2. 需求分析
分析大屏可視化的一些共性:
結合大屏的一些共性特點針對看到的線上舊版本設計,分析存在的問題。
3. 布局
整合數據,分析出主要數據、次要數據、總量數據、細分數據、各數據的維度等等。首先優化頁面布局,可以先在紙上畫一畫,然后腦子里有大概思路以后再用電腦繪制,如下圖:
采用柵格化對稱布局,讓整體視覺左右平衡。
4. 風格
一提到數據可視化大家往往能想到科技、數據、藍色等一些普遍關鍵詞。
了解到客戶是想做一個科技感強、炫酷的視覺效果??梢栽诰W上找一些效果圖或是自己曾經做過的案例供客戶選擇,確定一個大致的風格,然后結合具體的業務場景進行設計。
5. 顏色
顏色上結合產品使用場景,以及整個產品調性還是以藍色為主,背景選用深色調,讓視覺更好聚焦,內容部分采用比較透亮的藍色系,保證內容與背景有一定的對比關系,便于業務信息傳達。
6. 主體地圖
地圖為大屏的主要展示內容,首先分析展示的目的是為了看清各個城市間的不同分布情況,和城市數據的匯集效果。
如圖:
改版前:地圖過于單薄,沒有立體感,太平缺乏層次,顏色黃色不符合產品調性。
改版后:主色調改為科技藍,在原有地圖上增加外發光和多層陰影疊加,增加地圖的立體感,地圖上增加科技線條上升的效果代表每個城市數據變化的提升,地圖背景采用比較弱化的轉動線條圓形,襯托地圖主體,使得畫面更加豐富。
7. 地圖效果的實現方法
首先用 ps 拉框助手新建一個山東的地圖(拉框助手的使用獲取方法可以參照上篇文章)。
完成后會得到一個叫 map 文件夾的地圖分層文件,如圖所示。這里需要對每個城市的顏色進行調整,為了區分每個城市之間的數據不同關系。
調整完塊之間的顏色后,就需要給地圖整體增加立體效果。
首先,是整體給地圖加了一個描邊和外發光。描邊是為了強化地圖邊緣,外發光是為了地圖與背景有一個區分。
其次為了增加地圖立體感,需要給地圖增加多層陰影疊加的效果。復制現有形狀層,拼合成一個山東省的地圖,如下圖:
最后,把拼合好的圖層移動到 map 文件夾下面,陰影可以添加多層,這里針對每一層進行不同顏色大小的調整,就是下面的這種效果了,地圖的體積厚度感也就出來了。這里只是提供一個大概的思路,大家可以多去嘗試。
整體地圖效果調整完成后,就是給地圖增加些紋理,和上升線條這些細節上的效果了。紋理可以用圖案疊加,或者找一張紋理圖剪切蒙版實現,最后再添加上升線條的效果,地圖的效果就完成了。
最后加上線條上升的動態效果:
8. 數據圖表拆分
在選定數據圖表之前,首先要確定圖表之間的關系,可以從以下四個維度進行思考分析:
可以參照下面這個圖:
△ 圖片來自于網絡,侵刪
當確定好分析維度后,事實上我們所能選用的圖表類型也就基本確定了。接下來我們只需要從少數幾個圖表里篩選出最能體現我們設計意圖的那個就好了。
傳統的圖表比如 echarts 圖表在視覺上展示可能不是很美觀好看,可根據選擇的圖表在其基礎之上進行美化設計,總之選定圖表最重要的兩個點就是:易理解、可實現。
易理解:就是要考慮最終用戶,可視化結果應該是一看就懂,不需要思考和過度理解,因而選定圖表時要理性,避免為了視覺上的效果而選擇一些對用戶不太友好的圖形及元素。
可實現:主要是跟開發前期溝通好實現方式,一般都采用開源組件庫的居多,比如 echarts 組件庫,我們設計的圖形圖表,要開發能夠實現。實際工作中,一些可視化效果開發用代碼寫很容易實現,效果也不錯,但這些效果設計師用 Ps/Ai/Ae 這些工具模擬時會發現比較困難。同樣的,某些效果設計師用設計工具可以輕易實現,但開發要用代碼落地卻非常困難,所以大屏設計中跟開發常溝通非常重要,我們需要明確哪些地方設計師可以盡情發揮,哪些地方需要謹慎設計。一個項目總有周期與預算限制,不會無限期的修改迭代,所以設計師在這里需要抓住重點,有取舍,不鉆牛角尖、死磕不放。
案例中在圖表選擇上,強化科技感元素,條形圖打破傳統條形圖的展示形式,采用電池晶格的展示形式,在保持圖表功能的同時更加凸顯科技感。
從頁面的整體看,已經有兩處用到了條形圖、柱狀圖,如果這里還是條形圖,那么頁面看起來會很單調,圖表也沒有表現出多樣性,所以現在設計要體現圖表的多樣性也能夠有排名的直觀呈現。以下圖表采用科技圓盤的形式,運用科技線條的上升狀態代表排名的先后順序,所有圖表都采用數據降序來展示排名更加直觀。
改版前的圖標樣式比較單一,改版后針對每組數據不同的對比形式,采用比較貼合的圖表進行展示,篇幅原因就不一一做展示了。
附上最終視覺效果圖:
大屏設計需要注意的點:
以上是我對數據可視化大屏的案例總結,希望能幫助到你。除此之外還有很多地方沒有涉及到,包括具體設計的操作方式、選取圖形元素的具體方法,以及在各種大屏中所需要的相對應的組件等,在龐大的可視化大屏設計系統中,還有很多值得學習參考和優化的知識,歡迎溝通交流,大家一起努力。
文章來源:優設
有句話叫:「設計無小事」,很多看似不起眼的東西卻起著至關重要的作用,比如這期要說的線條,很多人對于線條的理解有局限性,比如:線條的形態可以是曲線、直線、折線、粗線、細線、實線、虛線等等。其實已經牽扯到了點、線、面的知識,這也是很多科班生在學校必學的知識點,但是這期所說的線與點線面中的線還是有所不同的,點線面中的線可以是線條、可以是文字或者是看不到的視線,而是今天著重說的是設計中很直觀的線條。下面我們還是通過實際的案例逐一分析:
設計類的知識很多都和日常生活息息相關,嘗試著把設計類的知識點與日常生活想結合,對于記憶和理解來說會更加得心應手,例如:
圖中的閃電可以視作為設計中的線條,給人的視覺感受是通過閃電把天與地連接為一個整體,而閃電在圖中的作用就是串聯整體,那么回到這里的正題:線條有引導視覺的作用該怎么理解呢?再舉一個現實生活中的案例:
我們選擇從北京到拉薩開車去,出發之前可能需要在地圖上看下路線,知道途徑哪些省市,規劃好行程路線,這里綠色的虛線就起到了引導視覺的作用。回歸設計中道理是一樣的,線條可以引導用戶把原本雜亂無章的視覺點規整為有次序的視覺元素,例如:
當看到左側這張海報時我們視覺次序會出現很多變化,比如:1>A>3>B>4>C>2 或者 A>2>C>4>B>3>1 等等 N 多種順序,這時給人的感覺就是雜亂無章的,毫無視覺次序而言;而看右側的海報給人的感覺卻是條例清晰的,相比而言只是多了兩條線,但是卻在整個海報中起到了引導視覺的作用,它可以給與用戶閱讀海報時視覺輔助的作用,讓用戶以右>左>右的視覺次序欣賞、閱讀,看似很不起眼,其作用卻至關重要。
前面也說了,線的形態可以有很多種,例如:
這里是以真實的可口可樂吸管作為設計中的線條,同樣起到了視覺引導的作用,但是我們不難發現,這里的線條不僅僅只有一個作用,也牽扯到另一個作用:線條有串聯整體的作用。
在排版時我們有分組原則,即把互想關聯的元素彼此靠近,無關聯的相互疏遠。在頁面中我們會把同一色塊上的元素視作為一個整體;下面我們說下線條所帶來的串聯整體的作用是如何體現的,比如:
△ 圖一
△ 圖二
圖一因為大面積的留白能使得用戶很容易分辨出自行車與文案是一個整體,但是相較于圖二而言,其整體性略顯不足,而且給人的感覺太過單薄、重心不穩;圖二的整體性更強,這里的矩形線條就起到了串聯主題的作用,類似的還有:
不難看出,這些案例中的線條都有串聯主題的作用,線條使得主題元素整體感更強、畫面板式更加嚴謹;對于整體的視覺傳達也起到了串聯、引導的作用;在文字排版中,也有類似的線條,比如:
同樣是通過線條把文案更加整體化,也起到了串聯的作用。
突出主題的方式有很多種,像我們之前所說到的留白、對比。接下來繼續說下另一個可以突出主題的方式—線條,下面看個案例:
通過對比觀察我們發現,右側海報整體感更強,主題文案信息更加清晰,主體更明確。其中的原理可以理解為:因為線條的存在,使得主題信息有了一定的范圍,在視覺上等于是在海報中劃定了視覺焦點,從而起到了突出主題的作用。當然還有其他的表現形式,比如:
很好的詮釋了線條的作用——突出標題序號。在進行創作時,作品的每個元素都要做到有理有據,否則只是一味的抄襲,到再創作時腦袋里還是一片空白,只有明白了其中的設計原理,才能做到活學活用。再看幾個案例:
突出主題也許一個線條就可以表現的淋漓盡致,因設計目的的不同,線條所發揮的作用也不盡相同。下面繼續分析:
前面說了線條有串聯整體的作用,而這里又說可以分割整體,是否存在矛盾呢?下面舉個簡單的例子:
在小文案的區域中間我加了兩個線條,看似很小的改變,其目的是把文案很準確、嚴謹地分割為三個小整體,希望能給用戶帶來識別性更強的閱讀性,再舉個例子:
這里的線條把月份和日期分割、英文和中文分割開,使得用戶對于信息的捕捉能力以及可辨識性都提升了很多,而線條的作用就起到了分割的作用。
線條也能起到修飾、襯托的作用,很多小伙伴會忽視這一點,其實線條也可以成為海報中襯托畫面、修飾主題的元素,例如:
海報中的線條起到了襯托、修飾主題的作用,假如把這些線條都刪除,畫面整體會顯得相對單薄。
更多設計中線條的應用:
線條的作用我們分為四個逐一分析,其實它們之間也存在著相輔相成的作用,不能以一概全,線條所起到的作用可以是一種,也可以是多種,比如:我們前面「可口可樂」的案例,即有串聯整體的作用,又有引導視覺的作用。只要我們在使用的時候能明確目的,而不是機械式的抄襲,那么最終一定會得心應手。
文章來源:優設 作者:美工美邦
未來熒黑是一個基于思源黑體、Fira Sans和Raleway的開源字體項目,支持簡體中文、繁體中文與日文。
思源黑體的7種字重被擴展為9種字重,并增加了5種字體寬度,全家族共包含44款字體。
相比于思源黑體,未來熒黑的造型更加簡明現代,版面效果清新輕快。未來熒黑的中宮與字面更加收斂,重心在字重之間經過了重新配置;筆畫細節上處理得更加干練,簡潔而平直化。
開發者:Celestial Phineas
字體文件以SIL Open Font License 1.1發布,構建字體開發的代碼以MIT License發布。
發布地址:github.com/welai/glow-sans
網盤地址:https://pan.baidu.com/s/1f2UuFO8ZxWa8v5XXYUEmig 提取碼 2e8w
備份下載鏈接:https://pan.baidu.com/s/1E1woRsZX91bCrq5FT1SAzg 提取碼: 92t2
文章來源:優設 作者:GrayDesign
微信在3.23號上線了傳聞已久的 「暗黑版」,用來適配 iOS 的深色模式,相信不少同學已經安裝并體驗上了,如果還沒安裝的可以看看下面圖例。
微信每有一次大動作都會引發全網性的討論,而針對設計上的調整,往往只會迎來一片罵聲。比如我們看看知乎中討論的內容,感覺民憤都快壓抑不住了。
但我們先別急著參與網上的聲討,現在站在設計師的角度,來評價一下遲到的微信深色模式。
很多人會把深色模式和夜間模式搞混,這里必須強調這是兩種不同的模式,所以我們要對還沒搞清楚狀況的同學先做一個掃盲(最近掃盲好像搞的比較多…)。
先說夜間模式,是一個專門針對夜晚環境適配的設計版本。騰訊的 ISUX 團隊之前做過調研,有 71.1% 的用戶習慣在夜間不開燈看手機。
如果在深夜漆黑的房間中看手機屏幕,對我們的健康有非常大的損害,不僅表現在對視力的傷害上面,視網膜和神經元容易受損,同時主流的研究項目還表明會這會抑制我們褪黑素的分泌造成失眠。
所以,夜間模式,是一個用來降低屏幕對用戶傷害,提升夜間使用體驗的特殊模式。
通常,夜間模式會采取 降低尼特值、減少對比度、降低色彩明度、增加深色遮罩的方法,比如之前 QQ 官方的夜間模式示意圖,大家感受一下,是不是有內味兒了?
這里提一下尼特這個概念,尼特是用來說明亮度的術語,1nit=1坎德拉/平方米。是現在各大手機發布會中介紹屏幕的時候都要強調的參數之一,因為尼特值越高,證明在戶外大白天的環境中屏幕內容可以越清晰,而夜間模式做的就是用來降低顯示亮度尼特值的。
然后再解釋一下蘋果的深色模式,蘋果的 DarkMode,并不是一個專門面向深夜環境的模式。官方對此版本的解釋,詳見我們翻譯的 iOS 官方文檔中 112 頁。
這是一個面向全天候的視覺風格,同時通過深色背景的對比,來加凸顯圖片、文字內容。包括上面那種官方的配圖,大家應該就能感覺到主體元素比白色模式下更凸出,更激烈。
所以了解了這兩個模式的區別,我們才能好好展開對微信深色模式的討論。
接下來,我們先來總結一遍微信的深色模式。首先是分析一遍它使用的背景色。
背景色純灰色,有3個等級的灰度。熟悉我的都了解,看色彩的奧秘,靠16進制代碼和 RGB 那是分析不出個什么所以然的。如果我們把它們轉化成 HSB 一看,規律性就來了。
背景色從深到淺色的灰度值 B 值分為 10、14、18,是不是朗朗上口。應用的層級雖然和官方規范一樣使用了三級,但是設置和官方的不同。
然后再看看其中使用的其它配色,其中主色保持了不變,其它應用到圖標色彩,都進行了明度的調整,比如下圖案例。
再看看文字的用色,也是純灰色,標題使用 B:85,正文使用 B:65,注釋使用 B:35(主要用色)。
而官方使用的文字色彩中,卻并不是純灰色,除了第一級的白色以外,其它灰階的文字是由帶有藍色色相的色彩通過降低透明度來呈現的。
對中性色增加藍色色值是一個常規操作,可以讓這種灰色看起來有一點活力,不會像純灰色那么單調,不過這次微信明顯在文字的應用上更保守,一點色相也沒有給。
這次微信被大面積吐槽的,就是顏色的應用上和官方的規范不一致,作為從業人員直接開噴是相當不專業的(最起碼要先走個形式),這就是我要分析的重點了。
要說微信的 UED 團隊,專業性肯定是國內最頂尖的,你們網上所有看過有關交互的方法論、可用性測試的分享, 他們肯定都有做過,而且執行得更專業。
直接用官方規范的黑底白字模式,微信團隊不可能沒有出過這樣的方案。但很明顯,這個方案最后被斃了,上了我們看見的這個版本,雖然不知道以后會不會變。
再看看下面官方發的一條微博。
其中已經提及了,是和蘋果 「共同探索」 出來的方案,這是非常耐人尋味的。也就是說,這個不用官方的模式是蘋果團隊也通過的。
那么為什么要做的不一樣呢?沒有內幕消息,就根據自己的經驗來判斷一下。
我自己認為的一個非常重要的原因,就是對于 「夜間模式「 的兼容。前面不是講暗黑模式和夜間模式不一樣嘛?為什么微信的暗黑模式又去兼容夜間模式了。
這里面有幾個小彩蛋,首先就是官方對深色模式的態度,在我看來越來越曖昧了。比如在顯示與亮度設置頁面里,有一個自動設置外觀 —— 日出前保持深色外觀的功能。這樣,就等于默認將深色模式和夜間模式掛鉤。
還有,如果過去我們沒有整理 iOS13 的翻譯,就不會發現,上面我們展示的那句專注于內容的解釋,現在在官網已經被刪掉了(你品,你細品)。
再說關于用戶認知的問題上面,在 UI 群體中,能了解暗黑模式和夜間模式是不一樣的可能就只占 10 分之一,那么對于普通用戶來說,這個情況就更不樂觀,能有 1% 的用戶了解這個概念就不錯了。所以,絕大多數用戶會直接認為暗黑模式就是夜間模式。
如果暗黑模式直接當成夜間模式用,在深夜的環境里,觀感會特別差,因為 —— 明暗對比度過高。
如果在黑底中直接用白字,那么可以說屏幕的文字和背景的對比度就是 100(HSB中 B 的差值),在一個漆黑的環境中會非常應驗 「讓內容脫穎而出」 的效果,刺激性會非常強烈,文字將變得非常尖銳,比如 QQ 暗黑模式,大家可以在被窩里打開下面這張圖感受一下。
新的深色模式版本中,文字和背景的對比度基本控制在 50 左右,降低了一半。
并且,中英文字形在正負形上的差異(簡單理解就是中文筆劃更復雜),這個感覺會更強烈。比如我們用一個公眾號頁面舉例,使用純黑底白字,采用相同字號的中英文,看看顯示的效果。
還有,純黑背景色和白色的對比度,會根據屏幕的類型和參數不同而有不一樣的感受,比如蘋果從 X 后旗艦機型使用 OLED 屏幕,純黑色區域將不會發光,進一步擴大對比度,使得文字變得更尖銳,更讓人難以接受。
如果不是使用 OLED 屏幕的同學光看一個案例可能很難受,所以我們用純黑的案例來對比一下現在的狀態。
是不是發現明顯在夜間的情況下黑白模式并不如另一個模式看起來舒適?而這種不舒適的差別并不會隨著屏幕亮度降低而變化。
所以色彩并沒有符合官方的原因,我的判斷就在大環境中無法割裂夜間和深色模式的區別,同時也要讓深色模式更適應夜間環境,做出了調整。而又因為它不是真正的夜間模式,所以對比度也不能像 QQ 之前的夜間版本一樣將整體環境完全壓暗。
你看,真是一個讓人矛盾的過程……
最后再簡單討論一個問題,為什么深色模式來得這么晚。很多用戶一直嘲諷,不就是換一套皮膚的事嘛,為什么就是不上線。
外行可以看熱鬧,但是如果是從業人員就應該知道,微信這種體量的應用,上線深色模式絕對不是一個非常容易的事情。
適配黑暗模式首先需要使用蘋果新的 iOS 13 SDK(開發者工具)進行編譯,等于應用中有大量的代碼需要調整,而這種升級調整的結果還會導致沉重的測試壓力。有經歷過 Darkmode 開發的團隊應該都知道這絕對不是改改顏色就能上線的皮膚。
再看到知乎另一個回答中提到的:
另一方面點大家隨便聽聽。使用 iOS 13 SDK 之后,Apple 要求 VoIP 推送必須使用 CallKit,否則應用程序會被終止。而由于眾所周知的原因,CallKit 在中國大陸是無法使用的,這樣的改動會降低微信語音電話的體驗。
原文地址:https://www.zhihu.com/question/378027349/answer/1069072154
再者,拋去大量用戶體驗調研相關的工作,微信整個生態對于暗黑模式的不友好可以說是無解的。比如說公眾號,有大量公眾號內部的標題、分割線、引用語句是用圖片做上去的,而圖片還用的是白底(透明底黑字的也有),于是現在就產生了災難性的閱讀體驗。
比如我的公眾號:超人的電話亭,其中文章展示的截圖。
而且因為公眾號發出去是不能修改的,只能刪除,那么這部分存量文章將無法更改,體驗也無法扭轉。而且公眾號還支持文字色彩等自定義,那么你在白色背景下添加的顏色,可不會直接適配深色模式,尤其是官方也不可能輕易直接給你們 「適配」 掉。
而在夜間模式,正常訪問的文章網頁,也和公眾號會很像,但是打開以后是白色背景的話,統一的體驗在哪里?
再者還有小程序,小程序雖然也可以通過微信官方提供小程序的深色模式適配文檔,對應的 SDK,但是小程序不是 APP,其中有大量小程序開發后是缺少維護的。
因為線下門店通過外包方做好一個小程序上線以后,沒特殊的原因不會直接去更新它,那么這部分小程序的升級適配無從談起,會出現打開小程序一個白一個黑的窘境。
最后,再講一個微信里最高頻使用的功能 —— 發表情。深色模式直接造成大量自定義表情報廢,無法正常顯示的問題,比如看看下面我自己發的表情。
前面提到的,都是不能解決的問題,這就是做深色模式的挑戰,因為用戶 UGC 內容是不可控的,官方不可能通過算法直接幫用戶強行 「適配」。
而這些,就是做深色版的難點。
以上總結內容多數為主觀分析,純粹站在 UI 設計師角度進行專業解讀,不帶入個人立場。而一定要我自己評價的話,那就是 :趕緊把這模式給我移除!!
再順便提一點小感想,一個有數億用戶的產品,每一個小調整分量都不輕,都要慎之又慎。同時,你做的每一個決策,都意味著要站在其中一部分用戶的對立面,因為你滿足不了所有用戶的需求。所以,這就是設計師的壓力與挑戰。
文章來源:優設 作者:超人的電話亭
網上對于這兩個的區別解釋都是統一口徑的,一個是開發依賴,一個是線上依賴,打包發布需要用到的要添加到線上依賴,一模一樣的回答,誤導了很多人。今天自己測試一下這兩個命令,記錄一下。
–save-dev,會在devDependencies里面添加依賴
-D,會在devDependencies里面添加依賴
–save,會在dependencies里面添加依賴
-S,會在dependencies里面添加依賴
devDependencies和dependencies可以同時存在同一個包的依賴。
如果npm install xxx后面沒有輸入要保存到哪個里面,devDependencies和dependencies都沒有。
我這邊直接npm install jquery,node_modules下有jQuery。然后我刪除node_modules,執行npm install,node_modules下并沒有下載jQuery。
所以,安裝依賴的時候如果沒有加上要依賴到開發還是線上,只是臨時的在node_modules里面幫你下載,而devDependencies和dependencies的依賴都會幫你下載。
然后我在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。
測試的結果就是,無論是–save還是–save-dev,對于打包都沒有任何影響。devDependencies和dependencies兩種情況,打包出來的main.js都把jQuery打包進去。這兩種情況,如果都沒有引用jQuery的情況下,也都不會把jQuery打包。
接著在一個空白的項目里面下載axios,npm install axios -S,打開node_modules文件夾:
發現多出了另外三個依賴,查看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。
而這幾個包的devDependencies依賴的包沒有一個下載。
接著我在node_modules把follow-redirects、debugs、ms都刪了,把axios里面的package.js的dependencies給刪了,然后執行npm install,發現沒有下載follow-redirects、debugs、ms這幾個,也證明了如果node_modules里面有下載的包,是不會重新去下載的。我把node_modules刪除,執行npm install,這幾個包又都下載下來了。
最后得出 的結論是,–save-dev和–save在平時開發的時候,對于打包部署上線是沒有任何影響的。如果你是發布一個包給別人用,而你開發的包依賴第三方的包,那么你如果是–save,那么別人安裝你開發的包,會默認下載你依賴的包,如果你是–save-dev,那么別人安裝你開發的包,是不會默認幫忙下載你依賴的包。
其實發布的包如果沒有必要,很少會默認幫你下載,比如bootstrap,依賴jQuery,怕你原本就下載了引起沖突,也不會在dependencies里面安裝jQuery而是:
"peerDependencies": {
"jquery": "1.9.1 - 3",
"popper.js": "^1.16.0"
}
表示bootstrap依賴于這兩個包,你必須安裝,版本不固定,但是一定要安裝這兩個包,安裝的時候會有警告:
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
當你引用了然后打包,報錯:
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
以上就是對–save和–save-dev的一些測試,想更快的得出結論其實是自己發布一個包。至于本人的答案是不是存在錯誤,歡迎指出,因為只是自己簡單測試的結果。
用戶體驗地圖(Customer Journey Map)是什么?
用戶體驗地圖是從用戶的視角出發,去理解用戶、產品或者服務交互的一個重要的設計工具。
也可以說是以可視化的形式,來表現一個用戶使用產品或者接受服務的體驗情況,從體驗的過程中來發現用戶在整個體驗過程中的問題點與情緒點,以此來從中提取出產品的優化點,方便對產品進行迭代,從而保證良好的用戶體驗。
經典案例
Chris Risdon繪制的歐洲鐵路購票的體驗地圖
上圖中是歐洲鐵路公司整個體驗地圖的一部分。歐洲鐵路公司是一家美國經銷商,為北美旅客提供一個獨立預訂火車票去歐洲各地的平臺,而無需用戶去網站預定。他們已經擁有了一個良好體驗的網站和一個屢獲殊榮的咨詢中心,但他們希望通過所有接觸點來優化用戶使用過程,這樣可以讓他們更全面地了解,他們應該專注的投資,設計和技術資源。整體的“診斷”評價系統,包含一系列的重點舉措,體驗地圖只是其中派生的一部分。體驗地圖幫助建立同理心圖,來理解隨著時間和空間的推移,用戶與歐洲鐵路公司服務系統交互時接觸點的變化。
在這張體驗地圖中采用了五個關鍵組成一個體驗地圖,一個體驗地圖可以直觀的表示用戶操作流、期望、特定的目標、用戶情緒狀態和整體的體驗點,做到整體把控和評估產品體驗。
作用 :
用戶體驗地圖能幫助我們創造出一個有大局觀的用戶體驗,更好的幫助我們理解用戶的痛點和需求,幫助Team達成共識,非常有利于跨團隊合作。
用戶體驗地圖包含的內容 :
其中包括,人群(產品的用戶是哪一類人)、 用戶的需求(用戶想得到什么)、 路徑(在某特定的場景下體驗的整體過程) 、接觸點 (產品與人或人與服務接觸的關鍵點)、行為(用戶的行為是什么樣的?)、情緒 (體驗過程中的感受心情) 、機會點 (過程中可以突破的點,可以成為特色的地方)、 解決方案 (解決用戶在體驗過程的痛點)、 問題 (解決用戶在體驗過程的痛點)。
用戶畫像 :
在準備開始繪制用戶體驗地圖的時候,我們應該要確立用戶群體 / 確定產品目標 / 了解用戶目標,并作出用戶畫像。
視覺設計師怎么使用
舉例(一):
那我們看看作為一名視覺設計師應該關注哪部分的流程。
視覺設計師的用戶體驗地圖 :
我們的聚焦點應在上圖的這幾個部分。
所以當繪制完用戶體驗地圖后,應該再繪制一份視覺設計師看的版本,我們設計師主要關注的視覺的觸點。
定量方法(產品方向):
我們在行為和情緒上一般會使用問卷法、后臺數據分析法;而在需求和問題上一般會使用焦點小組、訪談法、觀察法、日志法和田野調查,下面就為大家來解釋下這些方法。
焦點小組:是指從研究產品中所確定的全部用戶群(總體)中抽取一定數量的用戶來組成樣本,根據樣本信息推斷用戶群總體特征的一種調查方法。
訪談法:訪談,就是以口頭形式向用戶進行詢問,根據被詢用戶的答復搜集客觀的、不帶偏見的事實信息,以準確地說明樣本所要代表的總體的一種方式。
觀察法:觀察法是指研究者根據一定的研究目的、研究提綱或觀察表,用自己的感官和輔助工具去直接觀察用戶,從而獲得資料的一種方法。
日志法:是由用研人員按時間順序,詳細記錄自己在一段時間內使用產品的過程,經過歸納、分析,達到分析產品目的的一種工作分析方法。
田野調查:在日常生活中,在一個有一個嚴格定義的空間和時間的范圍內,體驗特定用戶群的日常生活與思想境界,通過記錄自己的生活的方方面面,來展示不同階段用戶群的基本需求。
注意事項(5要點)
1. 在制作地圖前,應理清楚產品的前期規劃和需求,并且與同事達成共識。
2. 避免以自己的經驗或者認知來確定用戶體驗地圖中的接觸點,應當真正的從用戶的行為中去提取。
3. 不要將一些落后的信息加入到用戶體驗地圖中。
4. 最好先在Team內部腦暴一份地圖,再去與所制作的地圖進行對比。
5. 用戶體驗地圖不會涉及到實現方案和現實機制,只涉及用戶的體驗。
團隊人員的合理搭配 :
將公司或者團隊的PM、RD、運營、Leader等過來,詳細的描述這一份用戶體驗地圖,聆聽他們的反饋。
在分析用戶問題上 :
分為四個等級:ABCD,在對優先級進行排列的同時應該,考慮到產品在每個階段的側重點,根據不同的進度和情況,來對優先級進行排列,幫助我們整理問題和提煉最核心的一些體驗問題,區分問題還能幫助我們更好的把握產品的優化方向。
視覺設計師應該關注的點 :
視覺設計師的任務是什么?是有效的傳達出產品的信息、簡潔并且優雅的傳達、通過視覺設計制造出愉悅的用戶體驗。用戶在很多的場景下都可能接觸到企業的產品或者是服務,這個服務接觸帶給用戶的感受更多是偏向于視覺感知方面的。所以我們需要盡可能的列舉出企業的產品或者服務與用戶可能產生接觸的場景、服務觸點,再根據服務觸點延伸出相關的“視覺觸點”,用來梳理出我們需要輸出的視覺產物,做出相對應的查漏補缺和優化,輸出指導企業的品牌建設工作。而用戶體驗地圖就很適合作為這樣的工具。
“體驗地圖”對于優化視覺體驗的意義 :
整體性:系統性地規劃品牌的視覺統一化工作,提升品牌建設工作的全面性和完整度。也可以避免未來工作中不同的品牌 / UI / 運營設計師對于品牌概念的理解不同而帶來的設計出入。
品牌設計,是用戶對于公司產品的直接印象,所以在品牌設計的要求就是:建立特征、保持特征、推廣特征、美化特征、對于以上的要求,來提供完整且匹配的設計方案。
運營設計,運營設計的目標就是讓用戶盡可能的感知到產品的好,把產品的特點通過設計包裝傳遞給用戶,一個好的運營設計,應該是在用戶看到你的設計作品后,會產生足夠好的興趣和好感,并愿意去關注你的產品。
UI設計,這是產品與用戶接觸過程中,頻率最高、最直觀的部分,目的是為了讓用戶認識到產品的相貌和氣質,UI設計需要注意界面視覺層次的強弱、信息劃分、用戶的視線軌跡、色彩的表達、質感、舒適度等,來讓用戶覺得這個產品設計真好。
例如 :
OFO,以年輕人為主的共享騎行產品,無論是在品牌/運營/UI的設計上,都能讓人感覺時尚、年輕、陽光、且有親和力。
品牌設計 :
UI設計 :
運營設計 :
UI設計 :
運營設計 :
設計師的進階 :
在一開始的初級設計師階段(也就是1.0階段),我們需要從交互設計師手中接過交互設計稿,來對它進行氣質進行改造,做出獨特的視覺設計,也就是將其翻譯為高保真稿,然后再與開發同學進行對接,也要保持視覺走查,以防實際效果與預期效果的不符;在這個1.0階段我們的表現力和創造力,是最為主要的,如何去做出差異化?這是這個階段的設計師需要考慮的,在這個APP設計趨同的大浪潮下,你如果能夠做出不一樣的設計,那么你則可以一鳴驚人,從眾多水平相當的設計師中脫穎而出,這時你便可以考慮進入下一個階段,也就是2.0。
在高級設計階段(即2.0階段),這時候你就需要擁有更好的產品思維和邏輯能力,不僅僅只是從交互設計師拿到交互設計稿,直接上手開做,在這之前,你需要開始了解產品的業務定位、用戶人群、產品目標、當前的問題、未來的迭代等,需求方這時候就成你的主要對接對象,需要你具備拆解需求、采集用戶的需求、擴展業務、能進行設計驗證的能力,能將產品的氣質和品牌貫穿于整個產品(UI/運營/品牌),設計是怎么推導的,現在就不是僅僅只在停留在好看的層面上了,畢竟設計師不是畫師,而是解決問題的,我們在做了某個設計后,就要去關注它的變化了,看看用戶的反饋、商業轉化率等等,這都是為你的下一次設計迭代做的參考。
從
分享到脈脈
轉自:脈脈
原文鏈接:https://maimai.cn/article/detail?fid=988630001&efid=N-uHKNnf7vXGBmaFd3lZHA&use_rn=1
本文講述,在使用VUE的移動端實現類似于iPhone的懸浮窗的效果。
相關知識點
touchstart 當在屏幕上按下手指時觸發
touchmove 當在屏幕上移動手指時觸發
touchend 當在屏幕上抬起手指時觸發
mousedown mousemove mouseup對應的是PC端的事件
touchcancel 當一些更高級別的事件發生的時候(如電話接入或者彈出信息)會取消當前的touch操作,即觸發touchcancel。一般會在touchcancel時暫停游戲、存檔等操作。
效果圖
實現步驟
1.html
總結了一下評論,好像發現大家都碰到了滑動的問題。就在這里提醒一下吧。可將該懸浮 DIV 同你的 scroller web 同級。 —- (log: 2018-08-21)
html結構: <template> <div>你的web頁面</div> <div>懸浮DIV</div> </template>
<template> <div id="webId"> ... <div>你的web頁面</div> <!-- 如果碰到滑動問題,1.1 請檢查這里是否屬于同一點。 --> <!-- 懸浮的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: { // 實現移動端拖拽 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"; //阻止頁面的滑動默認事件;如果碰到滑動問題,1.2 請注意是否獲取到 touchmove document.addEventListener("touchmove",function(){ event.preventDefault(); },false); } }, //鼠標釋放時候的函數 end(){ this.flags = false; }, } </script>
3.CSS
<style> .xuanfu { height: 4.5rem; width: 4.5rem; /* 如果碰到滑動問題,1.3 請檢查 z-index。z-index需比web大一級*/ 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>
實現好JS邏輯,基本上,問題不大。
本文鏈接 http://www.luyixian.cn/javascript_show_166242.aspx
再加一點
css之display:inline-block布局
1.解釋一下display的幾個常用的屬性值,inline , block, inline-block
兩個圖可以看出,display:inline-block后塊級元素能夠在同一行顯示,有人這說不就像浮動一樣嗎。沒錯,display:inline-block的效果幾乎和浮動一樣,但也有不同,接下來講一下inline-block和浮動的比較。
2.inline-block布局 vs 浮動布局
a.不同之處:對元素設置display:inline-block ,元素不會脫離文本流,而float就會使得元素脫離文本流,且還有父元素高度坍塌的效果
b.相同之處:能在某程度上達到一樣的效果
我們先來看看這兩種布局:
圖一:display:inline-block
圖二:
對兩個孩子使用float:left,我在上一篇浮動布局講過,這是父元素會高度坍塌,所以要閉合浮動,對box使用overflow:hidden,效果如下:
>>乍一看兩個都能做到幾乎相同的效果,(仔細看看display:inline-block中有間隙問題,這個留到下面再講)
c.浮動布局不太好的地方:參差不齊的現象,我們看一個效果:
圖三:
圖四:
>>從圖3,4可以看出浮動的局限性在于,若要元素排滿一行,換行后還要整齊排列,就要子元素的高度一致才行,不然就會出現圖三的效果,而inline-block就不會。
3.inline-block存在的小問題:
a.上面可以看到用了display:inline-block后,存在間隙問題,間隙為4像素,這個問題產生的原因是換行引起的,因為我們寫標簽時通常會在標簽結束符后順手打個回車,而回車會產生回車符,回車符相當于空白符,通常情況下,多個連續的空白符會合并成一個空白符,而產生“空白間隙”的真正原因就是這個讓我們并不怎么注意的空白符。
b.去除空隙的方法:
1.對父元素添加,{font-size:0},即將字體大小設為0,那么那個空白符也變成0px,從而消除空隙
現在這種方法已經可以兼容各種瀏覽器,以前chrome瀏覽器是不兼容的
圖一:
c.瀏覽器兼容性:ie6/7是不兼容 display:inline-block的所以要額外處理一下:
在ie6/7下:
對于行內元素直接使用{dislplay:inline-block;}
對于塊級元素:需添加{display:inline;zoom:1;}
4.總結:
display:inline-block的布局方式和浮動的布局方式,究竟使用哪個,我覺得應該根據實際情況來決定的:
a.對于橫向排列東西來說,我更傾向與使用inline-block來布局,因為這樣清晰,也不用再像浮動那樣清除浮動,害怕布局混亂等等。
b.對于浮動布局就用于需要文字環繞的時候,畢竟這才是浮動真正的用武之地,水平排列的是就交給inline-block了。
Node 的os模塊是操作系統的
Node 的內置模塊 fs
內置模塊在下載node的時候就自帶的,使用 require()方法來導入
語法 :require(‘模塊fs’)
在內置模塊中的方法
1 fs.readFile() —》用來專門 異步 讀取文件的方法 三個參數
語法 :fs.readFile(‘要讀取的文件’,讀取文件的格式,讀取成功的回調函數)
Eg : fs.readFIle(‘a’,’utf8’,’function(err,data){ })
2 fs.readFileSync()-– 專門用來 同步 讀取的方法, 兩個參數
語法: fs.readFileSync(‘要讀取的文件’,讀取格式)
3 fs.writeFIle() —>用來寫入 異步 文件的方法 三個參數
語法: fs.writeFile(‘寫入到哪個文件’,寫入的內容,成功的回調函數)
Eg: fs.writeFile(‘./text.tex’,”內容”, function(){ })
注意:再次寫入的內容會完全覆蓋 。如果文件夾沒有 會自動創建一個文件夾
4 fs.writeFileSync() --> 同步寫入的方法
語法: fs.writeFileSync(‘寫入到文件’,“寫入的內容”)
Node的http模塊
這個模塊專門用來創建服務的
只能支持http協議。
也是使用require()方法
Const http= require(“http”)
方法
1 http.createServer(function(req,res){ }) 兩個形參
Req=request 代表每次的請求信息
Res=response 代表每次請求的響應
返回值是一個服務,當服務監聽端口號的時候,就變成了服務器。
2 監聽端口號
創建的服務.listen(監聽的端口號,監聽成功的回調函數(選填))
server.listen(8080,function(){ 端口號0-65535 建議0-1023不使用 })
此時瀏覽器就可以執行localhost進行訪問了
自定義模塊
每一個js文件都是一個獨立的模塊,他們都自帶一個 module 是一個對象,
其中 module里面的 exports,是一個對象 這個 module.exports 就是這個文件向外導出的內容,也就是說,只有導出,才能導入
Eg: function fn1(){console.log() }
Module.exports.fn1=fn1
這樣,才能是另一個js文件到入這個文件 同樣也是require(‘./js’)方法
想要學會這個漂亮的煙花嗎?快來跟著學習吧~
<div class="container"></div>
我們只需要一個盒子表示煙花爆炸范圍就可以了
fire是煙花 注意添加絕對定位
<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>
需要用到一個鼠標點擊的位置,一個div選擇器,一個爆炸樣式
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) }
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){ //創建一個煙花 this.ele = this.createFirework(); //xy為鼠標落點 this.x = x ; this.y = y; //maxXy為最大運動范圍 this.maxX = this.box.offsetWidth - this.ele.offsetWidth; this.maxY = this.box.offsetHeight - this.ele.offsetHeight; //初始化結束后 煙花隨機顏色 this.randomColor(this.ele); //煙花升空 this.fireworkUp(this.ele); }, //創造煙花 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") })
全部代碼
<!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); }, //創造煙花 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>
———————————————— 版權聲明:本文為CSDN博主「SpongeBooob」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。 原文鏈接:https://blog.csdn.net/qq_41383900/article/details/105026768
許多人都有這樣一種映像,NodeJS比較快; 但是因為其是單線程,所以它不穩定,有點不安全,不適合處理復雜業務; 它比較適合對并發要求比較高,而且簡單的業務場景。
在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
其實這幾條主要吐嘈了兩點: node.js錯誤處理很扯蛋,node.js的回調也很扯蛋。
事實上NodeJS里程確實有“脆弱”的一面,單線程的某處產生了“未處理的”異常確實會導致整個Node.JS的崩潰退出,來看個例子, 這里有一個node-error.js的文件:
var http = require('http'); var server = http.createServer(function (req, res) { //這里有個錯誤,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/');
啟動服務,并在地址欄測試一下發現 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)
其實Node.JS發展到今天,如果連這個問題都解決不了,那估計早就沒人用了。
我們可以uncaughtException來全局捕獲未捕獲的Error,同時你還可以將此函數的調用棧打印出來,捕獲之后可以有效防止node進程退出,如:
process.on('uncaughtException', function (err) { //打印出錯誤 console.log(err); //打印出錯誤的調用棧方便調試 console.log(err.stack); });
這相當于在node進程內部進行守護, 但這種方法很多人都是不提倡的,說明你還不能完全掌控Node.JS的異常。
我們還可以在回調前加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); };
這種方案的好處是,可以將錯誤和調用棧直接輸出到當前發生的網頁上。
標準的HTTP響應處理會經歷一系列的Middleware(HttpModule),最終到達Handler,如下圖所示:
這 些Middleware和Handler在NodeJS中都有一個特點,他們都是回調函數,而回調函數中是唯一會讓Node在運行時崩潰的地方。根據這個 特點,我們只需要在框架中集成一處try/catch就可以相對完美地解決異常問題,而且不會影響其它用戶的請求request。
事實上現在的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(); }
那么不在回調中產生的錯誤怎么辦?不必擔心,其實這樣的node程序根本就起不起來。
此外node自帶的 cluster 也有一定的容錯能力,它跟nginx的worker很類似,但消耗資源(內存)略大,編程也不是很方便,OurJS并沒有采用此種設計。
現 在已經基本上解決了Node.JS因異常而崩潰的問題,不過任何平臺都不是100%可靠的,還有一些錯誤是從Node底層拋出的,有些異常 try/catch和uncaughtException都無法捕獲。之前在運行ourjs的時侯,會偶爾碰到底層拋出的文件流讀取異常,這就是一個底層 libuv的BUG,node.js在0.10.21中進行了修復。
面對這種情況,我們就應該為nodejs應用添加守護進程,讓NodeJS遭遇異常崩潰以后能馬上復活。
另外,還應該把這些產生的異常記錄到日志中,并讓異常永遠不再發生。
node-forever 提供了守護的功能和LOG日志記錄功能。
安裝非常容易
[sudo] npm install forever
使用也很簡單
$ forever start simple-server.js $ forever list [0] simple-server.js [ 24597, 24596 ]
還可以看日志
forever -o out.log -e err.log my-script.js
使用node來守護的話資源開銷可能會有點大,而且也會略顯復雜,OurJS直接在開機啟動腳本來進程線程守護。
如在debian中放置的 ourjs 開機啟動文件: /etc/init.d/ourjs
這個文件非常簡單,只有啟動的選項,守護的核心功能是由一個無限循環 while true; 來實現的,為了防止過于密集的錯誤阻塞進程,每次錯誤后間隔1秒重啟服務
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
錯誤日志記錄也非常簡單,直接將此進程控制臺當中的錯誤輸出到error.log文件即可: 2>> $WEB_DIR/error.log 這一行, 2 代表 Error。
藍藍設計的小編 http://www.syprn.cn