<address id="ttjl9"></address>

      <noframes id="ttjl9"><address id="ttjl9"><nobr id="ttjl9"></nobr></address>
      <form id="ttjl9"></form>
        <em id="ttjl9"><span id="ttjl9"></span></em>
        <address id="ttjl9"></address>

          <noframes id="ttjl9"><form id="ttjl9"></form>

          首頁

          如何做好Banner設計?

          濤濤

          「空間陳列法」是說先構建一個空間,然后將主體元素用合適的形式陳列出來。

          這是隨著手機興起而真正流行起來的一招,因為PC時代都是寬大的橫屏設計,適合展現視野開闊的大場面,像大漠、海邊等等,而「空間陳列」作為小場景,在PC端就顯得不大氣,因此使用較少;而手機端卻剛好相反,瘦長豎屏就適合表現長焦特寫的小場景,像微距下的花鳥魚蟲等等,這時「空間陳列」就用的恰到好處。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          如圖所示,同樣的產品展示圖,在PC端就顯得單薄,版面空缺,整體不飽滿;而在手機端則用的剛剛好,確實這種長焦特寫、微距放大的陳列小場景就是手機屏的最愛,所以在手機時代,空間陳列圖才會呈現井噴式增長。

          其實合成、三維和攝影都可以實現「空間陳列」,但本書還是以合成為主,而合成的難點就在于如何將產品和空間進行自然融合,不能有違和感。

          若想合的天衣無縫,第一步就是要做到「透視準確」,而透視作為構圖中的重要知識點,可以說是無處不在,在前面的章節里也多次提及。我們只有掌握透視的變化規律,才能準確表現出元素的空間關系,如果透視不對,那空間將會失真,下面就來詳細講講這個理性知識點——透視。

          焦點透視

          日常生活中,當我們看周圍事物時,會有遠近、高低、長短、寬窄等不同,這是由于距離、方位等差異在視覺中呈現的不同反映,這種現象就是透視。透視學的出現可以幫我們非常科學的表現各種空間感和立體感,它廣泛用于繪畫、建筑、環藝、設計等諸多領域,而常見的透視共3類:空氣透視、散點透視和焦點透視,這3類的側重點各有不同。

          空氣透視又稱「色彩透視」,由于空氣介質的存在(雨、雪、霧、煙等),使人們看到近處景物比遠處的輪廓更清晰、色彩更飽滿的視覺現象,例如下方海報中的「煙雨蒙蒙」,這種近實遠虛感就是典型的空氣透視,隨著鏡頭拉遠,山川也變得越來越模糊。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          散點透視則是中國畫特有的一種透視類型,例如下方的《清明上河圖》就是這類透視的代表作,在這五米長的畫卷中,很難找出畫家的具體觀察位置,好似在移動中作畫,每到一處畫一部分,最后拼接起來,這種視點不斷移動的畫法就是散點透視,散點透視適合表現景色的波瀾壯闊,重在寫意,體現一種氣勢和意境。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          而焦點透視才是本文重點,它是透視學中的核心理論,也是西方繪畫所遵循的透視原則,最早研究透視就是從這里開始,如果散點透視是「寫意」,那焦點透視則「寫實」,一切都以客觀還原為準。

          例如名畫《最后的晚餐》,所有視線都匯聚一點(稱為滅點),營造出一種立體空間感,這些就像自己身處畫面中央所看到的逼真景象。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          而我們在設計中常說的透視也都指「焦點透視」,這是我們需要掌握的重中之重。記得高中學習素描時,老師就說畫靜物要「近大遠小」,其實就是對焦點透視最為形象的描述,例如草地上的奶牛,離我們越近就越大,越遠則越小,正是這種近大遠小的透視變化才使場景有了空間和層次。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          觀察視角

          在介紹焦點透視前,我們先說說透視中一個很重要的影響因素——觀察視角。視角即指人眼(稱為視點)在觀察事物時視線之間所形成的角度。

          如下圖所示,其實就是人眼觀看角度的變化,常見有3種:當我們平視前方時就是「平視視角」;仰頭看時則是「仰視視角」;低頭看時便是「俯視視角」。

          其中平視時人眼和物體形成的假想連線稱為「水平視線」,這是判斷視角高低的參考線。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          當我們將產品放入空間時,就要根據陳列形式選擇合適視角,從下方的示意圖中能看到,三種視角給人的感受都不相同:

          • 平視有種方方正正感,給人一種非常自然的觀察感受,雖然中規中矩但視覺舒服;
          • 仰視則能體現產品的高大和氣勢,用來烘托價值感;
          • 而俯視最接近我們日??醋烂嫘∥锲返囊暯?,很真實也很親切,同時還凸顯了產品的立體感。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          而上圖的仰視和俯視都屬于小視角,即產品視線和水平視線的夾角較小,這是設計中的常見視角,大概就是把頭微微抬起或低下時看到的場景,這時畫面最自然也最舒服。反之若視角過大,即頭抬的很高或壓的很低時,這時產品的形變就很夸張,顯得刻意、不舒服。

          說完3種視角,現在正式講解焦點透視,一般根據物體滅點的數量不同,焦點透視又分3種:平行透視(一點透視)、成角透視(兩點透視)和斜角透視(三點透視),它們都有各自的透視效果和適用范圍,但若鋪開講會很復雜,因此下面就結合「空間陳列」進行介紹。

          1. 平行透視

          用立方體簡單說明,就是有一面與畫面平行,這時物體的厚度邊線若向內延伸,最后都會匯聚到1個點上,因此又稱「一點透視」。這是最簡單也最易掌握的一種透視形式,其中匯聚點稱為「滅點」,而滅點所在的那條線則是「視平線」,即與人眼等高的一條水平線。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          再來看看平行透視在生活中的場景呈現,如圖所示,將各種景物進行前后連線并延伸,最后都是匯到一點才消失。平行透視適合表現場景縱深,給人一種正式感和平和感。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          電商中的產品展示也一樣,例如下方示意圖中,不管哪種視角,產品和立方體都是正對觀眾,讓人覺得擺放角度正正好。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          總體來說平行透視只有1個滅點,變化并不多,視覺表現單一,沒有太多的空間變化,基本就是從正面來表現整個場景,因此上手簡單,只要確保前后連線都匯聚一點即可,這樣畫面各元素也會顯得整齊。但有時這種正視會讓畫面缺少層次感,顯得很平,此時可嘗試俯視視角或者強化背景的空間縱深。

          下面展示平行透視在3種視角下的應用案例,注意觀察不同視角下的產品呈現和透視變化,雖然微妙,但每種視角確實給人不一樣的視覺感受。

          平行平視

          當畫面為平行透視和平視視角時,這時的觀察位置很正。如下圖所示,空間和產品都顯得有些平整,雖然場景的立體感較弱,但視覺舒服協調,表現起來也相對簡單。注意平視的「視平線」基本位于主體元素的中心處,即是說人眼此時正對前方物體的中心,這樣才會有平視效果。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          平行仰視

          當畫面為仰視時,一般視角都不會太大,微微仰視即可,這樣視覺才舒服。如下圖所示,其實和平視比起來,小角度仰視的透視變化并不明顯,沒有夸張形變,但依然能體現空間和產品的高大。此時「視平線」位于主體中心靠下的位置,這時人眼明顯是從下往上看。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          平行俯視

          如果覺得畫面的層次感和立體感不夠,那就嘗試下俯視視角,如下圖所示,由于俯視時我們能同時看到物體的頂面和正面,這樣就能表現物體的厚度,立體感也明顯增強。而畫面的「視平線」則位于主體中心靠上的位置,這時人眼就是從上往下看,但同樣屬于小角度俯視。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          2. 正俯視

          平行透視的俯視還有一種特殊情形——正俯視,即視角為90°的俯視,這時我們是從物體的正上方低頭往下看,如下圖所示,當產品平放桌面時,正俯視能清晰看到產品的全貌。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          在空間陳列中這是一種常見視角,上手簡單,展現清晰。例如下方案例中,俯視下的產品擺放非常靈活,根據構圖需求可以工整 ① 也可以隨意 ② ,并且產品多以正面展現為主,整體直觀、舒服。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          以上都是產品的陳列案例,其實正俯視有時也會用于場景呈現中,如下圖所示,視點位于場景的正上方,有點類似無人機的俯瞰拍攝,這種看似「刁鉆」的視角能給畫面帶來獨特的戲劇效果,令人印象深刻。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          空間平行透視

          除了3種視角,這里還要介紹一種平行透視的常見形式——空間平行透視,這種形式即畫面的正前方有個類似方盒的縱深空間,而人物或產品就放置在空間里。

          如圖所示,該形式也有視角的3種變化,但為了確保視覺的自然舒服,仰視和俯視也都是小角度的上下擺動,所以產品的透視變化并不明顯,場景呈現也沒有很夸張。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          為何要將該形式單獨列出?因為它非常適合手機端的豎屏構圖。

          手機不像PC,無法通過寬屏來表現視野開闊的大場面,手機屏更適合長焦特寫的小場景,但這樣有時就會顯得左右擁擠不透氣,這時「空間平行透視」就剛好取長補短,通過「深度」刻畫將狹窄空間的縱深感體現出來,最終使觀者視線在前后維度上有了延伸和舒展。

          該形式正是利用平行透視的縱深性,才緩解了手機屏的擁擠感。下面分視角列舉案例,要注意不管平視、仰視還是俯視,空間里的所有元素最后都要匯聚一點,這樣透視才合理,縱深效果也最好。

          3. 空間平視

          3種視角中,空間平視最常見,因為這種方方正正的空間展示最適合手機的豎屏構圖,看著最舒服,上手也簡單,易于搭建。在平視下,由于沒有視角的高低變化,空間基本位于人眼的正前方,無任何偏移,擺放角度非常正,構圖給人一種穩定感,元素也沒有夸張形變,組合方便,真實自然。

          對于「空間平行透視」,「深度」刻畫很關鍵,我們要根據版面構圖選擇合適的深淺。例如下方案例中:① ② 的深度淺,空間相對封閉,適合展現小空間,給人溫馨感和趣味性;而 ③ 的深度深,空間開闊,適合展現大空間,這樣能讓視線更舒展,畫面更透氣。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          4. 空間仰視

          在仰視下,我們是抬頭向上看,這時空間顯得高大,給人強烈的氣勢感。如下圖所示,視平線貼近地面,像是我們蹲著向上看,這種仰望視角,建筑和人物都很高大,再加上強烈的縱深感,雖然空間左右依然狹窄,但上下和前后維度卻變的非常開闊,畫面通透。

          空間仰視能渲染氛圍,提升場景的戲劇效果,突出視覺沖擊力,但要注意仰視視角不能太大,否則夸張的仰視效果反而給人一種壓抑感。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          5. 空間俯視

          在俯視下,視平線處于空間中心的上方,類似我們站在高處往下看,如下圖所示,這時空間顯得立體,遠近的各類元素也能得到清晰展現,層次分明。

          俯視適合展現空間的全局觀,也讓各物體有著豐富的體積感。除非有特殊的構圖需求,不然和仰視一樣,俯視視角不能過大,否則俯視就變成俯瞰,會產生遙遠的距離感,空間也壓縮的厲害,進而導致形變和失真。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          成角透視

          還是拿立方體舉例,就是物體與畫面形成一定夾角,這時物體的所有邊線分別向各自方向進行延伸,最后會在視平線上形成一左一右2個滅點,因此又稱「兩點透視」。這類透視最接近我們日常的觀察角度,即是說大部分時候,我們看到的物體都屬于成角透視。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          雖然成角透視只比平行透視多了1個滅點,但2個滅點的位置卻很靈活,這樣空間的透視變化也更加豐富。

          例如下方是我們經??吹降木跋螅m然都是典型的成角透視,但 ① 是2個滅點都在畫面外,這時建筑給人的感覺結構平穩,立體感強,側重寫實;而 ② 則是1個滅點在畫面內,另1個在畫面外,這時空間右側的透視形變較大,產生縱深感,整個場景更有張力和沖擊。

          其實還有第3種情形是2個滅點都在畫面內,但由于空間會產生夸張形變和失真,因此總體少見,不再舉例說明。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          再看看成角透視下的產品展示效果,如下圖所示,不同于單面展示為主的平行透視,成角透視則以展示物體的兩面為主,這樣立體感更強,構圖也更穩定。注意在成角透視中,畫面所有的豎向邊線都是平行,不會產生滅點。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          相比平行透視,成角透視在表現上更復雜一些,一般都以2滅點在畫面外的情形為主,這時透視最舒服。注意要想畫面只產生2個滅點,那當中的所有元素都需排列整齊,這也是成角透視的常用做法,此時畫面會顯得整齊統一。下面列舉3種視角下的電商案例,其中以仰視和俯視最為常見。

          1. 成角平視

          平視下的成角透視相對少見,因為使用成角透視就是為了凸顯物體的立體感,但平視由于視角很正,恰恰就會顯得立體感較弱,這時2種效果會有矛盾,影響場景的協調性。例如下方的2個案例中,產品看著就有些平整,和方形盒子以及立方體稍顯沖突,但整體視覺真實平和,沒有形變。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          2. 成角仰視

          仰視下的成角透視就顯得剛剛好,如下圖所示,所有元素均用2個立面展示,加上透視形變,這時空間的立體感強,產品和立方體也都有明顯的體積感,視覺平穩、飽滿,而且還能體現產品形象的高大,凸顯價值感。

          注意2個案例中,視平線上都只有2個滅點,這是因為產品和立方體的排列都很整齊,反之若無序排列,就會產生多個滅點,這樣畫面會顯凌亂,視覺不舒服,所以在表現成角透視時,盡量確保所有元素都能整齊排列。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          3. 成角俯視

          在成角透視中,俯視視角最常見。因為該視角下的物體可以展現3個面,能進一步強化元素的立體感和方位感,如圖所示,物體的空間關系明顯,層次分明,構圖也平穩。

          一般成角俯視適合小場景陳列(若是大場景則垂直方向會發生嚴重形變,這就是后面要講的「斜角透視」),剛好這是手機屏的擅長,小空間配上小角度俯視會給人一種親切感,類似長焦鏡頭的特寫畫面,很好的拉近了觀者的心理距離,因此屬于手機端的常用構圖。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          空間成角透視

          成角透視的優勢在于畫面立體、穩定和寫實,這些優勢剛好適合空間的立體呈現,如下圖所示,成角透視即可用于室內塑造 ① 也可用于外形搭建 ② ,類似我們站在空間側面看整體,此時空間立體、飽滿,結構有張力。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          但由于成角透視都是在畫面兩端形成滅點,所以該透視下的空間更適合橫構圖,但手機屏卻是豎構圖,對于橫向擁擠的豎長屏,成角透視就會有些施展不開,左右狹窄,無法像橫版那樣開闊的展現空間,也沒有平行透視那樣看著規整,因此使用較少。

          斜角透視

          物體與畫面存在一定夾角,并且在2點透視的基礎上,再加入了高度變化,這樣垂直方向的連線會向上或者向下匯聚,最終畫面形成3個滅點,又稱「三點透視」。相比成角透視,斜角透視其實就是讓本沒有交集的豎線有了交集,這樣垂直方向就有了強烈的匯聚感。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          斜角透視的形變夸張,常用于大型物體的仰視或高處俯瞰,類似廣角拍攝。

          如下圖所示,該透視能表現出建筑或空間的宏大感,并且越宏大透視就越強烈。這時畫面的夸張構圖會顯得觀者渺小,給人一種壓迫感,也讓場景有著極強沖擊力,同時帶來了更加刺激的視覺感受。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          其實只要觀者在場景中顯得很小,這時看到的畫面就會產生斜角透視,例如當我們仰望高樓時,相對高樓而言,渺小的我們就會看到斜角透視。

          但如果產品展示采用斜角透視時,就會有一種強烈的不真實感,因為相對產品來說,我們并不渺小,所以日常是不會看到這樣的場景,這種場景更像是「昆蟲視角」,如圖所示,斜角透視下,雖然畫面不真實,但會有種特別的戲劇效果。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          另外斜角透視沒有平視視角,因為平視物體的豎向邊線依然平行,不會在垂直方向產生第3個滅點,因此仍屬于成角透視。總之只有在大角度仰視或俯視大型物體時,才會看到斜角透視。

          這種形變強烈的夸張透視雖然生活中相對少見,但電商中用的還真不少。還記得我們在第2章講創意方法時提過的「獨特視角」嗎?其中一個方向就是使用斜角透視。

          這種透視可以體現物體的巨大(仰視)或者場景的宏大(俯視),正是這樣一種不真實也不自然的視覺感受,反倒給人一種強烈的氣勢和沖擊,畫面極具張力的構圖往往能脫穎而出,并在第一時間抓人眼球,吸引注意,所以成角透視特別適合大促主題的場景搭建和氛圍營造,下面分視角舉例。

          斜角仰視

          視能讓物體顯得高大,而斜角仰視則讓物體顯得「巨大」。如圖所示,2個案例中的產品都十分「巨大」,通過這樣一種「刁鉆」視角和夸張手法渲染出了產品氣勢,使產品顯得分量十足,同時也提升了視覺沖擊力,整個場景都變的大氣。其中 ① 由于版面有限,有個滅點沒有標示出來。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          斜角俯視

          當我們站在一個很高的地方俯瞰周圍,或者用無人機在高空航拍,這時看到的景象就是斜角俯視。如圖所示,盡管豎構圖并不適合展現遼闊的大場面,但在斜角俯視的幫助下,2個案例依然體現了場景的宏大,視覺沖擊強,這種居高臨下感使人視野開闊。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          平行斜角透視

          斜角透視中還有一種特殊情形——平行斜角透視,就是當空間或產品的一面與畫面平行時,此時不管透視多強烈,畫面也只有2個滅點。

          如圖所示,當立方體的一面正對視點時,側面便從2個主立面減為了1個,這時除了垂直方向的1個滅點外,原本視平線上的2個也就成了1個,雖然只剩2滅點,但本質仍屬于透視強烈的斜角透視。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          既然是與畫面平行,那和平行透視有何區別?

          下面看張空間對比圖,能看到2者的形變差異還是相當大:左圖為平行透視,像是我們在看一個小方盒,親切、自然、真實;而右圖則是平行斜角透視,更像是我們在仰望一個巨大空間的入口,充滿戲劇性,并有壓迫感。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          其實對于手機端,「平行斜角透視」才是斜角透視中的常用形式,因為它的擺放角度很正,這種正面觀察很適合手機端的豎屏構圖,而且前后的縱深刻畫也能緩解版面的左右擁擠,另外畫面縱向的匯聚感還能迅速吸引注意,給人一種巨大沖擊力和強烈氛圍感,其中仰視比俯視更加常見。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          平行斜角仰視

          近幾年這類透視越來越多見,因為它既適合豎構圖,也能提升畫面的形式感和表現力,非常利于促銷主題的氛圍打造。

          如圖所示,整個畫面就像是我們站在宏大場景的正中央仰望時看到的景象,各元素擺放很正,雖有壓迫感,但空間和產品都顯得非常高大,張力十足,能讓用戶牢牢聚焦,同時也產生了強烈沖擊力,更易在用戶腦海中形成記憶。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          平行斜角俯視

          在這類透視下我們會感覺自己擁有高高在上的「上帝視角」,元素擺放同樣很正,視野遼闊,場景宏大。但過大的俯視視角會對場景進行一定壓縮,再加上俯瞰產生的遙遠距離感,這樣就顯得元素有些「小氣」,無法體現仰視下的高大,因此畫面的刺激感沒那么強烈,總體相對少見。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          3. 小結

          以上便是焦點透視的3種類型,我們再來回顧下各自的使用情形,如下圖所示,這是一張3種透視的轉換示意圖,都是仰視視角,旁邊小人則是觀察者的大小示意:

          • 當立方體的一面正對觀察者時,就是「平行透視」,這時除了物體厚度的邊線會匯聚1點,其余邊線均無交集;
          • 而當立方體旋轉一個角度,任何一面都不正對觀察者時,就是「成角透視」,這時橫向邊線會向各自方向匯聚成2點,豎向邊線則無交集;
          • 此時若將立方體變的巨大,大到需要仰望,就是「斜角透視」,這時在2點基礎上,本無交集的豎向邊線將匯成1個新點。

          希望通過這張示意圖能幫大家更好理解什么時候該用哪種透視,總之小場景搭建一般以「平行透視」和「成角透視」為主,而恢弘的大場景則以「斜角透視」為主。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          當然現實里的透視遠不會這么單一,根據物體不同的擺放位置以及不同的觀測距離,很多時候同一畫面也會存在多種透視,例如平行透視和成角透視就經常組合出現。

          電商設計也一樣,例如下方案例中,整個空間是平行透視,而里面的盒子則是成角透視,這時視平線上會有3個滅點,其實若產品的擺放再凌亂些,還會出現更多滅點,但這種無序組合會讓空間塑造變的復雜,看著也不規整,因此并不推薦。

          但要注意不管畫面的透視多復雜,當中的視平線卻只能有1條,并且無論水平方向有多少個滅點,最后也都會落在視平線上。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          除了以上3種透視外,其實還有4點透視(超廣角透視)、5點透視(魚眼透視)等等,但都過于復雜,用的也很少,這里就不做展開。對于電商里的「空間陳列」,3種透視已夠用,它是我們塑造空間的基礎,如果一開始的透視錯了,即便配色、光影做的再出彩那也無用。

          總有人說透視難掌握,其實只要我們在生活中多觀察、多留意身邊物體的透視變化和規律,及時總結,那這種理性還原就不難做到。當然設計終歸還是理性與感性的雙重表達,所以透視雖要遵循,但切忌生搬硬套,視覺協調即可。

          講完了焦點透視,我們知道了空間塑造的基本原則,下面就來說說都有哪些陳列場景。

          陳列場景

          相對PC來說,手機端受屏幕所限,陳列場景其實沒那么復雜,核心是要先構建一個空間,然后讓產品以合適的視角及透視在空間里呈現出來,而這個空間場景則要和主題氛圍、產品氣質都高度匹配。

          一般來說,手機端常用的陳列場景有4類:盒子陳列、臺面陳列、自然陳列以及舞臺陳列,選擇哪類則要看哪個場景對產品的烘托效果最好。

          1. 產品組合

          在介紹每個場景前,我先說說關于產品組合的2原則,因為很多時候在空間擺放的產品數量較多,這時它們的組合形式就變的尤其重要,稍不注意就會顯得畫面雜亂無章,不夠協調,而且凌亂的擺放也會降低產品的品質感,缺少吸引力。關于組合原則,核心有2點:大小合理以及三角構圖。

          大小合理

          如果將多個產品擺一起,則要確保它們之間的相對大小符合現實中的真實差異,現實中尺寸大的產品就要相對大些,而尺寸小的產品就要相對小一點,這樣才會真實并經得起推敲。

          而有些設計師在組合產品時,也不管大小的真實差異,放進版面后就很隨意的放大或縮小,最后出來的組合要不就大小一樣,要不就比例失真,這些都會給用戶一種強烈的不協調感和不真實感。

          下面再看一組對比圖,明顯大小一樣的左圖會有不適感,也缺少層次;而大小合理的右圖則更有美感也更舒服。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          其實大小合理的最終目的是希望整體結構錯落有致,就像右圖一樣,這種有高有低的組合才能體現韻律感和結構美。所以如果可以選擇,那我們就選擇一些尺寸差異較大的產品,盡量避免出現大小差不多的情況。

          當然如果必須陳列大小一樣的產品,那也可以通過透視或者輔助元素來改善,例如空間里的近大遠小、立方體加高都能改變高度一樣的情形。

          三角構圖

          當我們選好不同大小的產品后,就要注意它們的組合形式,千萬不能亂堆一氣,不同的擺放會形成不同結構,而每種結構又會給人不同感受。我們在「圖形分割」中講過「正三角」具有很強的穩定性,因此當產品采用正三角構圖時,會讓人覺得版面平穩、視覺舒服。

          如圖所示,所謂三角構圖,其實就是將尺寸大的產品放中間,而尺寸小的產品放兩邊,這樣不但構圖穩定,而且畫面也有節奏和變化??梢哉f「三角構圖」就是空間陳列里最常用的構圖方式,而本節展示的所有案例中,大部分也都是三角構圖。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          明確了產品的組合原則,知道了如何陳列才舒服,下面就正式講講空間陳列的4類場景。

          2. 場景

          盒子陳列

          「盒子陳列」就是在盒子里面放產品,而盒子多以禮盒為主,使用場景和主題相對單一,基本用于送禮之意的專題頁。創意雖然普通,但卻不易出錯,是種相對安全的表現方式。當然若能在盒里加些小心思,畫面也會很出彩,像我之前看過一個新年Banner,就是禮盒里裝著一個大家庭在吃年夜飯的溫馨場景,這樣的新組合便讓人眼前一亮。

          另外若能提升禮盒刻畫的精致度,那畫面也會有不錯的設計感。而盒子外形也不只有方形,常用的還有圓形和異形。觀察視角則以小角度俯視居多,因為這個視角最接近我們日??炊Y盒的真實情形,盒內產品在俯視下能看的一清二楚,展現也立體。

          盒子陳列的難點在于當盒內要擺放很多產品時,如何能讓產品真實、自然的呈現,這需要我們既注意擺放的合理性,也能準確表現透視,還要刻畫出產品的明暗變化,總之只有把握好產品的空間感、立體感以及光影感,畫面才會舒服協調。

          方形盒是最常用的盒子類型,畢竟也是生活中最常用的禮盒外形,結構感強而且易表現。如下圖所示,禮盒都是成角透視,且左右滅點都在畫面之外,這樣結構最穩定,立體感也強。注意盒里的產品呈現,特別是俯視視角下,產品越多越要注意它們是否協調統一,透視、光影等細節一個都不能少,把控不到位就會顯得凌亂,畫面別扭。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          圓形盒比較少見,因為和立方體比起來,圓柱體的透視沒那么強烈,結構感偏弱,但圓潤的外形能使畫面變的柔和,給人一種親和力和溫馨感,如圖所示,由于圓形盒沒有明顯塊面,所以不管透視還是光影,刻畫起來都相對簡單。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          異形盒是指外形為不規則形狀的盒子,總體也很少見,但易出彩。形狀用的好便能打破「盒子陳列」的常規感,使畫面變的新穎有創意。

          例如下方案例中,不管是心形、貓頭輪廓還是圣誕樹,都能成為畫面焦點并引人注意。另外盒子呈現均用了「正俯視」視角,其實除了小角度俯視外,這種視角也很常見,因為該視角下的產品陳列清晰完整,盒子外形也能直觀顯示,的展現了其外形的特別之處。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          臺面陳列

          在空間陳列的4類場景中,「臺面陳列」用的最多,適用范圍也最廣,可以說電商中的大部分主題和產品都能使用,算是一種真正白搭的表現方式。

          「臺面陳列」就是在空間里搭建一個「臺面」,然后在上面放置相關產品。由于該手法還是以放大特寫的小場景為主,元素形變不能太大,因此畫面常用平行透視和成角透視,視角則很靈活,跟著構圖走,3種(平視、俯視、仰視)都有?!概_面陳列」上手簡單,場景多變,其中的關鍵元素——臺面需要根據主題、場景進行靈活變化,常見有2類:桌面和幾何體。

          桌面很好理解,就是桌子頂部的陳列面,所有產品都放置其上。由于桌子是家里常見家具之一,因此桌面陳列往往能傳遞一種「家」的溫暖和溫馨。

          可能有人覺得「桌面」形式有些單一,其實遠沒那么簡單,我們不要固化思維,要能靈活變化桌子的樣式和裝飾,例如方桌還是圓桌?木桌還是大理石桌?光面還是鋪桌布?這些都是可變量,再加上視角和周圍環境的變化,總之形式可以很豐富。

          如圖所示,桌面陳列尤其適合各類美食的組合呈現,這時整個場景貼近生活,頗有帶入感。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          就是將各種幾何形體作為陳列產品的臺面,幾何體相對抽象,表現場景更多元,因此比具象「桌面」更加常用。

          如圖所示,幾何體一般都是組合出現,特別適合多產品陳列,簡約大方,能烘托出產品的品質感。同時通過高高低低的大小排列也能表現出畫面的結構美以及層次感,總之是一種 「上手簡單、易出效果」的表現方式。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          常用幾何體有「立方體」和「圓柱體」,它們適合陳列,高度調節方便,使用靈活。

          如下圖所示,整體表現并不復雜,就是將各種產品放在幾何體上。但作為畫面的核心元素,這時幾何體的形態、排列、視角和透視就變的非常重要,我們要根據創意需求和產品氣質選擇最合適的展現方式,而這些展現本身就有不錯的形式感。

          幾何體陳列既能營造空間關系和簡約氣質,也能讓用戶聚焦產品本身,因為它的外形簡單,不搶產品,不像一些復雜元素或場景,雖然視覺豐富但最后卻讓產品淹沒其中,這樣就本末倒置。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          自然陳列

          「自然陳列」需要先創建一個合適的自然環境,然后再將產品以合適方式融入其中。相對其他場景,自然環境顯然復雜一些,呈現手法常以「合成」和「插畫」為主。因為產品都是實物拍攝,為了風格統一,自然環境會偏向寫實風格,這樣2者結合才協調。

          從下方案例能看到,「自然陳列」常用于季節感受或者產地溯源等主題,畫面通過「自然場景」營造出天然健康的綠色氛圍。而場景中的元素繁多,呈現復雜,這就需要我們具備優秀的整合能力。

          對于「自然」塑造,視角以平視和小角度俯視居多,但畫面由于沒有太多的幾何型物體,所以透視沒那么嚴謹,核心是注意近、中、遠景的層次區分,還有光影的合理添加。如果這些把控不到位,就很可能出現場景雜亂、缺少層次、沒有帶入感的粗糙畫面,最終淪為各種素材的亂堆一氣。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          以上列舉的都是以花草樹木為主的自然環境,確實綠色場景在「自然陳列」中用的也是最多,但除了綠色場景外,有時也會用到其他環境。如下圖所示,像海底 ① 、沙灘 ② 、海面 ③ 、冰山 ④ 也是適合陳列的自然場景,特別是夏季主題會經常用到。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          舞臺陳列

          最后一類「舞臺陳列」常用于大促主題的氣氛營造,這類場景不挑類目,任何產品放在「舞臺」上,燈光一打,色彩再斑斕些,都能營造出熱鬧的促銷氛圍。

          如圖所示,舞臺外形以圓形居多,因為圓形的透視感較弱,構圖靈活,而且也符合大家對舞臺的第一印象。舞臺視角則很靈活,3種均很常見,核心是和產品視角保持一致。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          關于「舞臺」塑造,還有2處需要注意的地方:

          舞臺外形除了最常用的「圓形」外,還有半圓形、方形和六邊形等等;舞臺造型也可以很豐富,并不局限于常規的表演舞臺,各種造型都可嘗試,例如上方左四圖就是現代感十足的三維舞臺,總之我們要根據創意和風格塑造相匹配的陳列舞臺。

          另外就是燈光運用,可以說這是「舞臺陳列」和其他場景的最大區別,但燈光也不是越多越好,太多反而顯得眼花繚亂,其實能渲染出絢爛氣氛即可。而有光就有影,在燈光照射下,產品一定要有準確的光影呼應,這樣才不會顯得突兀。例如上方案例中,仔細觀察燈光下的產品呈現,能看到產品表面都產生了被燈光照射后的色彩、明暗等變化,這些細節刻畫才讓畫面更真實,融合更自然。

          3. 實戰案例

          本次案例會用膠原蛋白口服液作主體元素,然后用這4類場景(盒子、臺面、自然、舞臺)來設計4張不同視角、不同風格的Banner,讓大家看看在不同場景下,如何將產品融入其中。先展示案例會用到的3種產品視角, 下方案例會根據不同的場景視角選擇對應素材。

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          如何做好Banner設計?先掌握這個高手都會的空間陳列法!

          總結

          「空間陳列法」的內容量挺大,主要分成了「焦點透視」和「陳列場景」2大部分來介紹,其中焦點透視是立體「空間」的塑造基礎;而陳列場景則是產品「陳列」的具體環境。

          常用場景有4類:盒子陳列、臺面陳列、自然陳列和舞臺陳列,每種陳列都有各自適用的主題和氛圍:「盒子」常用于溫馨的送禮主題;「臺面」則能根據不同主題靈活應變,屬于百搭場景;而「自然」則適合季節或者溯源主題,體現天然清新感;最后的「舞臺」則用于氛圍濃烈的大促主題。不管哪種場景,都要確保產品和空間的視角、透視相一致,這樣場景才會真實協調。另外多產品陳列時,還要注意它們之間的大小比例以及擺放結構,其中三角結構最常用??傊谑謾C時代,「空間陳列」是一種真正適合小屏豎構圖的表現方式。

          文章來源:優設    作者:賢輩

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務

          手機appUI界面設計賞析(四)

          前端達人

          與傳統PC桌面不同,手機屏幕的尺寸更加小巧操作,方式也已觸控為主,APP界面設計不但要保證APP功能的完整性和合理性,又要保證APP的功能性和實用性,在保證其擁有流暢的操作感受的同時,滿足人們的審美需求。

          接下來為大家介紹幾款手機appui界面設計

          微信圖片_20200721175459.jpg

          微信圖片_20200721175502.jpg

          微信圖片_20200721175510.jpg

          微信圖片_20200721175514.jpg

          微信圖片_20200721175540.jpg

          微信圖片_20200721175544.jpg

          微信圖片_20200721175548.jpg

          微信圖片_20200721175624.png

          微信圖片_20200721175631.jpg

          微信圖片_20200721175635.jpg

          微信圖片_20200721175639.jpg

          微信圖片_20200727222354.png

          微信圖片_20200727222406.jpg

          微信圖片_20200727222412.png

          微信圖片_20200727222421.jpg

          微信圖片_20200727222431.jpg


             --手機appUI設計--


          (以上圖片均來源于網絡)



            藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服



            更多精彩文章:

                 手機appUI界面設計賞析(一)

                 手機appUI界面設計賞析(二)

                 手機appUI界面設計賞析(三)



          巧用偽元素before和after制作絢麗效果

          seo達人

          CSS :before 選擇器

          定義和說明

          :before 選擇器向選定的元素前插入內容。

          使用content 屬性來指定要插入的內容。


          CSS :after 選擇器

          定義和說明

          :after 選擇器向選定的元素之后插入內容。

          使用content 屬性來指定要插入的內容。


          這兩個偽元素會在真正頁面元素之前和之后插入一個額外的元素,從技術角度上講,它們與下面的HTML標記是等效的。


          1.偽類光圈



          <div class="hover-circle">CSS</div>

          .hover-circle {

           width: 100%;

           display: flex;

           align-items: center;

           justify-content: center;

           height: 100%;

           font-size: 3rem;

           letter-spacing: 0.3rem;

           font-weight: bold;

           position: relative;

           cursor: pointer;

           color: #666;

          }


          .hover-circle::before {

           width: 8.5rem;

           height: 8.5rem;

           border: 3px solid pink;

           content: "";

           border-radius: 50%;

           position: absolute;

           opacity: 0;

          }


          .hover-circle::after {

           width: 7.2rem;

           height: 7.2rem;

           border: 6px solid pink;

           content: "";

           border-radius: 50%;

           position: absolute;

           opacity: 0;

          }


          .hover-circle:hover::before,

          .hover-circle:hover::after {

           animation-duration: 0.8s;

           animation-delay: 0.2s;

           animation: circle 0.8s;

          }


          @keyframes circle {

           0% {

             opacity: 0;

             scale: 1;

           }


           25% {

             opacity: 0.25;

           }


           50% {

             opacity: 0.5;

             scale: 1.03;

           }


           75% {

             opacity: 0.75;

           }


           100% {

             opacity: 1;

             scale: 1.03;

           }

          }

          2.偽類括號效果



          <div class="hover-text">CSS</div>

          .hover-text {

           width: 100%;

           display: flex;

           align-items: center;

           justify-content: center;

           height: 100%;

           font-size: 3rem;

           letter-spacing: 0.3rem;

           font-weight: bold;

           position: relative;

           cursor: pointer;

           color: #666;

          }


          .hover-text::before {

           content: "[";

           position: absolute;

           left: 0.8rem;

           opacity: 0;

           color: #999;

          }


          .hover-text::after {

           content: "]";

           position: absolute;

           right: 0.8rem;

           opacity: 0;

           color: #999;

          }


          .hover-text:hover::before {

           animation-duration: 0.8s;

           animation-delay: 0.2s;

           animation: hovertext1 0.8s;

          }


          .hover-text:hover::after {

           animation-duration: 0.8s;

           animation-delay: 0.2s;

           animation: hovertext2 0.8s;

          }


          @keyframes hovertext1 {

           0% {

             opacity: 0;

             left: 0.8rem;

           }


           100% {

             opacity: 1;

             left: 0.5rem;

           }

          }


          @keyframes hovertext2 {

           0% {

             opacity: 0;

             right: 0.8rem;

           }


           100% {

             opacity: 1;

             right: 0.5rem;

           }

          }

          3.炫酷絲帶效果

          雙邊絲帶



          <div class="tc">

             <div class="title1"><span>距離結束還有10天</span></div>

          </div>

          .title1 {

           position: relative;

           display: inline-block;

          }


          .title1 span {

           position: relative;

           z-index: 2;

           display: inline-block;

           padding: 0 15px;

           height: 32px;

           line-height: 32px;

           background-color: #dc5947;

           color: #fff;

           font-size: 16px;

           box-shadow: 0 10px 6px -9px rgba(0, 0, 0, 0.6);

          }


          .title1 span::before,

          .title1 span::after {

           position: absolute;

           bottom: -6px;

           border-width: 3px 5px;

           border-style: solid;

           content: "";

          }


          .title1 span::before {

           left: 0;

           border-color: #972f22 #972f22 transparent transparent;

          }


          .title1 span::after {

           right: 0;

           border-color: #972f22 transparent transparent #972f22;

          }


          .title1::before,

          .title1::after {

           position: absolute;

           top: 6px;

           content: "";

           border-style: solid;

           border-color: #dc5947;

          }


          .title1::before {

           left: -32px;

           border-width: 16px 26px 16px 16px;

           border-left-color: transparent;

          }


          .title1::after {

           right: -32px;

           border-width: 16px 16px 16px 26px;

           border-right-color: transparent;

          }

          右邊絲帶



          <span class="title2">距離結束還有10天</span>

          .title2 {

           position: relative;

           display: inline-block;

           padding: 0 15px;

           height: 32px;

           line-height: 32px;

           background-color: #dc5947;

           color: #fff;

           font-size: 16px;

          }


          .title2::before {

           position: absolute;

           top: -4px;

           left: 0;

           border-width: 2px 4px;

           border-style: solid;

           border-color: transparent #972f22 #972f22 transparent;

           content: "";

          }


          .title2::after {

           position: absolute;

           top: 0;

           right: -8px;

           border-width: 16px 8px 16px 0;

           border-style: solid;

           border-color: #dc5947 transparent #dc5947 #dc5947;

           content: "";

          }

          箭頭絲帶



          <span class="title3">距離結束還有10天</span>

          .title3 {

           position: relative;

           display: inline-block;

           margin-right: 16px;

           padding: 0 10px;

           height: 32px;

           line-height: 32px;

           background-color: #dc5947;

           color: #fff;

           font-size: 16px;

          }


          .title3::before {

           position: absolute;

           top: 0;

           left: -16px;

           border-width: 16px 16px 16px 0;

           border-style: solid;

           border-color: transparent #dc5947 transparent transparent;

           content: "";

          }


          .title3::after {

           position: absolute;

           top: 0;

           right: -16px;

           border-width: 16px 16px 16px 0;

           border-style: solid;

           border-color: #dc5947 transparent #dc5947 #dc5947;

           content: "";

          }

          多個箭頭絲帶



          <div class="mt30 pl16">

             <span class="title3">距離結束還有10天</span>

             <span class="title3 ml5">距離結束還有10天</span>

             <span class="title3 ml5">距離結束還有10天</span>

          </div>

          .title4 {

           width: 200px;

           height: 140px;

           position: absolute;

           top: -8px;

           left: -8px;

           overflow: hidden;

          }


          .title4::before {

           position: absolute;

           left: 124px;

           border-radius: 8px 8px 0 0;

           width: 16px;

           height: 8px;

           background-color: #972f22;

           content: "";

          }


          .title4::after {

           position: absolute;

           left: 0;

           top: 124px;

           border-radius: 0 8px 8px 0;

           width: 8px;

           height: 16px;

           background-color: #972f22;

           content: "";

          }


          .title4 span {

           display: inline-block;

           text-align: center;

           width: 200px;

           height: 40px;

           line-height: 40px;

           position: absolute;

           top: 30px;

           left: -50px;

           z-index: 2;

           overflow: hidden;

           -ms-transform: rotate(-45deg);

           -moz-transform: rotate(-45deg);

           -webkit-transform: rotate(-45deg);

           -o-transform: rotate(-45deg);

           transform: rotate(-45deg);

           border: 1px dashed #fff;

           box-shadow: 0 0 0 3px #dc5947, 0 14px 7px -9px rgba(0, 0, 0, 0.6);

           background-color: #dc5947;

           color: #fff;

          }

          懸掛標簽



          <div class="pr mt30" style="background-color: #eee; height: 200px;">

             <div class="title4"><span>企業熱門動態</span></div>

             <div class="title5"><span>企業熱門動態</span></div>

          </div>

          .title5 {

           width: 140px;

           height: 200px;

           position: absolute;

           top: -8px;

           right: -8px;

           overflow: hidden;

          }


          .title5::before {

           position: absolute;

           right: 124px;

           border-radius: 8px 8px 0 0;

           width: 16px;

           height: 8px;

           background-color: #972f22;

           content: "";

          }


          .title5::after {

           position: absolute;

           right: 0;

           top: 124px;

           border-radius: 0 8px 8px 0;

           width: 8px;

           height: 16px;

           background-color: #972f22;

           content: "";

          }


          .title5 span {

           display: inline-block;

           text-align: center;

           width: 200px;

           height: 40px;

           line-height: 40px;

           position: absolute;

           top: 30px;

           right: -50px;

           z-index: 2;

           overflow: hidden;

           -ms-transform: rotate(45deg);

           -moz-transform: rotate(45deg);

           -webkit-transform: rotate(45deg);

           -o-transform: rotate(45deg);

           transform: rotate(45deg);

           border: 1px dashed #fff;

           box-shadow: 0 0 0 3px #dc5947, 0 14px 7px -9px rgba(0, 0, 0, 0.6);

           background-color: #dc5947;

           color: #fff;

          }

          4.幾何圖形

          三角形



          <div class="triangle"></div>

          .triangle {

           width: 0;

           height: 0;

           margin: 50px auto;

           border-bottom: 100px solid #dc5947;

           border-left: 50px solid transparent;

           border-right: 50px solid transparent;

           cursor: pointer;

           transform: scale(1.2);

           transition: 0.5s;

          }

          五角星



          <div class="pentagram"></div>

          .pentagram {

           width: 0;

           height: 0;

           margin: 100px auto;

           position: relative;

           border-bottom: 70px solid #dc5947;

           border-left: 100px solid transparent;

           border-right: 100px solid transparent;

           -webkit-transform: rotate(35deg);

           -moz-transform: rotate(35deg);

           -ms-transform: rotate(35deg);

           -o-transform: rotate(35deg);

           transform: rotate(35deg);

           -webkit-transform: scale(1), rotate(35deg);

           -moz-transform: scale(1), rotate(35deg);

           -ms-transform: scale(1), rotate(35deg);

           -o-transform: scale(1), rotate(35deg);

           transform: scale(1), rotate(35deg);

          }


          .pentagram::after {

           content: "";

           width: 0;

           height: 0;

           border-bottom: 70px solid #dc5947;

           border-left: 100px solid transparent;

           border-right: 100px solid transparent;

           -webkit-transform: rotate(-70deg);

           -moz-transform: rotate(-70deg);

           -ms-transform: rotate(-70deg);

           -o-transform: rotate(-70deg);

           transform: rotate(-70deg);

           position: absolute;

           top: 0px;

           left: -100px;

          }


          .pentagram::before {

           content: "";

           width: 0;

           height: 0;

           border-bottom: 80px solid #dc5947;

           border-left: 30px solid transparent;

           border-right: 30px solid transparent;

           -webkit-transform: rotate(-35deg);

           -moz-transform: rotate(-35deg);

           -ms-transform: rotate(-35deg);

           -o-transform: rotate(-35deg);

           transform: rotate(-35deg);

           position: absolute;

           top: -45px;

           left: -60px;

          }

          5.水滴



          <div class="drop"></div>

          .drop::after {

           content: "";

           position: absolute;

           width: 30px;

           height: 20px;

           border-radius: 50%;

           background-color: #ace3ff;

           margin: 100px auto;

           top: -50px;

           left: 25px;

           box-shadow: 5px 12px 4px #ace3ff, -5px 11px 4px #ace3ff, 0px 14px 4px #4d576e;

           -webkit-transform: rotate(35deg);

          }


          .drop::before {

           content: "";

           position: absolute;

           width: 0px;

           height: 0px;

           border-style: solid;

           border-width: 0 40px 50px 40px;

           border-color: transparent transparent #ace3ff transparent;

           top: -30px;

           left: 10px;

          }


          .drop {

           width: 100px;

           height: 100px;

           border-radius: 50%;

           background-color: #ace3ff;

           position: relative;

           margin: 100px auto;

           box-shadow: 0px 6px 0 #3f475a;

          }

          6 絢麗流動邊框





          <div class="box-line1"></div>

          .box-line2,

          .box-line2::before,

          .box-line2::after {

           position: absolute;

           top: 0;

           bottom: 0;

           left: 0;

           right: 0;

          }


          .box-line2 {

           width: 200px;

           height: 200px;

           margin: auto;

           color: #69ca62;

           box-shadow: inset 0 0 0 1px rgba(105, 202, 98, 0.5);

          }


          .box-line2::before,

          .box-line2::after {

           content: "";

           z-index: 99;

           margin: -5%;

           box-shadow: inset 0 0 0 2px;

           animation: clipMe 8s linear infinite;

          }


          .box-line2::before {

           animation-delay: -4s;

          }


          .box-line2:hover::after,

          .box-line2:hover::before {

           background-color: rgba(255, 0, 0, 0.3);

          }


          @keyframes clipMe {


           0%,

           100% {

             clip: rect(0px, 220px, 2px, 0px);

           }


           25% {

             clip: rect(0px, 2px, 220px, 0px);

           }


           50% {

             clip: rect(218px, 220px, 220px, 0px);

           }


           75% {

             clip: rect(0px, 220px, 220px, 218px);

           }

          }


          @keyframes surround {


           0%,

           100% {

             clip: rect(0px, 220px, 2px, 0px);

           }


           25% {

             clip: rect(0px, 2px, 220px, 0px);

           }


           50% {

             clip: rect(218px, 220px, 220px, 0px);

           }


           75% {

             clip: rect(0px, 220px, 220px, 218px);

           }

          }


          .box-line1:before,

          .box-line1:after {

           position: absolute;

           top: 0;

           left: 0;

           bottom: 0;

           right: 0;

           content: "";

           z-index: 99;

           margin: -5%;

           animation: surround linear infinite 8s;

           box-shadow: inset 0 0 0 2px #69ca62;

          }


          .box-line1:before {

           animation-delay: -4s;

          }


          .box-line1 {

           border: 1px solid #69ca62;

           position: absolute;

           left: 500px;

           top: 200px;

           margin: auto;

           width: 200px;

           height: 200px;

           margin: auto;

          }

          7.Tooltip提示



          <div class="tip" data-tip="CSS偽類">CSS偽類</div>

          .tip::after {

           content: attr(data-tip);

           display: none;

           position: absolute;

           padding: 5px 10px;

           left: 15%;

           bottom: 100%;

           width: 150px;

           margin-bottom: 12px;

           transform: translateX(-50%);

           font-size: 12px;

           background: #000;

           color: #fff;

           cursor: default;

           border-radius: 4px;

          }


          .tip::before {

           content: " ";

           position: absolute;

           display: none;

           left: 15%;

           bottom: 100%;

           transform: translateX(-50%);

           margin-bottom: 3px;

           width: 0;

           height: 0;

           border-left: 6px solid transparent;

           border-right: 6px solid transparent;

           border-top: 9px solid #000;

          }


          .tip:hover::after,

          .tip:hover::before {

           display: block;

          }

          8.CSS 偽類盒子陰影

          使用偽元素:before and :after制作出了完美驚艷的相片陰影效果。其中的技巧是使用絕對定位固定偽元素,然后給它們的z-index一個負值,以背景出現。






          <div class="box effect2">

             <h3>CSS 偽類盒子陰影</h3>

          </div>

          .effect2 {

             position: relative;

          }


          .effect2::before, .effect2::after {

             z-index: -1;

             position: absolute;

             content: "";

             bottom: 15px;

             left: 10px;

             width: 50%;

             top: 80%;

             max-width: 300px;

             background: #777;

             -webkit-box-shadow: 0 15px 10px #777;

             -moz-box-shadow: 0 15px 10px #777;

             box-shadow: 0 15px 10px #777;

             -webkit-transform: rotate(-3deg);

             -moz-transform: rotate(-3deg);

             -o-transform: rotate(-3deg);

             -ms-transform: rotate(-3deg);

             transform: rotate(-3deg);

          }

          .effect2::after {

             -webkit-transform: rotate(3deg);

             -moz-transform: rotate(3deg);

             -o-transform: rotate(3deg);

             -ms-transform: rotate(3deg);

             transform: rotate(3deg);

             right: 10px;

             left: auto;

          }

          CSS Box 陰影效果


          9.Tabs當前激活狀態



             <div class="sm-box flex">

                 <div class="menu-tabs active">首頁</div>

                 <div class="menu-tabs">新聞</div>

                 <div class="menu-tabs">視頻</div>

                 <div class="menu-tabs">圖片</div>

             </div>

          .menu-tabs {

           display: block;

           padding: 0.25rem 1.5rem;

           clear: both;

           font-weight: 400;

           color: #212529;

           text-align: inherit;

           white-space: nowrap;

           background-color: transparent;

           width: 50px;

           border: 0;

           height: 35px;

           justify-content: center;

           display: flex;

           cursor: pointer;

          }


          .menu-tabs:hover {

           color: #20a884;

           position: relative;

          }


          .menu-tabs:hover:after {

           position: absolute;

           content: "";

           border: 1px solid #20a884;

           width: 3rem;

           left: 0;

           bottom: 0;

           margin-left: 50%;

           transform: translateX(-50%);

          }


          .active {

           position: relative;

           color: #20a884;

          }


          .flex {

           display: flex;

          }


          .active::after {

           position: absolute;

           content: "";

           border: 1px solid #20a884;

           width: 3rem;

           left: 0;

           bottom: 0;

           margin-left: 50%;

           transform: translateX(-50%);

          }

          10.偽元素模糊背景



          <div class="container">

            <div class="overlay">

               <h1>A blurred overlay</h1>

              <p>... mask or whatever

              <br>that is responsive and could be cross-browser compatible back to IE9</p>

            </div>

          </div>

          .container {

           width: 100%;

           height: 100%;

           margin: 0;

          }


          .container,

          .overlay:before {

           background: url(https://wow.techbrood.com/assets/landing.jpg) no-repeat fixed 0 0 / cover;

          }


          .container {

           -webkit-box-align: center;

           -webkit-align-items: center;

           -ms-flex-align: center;

           align-items: center;

           display: -webkit-box;

           display: -webkit-flex;

           display: -ms-flexbox;

           display: flex;

           -webkit-box-pack: center;

           -webkit-justify-content: center;

           -ms-flex-pack: center;

           justify-content: center;

          }


          .overlay {

           max-height: 200px;

           margin: 0 auto;

           max-width: 768px;

           padding: 50px;

           position: relative;

           color: white;

           font-family: "Lato";

           position: relative;

           text-align: center;

           z-index: 0;

          }


          .overlay:before {

           content: "";

           -webkit-filter: blur(100px);

           filter: blur(100px);

           height: 100%;

           left: 0;

           position: absolute;

           top: 0;

           width: 100%;

           z-index: -1;

          }

          11.藍湖文字



          <span class="lanhu_text">

              本站由叫我詹躲躲提供技術支持

          </span>

          .lanhu_text {

           position: relative;

           color: #2878ff;

          }


          .lanhu_text::before {

           content: "";

           width: 80px;

           height: 20px;

           position: absolute;

           left: -86px;

           top: 0;

           background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAAABCAYAAABJwyn/AAAAjElEQVQoU22NSw7CQAxDX8ahICF2HIDTcf9d1c8kaDpthQSL6CmxHRuk8cZfMxqf6DGh+Y5uCxquaB7xdUCXilZHWvBorEiOaqAKrkRRUEmUOw283TKRRb9b4GnIEpWmGYrA237kDh1w6J5N7zzzZv13gtuvT7t++jefUTYmwvpk7v3fPaCzn//9LfsBvRpHnliu+xMAAAAASUVORK5CYII=) 0 no-repeat;

          }


          .lanhu_text::after {

           content: "";

           width: 80px;

           height: 20px;

           position: absolute;

           right: -86px;

           top: 0;

           background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAAABCAYAAABJwyn/AAAAhElEQVQoU22OSw7CQAxDXzJDWbHiEFyRJQflFggJSjyDph9oC6snx45lO52rrKJqzIyNlkFUPv6vnv+3uW9vrPpBq77/uWFPAXnrnfYVQ16JiWq0stjvxKB9vDc/MeaXWo5cqDEFUkKpEuEoB3ollHvUZ9QV4rFD3R3d9ujwJK5HxAW9AStemAIOwCNlAAAAAElFTkSuQmCC) 100% no-repeat;

          }

          12 主要標題



          <div class="first-title">服務項目</div>

          .first-title {

           position: relative;

           color: #a98661;

           font-weight: 400;

           font-size: 30px;

           text-align: center;

          }


          .first-title::before,

          .first-title::after {

           position: absolute;

           content: "";

           width: 110px;

           border-bottom: 1px solid #a98661;

           top: 50%;

           transform: translateY(-50%);

          }


          .first-title::before {

           left: 100px;

          }


          .first-title::after {

           right: 100px;

          }

          13.鼠標浮層遮罩浮層



          <div class="black-mask"></div>

          .black-mask {

           position: relative;

           height: 100%;

           width: 100%;

           cursor: pointer;

          }


          .black-mask:hover {

           transition-duration: 1s;

           scale: 1.02;

          }


          .black-mask:hover:before {

           object-fit: cover;

          }


          .black-mask:hover:after {

           height: 100%;

           opacity: 1;

           transition-duration: 1s;

           display: flex;

           align-items: flex-end;

           padding: 0 30px 15px;

          }


          .black-mask::before {

           position: absolute;

           content: "";

           background: url(https://dcdn.it120.cc/2019/11/14/f17c5848-6d1f-4254-b3ba-64d3969d16b6.jpg) no-repeat;

           background-size: 100% 100%;

           width: 100%;

           height: 100%;

          }


          .black-mask::after {

           position: absolute;

           content: "霧在微風的吹動下滾來滾去,像冰峰雪山,似蓬萊仙境,如海市蜃樓,使人覺得飄然欲仙。山河景色在霧的裝點下,變得更加美麗。遠處的七連山巍峨挺拔,它們仿佛成了神仙住的寶山,令人神往。近處池塘邊時時飄來霧氣,在初升陽光的照耀下,呈現出赤、橙、黃、綠、青、藍、紫七種色彩。......";

           width: 90%;

           height: 0%;

           bottom: 0;

           right: 0;

           z-index: 32;

           background: rgba(0, 0, 0, 0.3);

           opacity: 1;

           color: #fff;

           opacity: 0;

           padding: 0 30px 0;

          }

          14.絢麗光圈



          <div class="aperture">光圈</div>

          .aperture {

           width: 136px;

           height: 136px;

           background-color: #dc5947;

           border-radius: 50%;

           line-height: 136px;

           text-align: center;

           color: #fff;

           font-size: 24px;

           cursor: pointer;

           position: relative;

          }


          .aperture::before {

           border: 3px dashed #a0ff80;

           content: "";

           width: 144px;

           height: 144px;

           position: absolute;

           border-radius: 50%;

           left: -8px;

           top: -6px;

           animation: clockwise 5s linear infinite;

          }


          @keyframes clockwise {

           100% {

             transform: rotate(360deg);

           }

          }

          15.彩色流動邊框



          <div class="rainbow"></div>

          .rainbow {

           position: relative;

           z-index: 0;

           width: 400px;

           height: 300px;

           border-radius: 10px;

           overflow: hidden;

           padding: 2rem;

          }


          .rainbow::before {

           content: '';

           position: absolute;

           z-index: -2;

           left: -50%;

           top: -50%;

           width: 200%;

           height: 200%;

           background-color: #399953;

           background-repeat: no-repeat;

           background-size: 50% 50%, 50% 50%;

           background-position: 0 0, 100% 0, 100% 100%, 0 100%;

           background-image: linear-gradient(#399953, #399953), linear-gradient(#fbb300, #fbb300), linear-gradient(#d53e33, #d53e33), linear-gradient(#377af5, #377af5);

           -webkit-animation: rotate 4s linear infinite;

           animation: rotate 4s linear infinite;

          }


          .rainbow::after {

           content: '';

           position: absolute;

           z-index: -1;

           left: 6px;

           top: 6px;

           width: calc(100% - 12px);

           height: calc(100% - 12px);

           background: white;

           border-radius: 5px;

          }


          @keyframes rotate {

           100% {

             -webkit-transform: rotate(1turn);

             transform: rotate(1turn);

           }

          }

          16.炫酷偽類邊框



          <div class="corner-button">CSS3</div>

          .corner-button::before, .corner-button::after {

           content: '';

           position: absolute;

           background: #2f2f2f;

           z-index: 1;

           transition: all 0.3s;

          }

          .corner-button::before {

           width: calc(100% - 3rem);

           height: calc(101% + 1rem);

           top: -0.5rem;

           left: 50%;

           -webkit-transform: translateX(-50%);

           transform: translateX(-50%);

          }

          .corner-button::after {

           height: calc(100% - 3rem);

           width: calc(101% + 1rem);

           left: -0.5rem;

           top: 50%;

           -webkit-transform: translateY(-50%);

           transform: translateY(-50%);

          }



          .corner-button:hover {

           color: pink;

          }

          .corner-button {

           font-family: 'Lato', sans-serif;

           letter-spacing: .02rem;

           cursor: pointer;

           background: transparent;

           border: 0.5rem solid currentColor;

           padding: 1.5rem 2rem;

           font-size: 2.2rem;

           color: #06c17f;

           position: relative;

           transition: color 0.3s;

           text-align: center;

           margin: 5rem 12rem;

          }

          .corner-button:hover::after {

           height: 0;

          }


          .corner-button:hover::before {

           width: 0;

          }

          .bg-f2{

           background: #2f2f2f;

          }

          17.偽類美化文字



          <div class="beautify-font" data-text='躲躲'>躲躲</div>

          <div class="beautify-font2" data-text='躲躲'>躲躲</div>

          .beautify-font{

           position: relative;

           font-size: 12rem;

           color: #0099CC

          }

          .beautify-font::before{

           position: absolute;

           font-size: 12rem;

           color: #333;

           content: attr(data-text);

           white-space:nowrap;

           width: 50%;

           display: inline-block;

           overflow: hidden;

           transition:1s ease-in-out 0s;

          }

          .beautify-font2{

           position: relative;

           font-size: 6rem;

           color: #0099CC

          }

          .beautify-font2::before{

           position: absolute;

           font-size: 6rem;

           color: #333;

           content: attr(data-text);

           white-space:nowrap;

           height: 50%;

           display: inline-block;

           overflow: hidden;

           transition:1s ease-in-out 0s;

          }


          .beautify-font:hover::before{

           width:0;

          }

          .beautify-font2:hover::before{

           height: 0;

          }

          18.照片堆疊效果

          只使用一張圖片來創造出一堆圖片疊摞在一起的效果,能做到嗎?當然,關鍵是要使用偽元素:before和:after來幫助呈現。把這些偽元素的z-index設置成負值,讓它們以背景方式起作用。




          <div class="stackthree"><img src="./images/city.jpg"></div>

          .stackthree::before {

           background: #eff4de;

          }


          .stackthree, .stackthree::before, .stackthree::after {

           border: 6px solid #fff;

           height: 200px;

           width: 200px;

           -webkit-box-shadow: 2px 2px 5px rgba(0,0,0,0.3);

           -moz-box-shadow: 2px 2px 5px rgba(0,0,0,0.3);

           box-shadow: 2px 2px 5px rgba(0,0,0,0.3);

          }


          .stackthree::before {

           top: 5px;

           left: -15px;

           z-index: -1;

           -webkit-transform: rotate(-10deg);

           -moz-transform: rotate(-10deg);

           -o-transform: rotate(-10deg);

           -ms-transform: rotate(-10deg);

           transform: rotate(-10deg);

          }

          .stackthree::after {

           top: -2px;

           left: -10px;

           -webkit-transform: rotate(-5deg);

           -moz-transform: rotate(-5deg);

           -o-transform: rotate(-5deg);

           -ms-transform: rotate(-5deg);

           transform: rotate(-5deg);

          }


          .stackthree::before, .stackthree::after {

           background: #768590;

           content: "";

           position: absolute;

           z-index: -1;

           height: 0px\9;

           width: 0px\9;

           border: none\9;

          }

          .stackthree {

           float: left;

           position: relative;

           margin: 50px;

          }

          為元素的兼容性

          不論你使用單冒號還是雙冒號語法,瀏覽器都能識別。因為IE8只支持單冒號的語法,所以,如果你想兼容IE8,保險的做法是使用單冒號。

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務

          你不知道的 WebSocket

          seo達人

          在最后的 阿寶哥有話說 環節,阿寶哥將介紹 WebSocket 與 HTTP 之間的關系、WebSocket 與長輪詢有什么區別、什么是 WebSocket 心跳及 Socket 是什么等內容。


          下面我們進入正題,為了讓大家能夠更好地理解和掌握 WebSocket 技術,我們先來介紹一下什么是 WebSocket。


          一、什么是 WebSocket

          1.1 WebSocket 誕生背景

          早期,很多網站為了實現推送技術,所用的技術都是輪詢。輪詢是指由瀏覽器每隔一段時間向服務器發出 HTTP 請求,然后服務器返回的數據給客戶端。常見的輪詢方式分為輪詢與長輪詢,它們的區別如下圖所示:




          為了更加直觀感受輪詢與長輪詢之間的區別,我們來看一下具體的代碼:




          這種傳統的模式帶來很明顯的缺點,即瀏覽器需要不斷的向服務器發出請求,然而 HTTP 請求與響應可能會包含較長的頭部,其中真正有效的數據可能只是很小的一部分,所以這樣會消耗很多帶寬資源。


          比較新的輪詢技術是 Comet)。這種技術雖然可以實現雙向通信,但仍然需要反復發出請求。而且在 Comet 中普遍采用的 HTTP 長連接也會消耗服務器資源。


          在這種情況下,HTML5 定義了 WebSocket 協議,能更好的節省服務器資源和帶寬,并且能夠更實時地進行通訊。Websocket 使用 ws 或 wss 的統一資源標志符(URI),其中 wss 表示使用了 TLS 的 Websocket。如:


          ws://echo.websocket.org

          wss://echo.websocket.org

          WebSocket 與 HTTP 和 HTTPS 使用相同的 TCP 端口,可以繞過大多數防火墻的限制。默認情況下,WebSocket 協議使用 80 端口;若運行在 TLS 之上時,默認使用 443 端口。


          1.2 WebSocket 簡介

          WebSocket 是一種網絡傳輸協議,可在單個 TCP 連接上進行全雙工通信,位于 OSI 模型的應用層。WebSocket 協議在 2011 年由 IETF 標準化為 RFC 6455,后由 RFC 7936 補充規范。


          WebSocket 使得客戶端和服務器之間的數據交換變得更加簡單,允許服務端主動向客戶端推送數據。在 WebSocket API 中,瀏覽器和服務器只需要完成一次握手,兩者之間就可以創建持久性的連接,并進行雙向數據傳輸。


          介紹完輪詢和 WebSocket 的相關內容之后,接下來我們來看一下 XHR Polling 與 WebSocket 之間的區別:




          1.3 WebSocket 優點

          較少的控制開銷。在連接創建后,服務器和客戶端之間交換數據時,用于協議控制的數據包頭部相對較小。

          更強的實時性。由于協議是全雙工的,所以服務器可以隨時主動給客戶端下發數據。相對于 HTTP 請求需要等待客戶端發起請求服務端才能響應,延遲明顯更少。

          保持連接狀態。與 HTTP 不同的是,WebSocket 需要先創建連接,這就使得其成為一種有狀態的協議,之后通信時可以省略部分狀態信息。

          更好的二進制支持。WebSocket 定義了二進制幀,相對 HTTP,可以更輕松地處理二進制內容。

          可以支持擴展。WebSocket 定義了擴展,用戶可以擴展協議、實現部分自定義的子協議。

          由于 WebSocket 擁有上述的優點,所以它被廣泛地應用在即時通信、實時音視頻、在線教育和游戲等領域。對于前端開發者來說,要想使用 WebSocket 提供的強大能力,就必須先掌握 WebSocket API,下面阿寶哥帶大家一起來認識一下 WebSocket API。


          二、WebSocket API

          在介紹 WebSocket API 之前,我們先來了解一下它的兼容性:




          (圖片來源:https://caniuse.com/#search=W...)


          從上圖可知,目前主流的 Web 瀏覽器都支持 WebSocket,所以我們可以在大多數項目中放心地使用它。


          在瀏覽器中要使用 WebSocket 提供的能力,我們就必須先創建 WebSocket 對象,該對象提供了用于創建和管理 WebSocket 連接,以及可以通過該連接發送和接收數據的 API。


          使用 WebSocket 構造函數,我們就能輕易地構造一個 WebSocket 對象。接下來我們將從 WebSocket 構造函數、WebSocket 對象的屬性、方法及 WebSocket 相關的事件四個方面來介紹 WebSocket API,首先我們從 WebSocket 的構造函數入手:


          2.1 構造函數

          WebSocket 構造函數的語法為:


          const myWebSocket = new WebSocket(url [, protocols]);

          相關參數說明如下:


          url:表示連接的 URL,這是 WebSocket 服務器將響應的 URL。

          protocols(可選):一個協議字符串或者一個包含協議字符串的數組。這些字符串用于指定子協議,這樣單個服務器可以實現多個 WebSocket 子協議。比如,你可能希望一臺服務器能夠根據指定的協議(protocol)處理不同類型的交互。如果不指定協議字符串,則假定為空字符串。

          當嘗試連接的端口被阻止時,會拋出 SECURITY_ERR 異常。


          2.2 屬性

          WebSocket 對象包含以下屬性:




          每個屬性的具體含義如下:


          binaryType:使用二進制的數據類型連接。

          bufferedAmount(只讀):未發送至服務器的字節數。

          extensions(只讀):服務器選擇的擴展。

          onclose:用于指定連接關閉后的回調函數。

          onerror:用于指定連接失敗后的回調函數。

          onmessage:用于指定當從服務器接受到信息時的回調函數。

          onopen:用于指定連接成功后的回調函數。

          protocol(只讀):用于返回服務器端選中的子協議的名字。

          readyState(只讀):返回當前 WebSocket 的連接狀態,共有 4 種狀態:


          CONNECTING — 正在連接中,對應的值為 0;

          OPEN — 已經連接并且可以通訊,對應的值為 1;

          CLOSING — 連接正在關閉,對應的值為 2;

          CLOSED — 連接已關閉或者沒有連接成功,對應的值為 3。

          url(只讀):返回值為當構造函數創建 WebSocket 實例對象時 URL 的絕對路徑。

          2.3 方法

          close([code[, reason]]):該方法用于關閉 WebSocket 連接,如果連接已經關閉,則此方法不執行任何操作。

          send(data):該方法將需要通過 WebSocket 鏈接傳輸至服務器的數據排入隊列,并根據所需要傳輸的數據的大小來增加 bufferedAmount 的值 。若數據無法傳輸(比如數據需要緩存而緩沖區已滿)時,套接字會自行關閉。

          2.4 事件

          使用 addEventListener() 或將一個事件監聽器賦值給 WebSocket 對象的 oneventname 屬性,來監聽下面的事件。


          close:當一個 WebSocket 連接被關閉時觸發,也可以通過 onclose 屬性來設置。

          error:當一個 WebSocket 連接因錯誤而關閉時觸發,也可以通過 onerror 屬性來設置。

          message:當通過 WebSocket 收到數據時觸發,也可以通過 onmessage 屬性來設置。

          open:當一個 WebSocket 連接成功時觸發,也可以通過 onopen 屬性來設置。

          介紹完 WebSocket API,我們來舉一個使用 WebSocket 發送普通文本的示例。


          2.5 發送普通文本



          在以上示例中,我們在頁面上創建了兩個 textarea,分別用于存放 待發送的數據 和 服務器返回的數據。當用戶輸入完待發送的文本之后,點擊 發送 按鈕時會把輸入的文本發送到服務端,而服務端成功接收到消息之后,會把收到的消息原封不動地回傳到客戶端。


          // const socket = new WebSocket("ws://echo.websocket.org");

          // const sendMsgContainer = document.querySelector("#sendMessage");

          function send() {

           const message = sendMsgContainer.value;

           if (socket.readyState !== WebSocket.OPEN) {

             console.log("連接未建立,還不能發送消息");

             return;

           }

           if (message) socket.send(message);

          }

          當然客戶端接收到服務端返回的消息之后,會把對應的文本內容保存到 接收的數據 對應的 textarea 文本框中。


          // const socket = new WebSocket("ws://echo.websocket.org");

          // const receivedMsgContainer = document.querySelector("#receivedMessage");    

          socket.addEventListener("message", function (event) {

           console.log("Message from server ", event.data);

           receivedMsgContainer.value = event.data;

          });

          為了更加直觀地理解上述的數據交互過程,我們使用 Chrome 瀏覽器的開發者工具來看一下相應的過程:




          以上示例對應的完整代碼如下所示:


          <!DOCTYPE html>

          <html>

           <head>

             <meta charset="UTF-8" />

             <meta name="viewport" content="width=device-width, initial-scale=1.0" />

             <title>WebSocket 發送普通文本示例</title>

             <style>

               .block {

                 flex: 1;

               }

             </style>

           </head>

           <body>

             <h3>阿寶哥:WebSocket 發送普通文本示例</h3>

             <div style="display: flex;">

               <div class="block">

                 <p>即將發送的數據:<button onclick="send()">發送</button></p>

                 <textarea id="sendMessage" rows="5" cols="15"></textarea>

               </div>

               <div class="block">

                 <p>接收的數據:</p>

                 <textarea id="receivedMessage" rows="5" cols="15"></textarea>

               </div>

             </div>


             <script>

               const sendMsgContainer = document.querySelector("#sendMessage");

               const receivedMsgContainer = document.querySelector("#receivedMessage");

               const socket = new WebSocket("ws://echo.websocket.org");


               // 監聽連接成功事件

               socket.addEventListener("open", function (event) {

                 console.log("連接成功,可以開始通訊");

               });


               // 監聽消息

               socket.addEventListener("message", function (event) {

                 console.log("Message from server ", event.data);

                 receivedMsgContainer.value = event.data;

               });


               function send() {

                 const message = sendMsgContainer.value;

                 if (socket.readyState !== WebSocket.OPEN) {

                   console.log("連接未建立,還不能發送消息");

                   return;

                 }

                 if (message) socket.send(message);

               }

             </script>

           </body>

          </html>

          其實 WebSocket 除了支持發送普通的文本之外,它還支持發送二進制數據,比如 ArrayBuffer 對象、Blob 對象或者 ArrayBufferView 對象:


          const socket = new WebSocket("ws://echo.websocket.org");

          socket.onopen = function () {

           // 發送UTF-8編碼的文本信息

           socket.send("Hello Echo Server!");

           // 發送UTF-8編碼的JSON數據

           socket.send(JSON.stringify({ msg: "我是阿寶哥" }));

           

           // 發送二進制ArrayBuffer

           const buffer = new ArrayBuffer(128);

           socket.send(buffer);

           

           // 發送二進制ArrayBufferView

           const intview = new Uint32Array(buffer);

           socket.send(intview);


           // 發送二進制Blob

           const blob = new Blob([buffer]);

           socket.send(blob);

          };

          以上代碼成功運行后,通過 Chrome 開發者工具,我們可以看到對應的數據交互過程:




          下面阿寶哥以發送 Blob 對象為例,來介紹一下如何發送二進制數據。


          Blob(Binary Large Object)表示二進制類型的大對象。在數據庫管理系統中,將二進制數據存儲為一個單一個體的集合。Blob 通常是影像、聲音或多媒體文件。在 JavaScript 中 Blob 類型的對象表示不可變的類似文件對象的原始數據。

          對 Blob 感興趣的小伙伴,可以閱讀 “你不知道的 Blob” 這篇文章。


          2.6 發送二進制數據



          在以上示例中,我們在頁面上創建了兩個 textarea,分別用于存放 待發送的數據 和 服務器返回的數據。當用戶輸入完待發送的文本之后,點擊 發送 按鈕時,我們會先獲取輸入的文本并把文本包裝成 Blob 對象然后發送到服務端,而服務端成功接收到消息之后,會把收到的消息原封不動地回傳到客戶端。


          當瀏覽器接收到新消息后,如果是文本數據,會自動將其轉換成 DOMString 對象,如果是二進制數據或 Blob 對象,會直接將其轉交給應用,由應用自身來根據返回的數據類型進行相應的處理。


          數據發送代碼


          // const socket = new WebSocket("ws://echo.websocket.org");

          // const sendMsgContainer = document.querySelector("#sendMessage");

          function send() {

           const message = sendMsgContainer.value;

           if (socket.readyState !== WebSocket.OPEN) {

             console.log("連接未建立,還不能發送消息");

             return;

           }

           const blob = new Blob([message], { type: "text/plain" });

           if (message) socket.send(blob);

           console.log(`未發送至服務器的字節數:${socket.bufferedAmount}`);

          }

          當然客戶端接收到服務端返回的消息之后,會判斷返回的數據類型,如果是 Blob 類型的話,會調用 Blob 對象的 text() 方法,獲取 Blob 對象中保存的 UTF-8 格式的內容,然后把對應的文本內容保存到 接收的數據 對應的 textarea 文本框中。


          數據接收代碼


          // const socket = new WebSocket("ws://echo.websocket.org");

          // const receivedMsgContainer = document.querySelector("#receivedMessage");

          socket.addEventListener("message", async function (event) {

           console.log("Message from server ", event.data);

           const receivedData = event.data;

           if (receivedData instanceof Blob) {

             receivedMsgContainer.value = await receivedData.text();

           } else {

             receivedMsgContainer.value = receivedData;

           }

          });

          同樣,我們使用 Chrome 瀏覽器的開發者工具來看一下相應的過程:




          通過上圖我們可以很明顯地看到,當使用發送 Blob 對象時,Data 欄位的信息顯示的是 Binary Message,而對于發送普通文本來說,Data 欄位的信息是直接顯示發送的文本消息。


          以上示例對應的完整代碼如下所示:


          <!DOCTYPE html>

          <html>

           <head>

             <meta charset="UTF-8" />

             <meta name="viewport" content="width=device-width, initial-scale=1.0" />

             <title>WebSocket 發送二進制數據示例</title>

             <style>

               .block {

                 flex: 1;

               }

             </style>

           </head>

           <body>

             <h3>阿寶哥:WebSocket 發送二進制數據示例</h3>

             <div style="display: flex;">

               <div class="block">

                 <p>待發送的數據:<button onclick="send()">發送</button></p>

                 <textarea id="sendMessage" rows="5" cols="15"></textarea>

               </div>

               <div class="block">

                 <p>接收的數據:</p>

                 <textarea id="receivedMessage" rows="5" cols="15"></textarea>

               </div>

             </div>


             <script>

               const sendMsgContainer = document.querySelector("#sendMessage");

               const receivedMsgContainer = document.querySelector("#receivedMessage");

               const socket = new WebSocket("ws://echo.websocket.org");


               // 監聽連接成功事件

               socket.addEventListener("open", function (event) {

                 console.log("連接成功,可以開始通訊");

               });


               // 監聽消息

               socket.addEventListener("message", async function (event) {

                 console.log("Message from server ", event.data);

                 const receivedData = event.data;

                 if (receivedData instanceof Blob) {

                   receivedMsgContainer.value = await receivedData.text();

                 } else {

                   receivedMsgContainer.value = receivedData;

                 }

               });


               function send() {

                 const message = sendMsgContainer.value;

                 if (socket.readyState !== WebSocket.OPEN) {

                   console.log("連接未建立,還不能發送消息");

                   return;

                 }

                 const blob = new Blob([message], { type: "text/plain" });

                 if (message) socket.send(blob);

                 console.log(`未發送至服務器的字節數:${socket.bufferedAmount}`);

               }

             </script>

           </body>

          </html>

          可能有一些小伙伴了解完 WebSocket API 之后,覺得還不夠過癮。下面阿寶哥將帶大家來實現一個支持發送普通文本的 WebSocket 服務器。


          三、手寫 WebSocket 服務器

          在介紹如何手寫 WebSocket 服務器前,我們需要了解一下 WebSocket 連接的生命周期。




          從上圖可知,在使用 WebSocket 實現全雙工通信之前,客戶端與服務器之間需要先進行握手(Handshake),在完成握手之后才能開始進行數據的雙向通信。


          握手是在通信電路創建之后,信息傳輸開始之前。握手用于達成參數,如信息傳輸率,字母表,奇偶校驗,中斷過程,和其他協議特性。 握手有助于不同結構的系統或設備在通信信道中連接,而不需要人為設置參數。


          既然握手是 WebSocket 連接生命周期的第一個環節,接下來我們就先來分析 WebSocket 的握手協議。


          3.1 握手協議

          WebSocket 協議屬于應用層協議,它依賴于傳輸層的 TCP 協議。WebSocket 通過 HTTP/1.1 協議的 101 狀態碼進行握手。為了創建 WebSocket 連接,需要通過瀏覽器發出請求,之后服務器進行回應,這個過程通常稱為 “握手”(Handshaking)。


          利用 HTTP 完成握手有幾個好處。首先,讓 WebSocket 與現有 HTTP 基礎設施兼容:使得 WebSocket 服務器可以運行在 80 和 443 端口上,這通常是對客戶端唯一開放的端口。其次,讓我們可以重用并擴展 HTTP 的 Upgrade 流,為其添加自定義的 WebSocket 首部,以完成協商。


          下面我們以前面已經演示過的發送普通文本的例子為例,來具體分析一下握手過程。


          3.1.1 客戶端請求

          GET ws://echo.websocket.org/ HTTP/1.1

          Host: echo.websocket.org

          Origin: file://

          Connection: Upgrade

          Upgrade: websocket

          Sec-WebSocket-Version: 13

          Sec-WebSocket-Key: Zx8rNEkBE4xnwifpuh8DHQ==

          Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

          備注:已忽略部分 HTTP 請求頭

          字段說明


          Connection 必須設置 Upgrade,表示客戶端希望連接升級。

          Upgrade 字段必須設置 websocket,表示希望升級到 WebSocket 協議。

          Sec-WebSocket-Version 表示支持的 WebSocket 版本。RFC6455 要求使用的版本是 13,之前草案的版本均應當棄用。

          Sec-WebSocket-Key 是隨機的字符串,服務器端會用這些數據來構造出一個 SHA-1 的信息摘要。把 “Sec-WebSocket-Key” 加上一個特殊字符串 “258EAFA5-E914-47DA-95CA-C5AB0DC85B11”,然后計算 SHA-1 摘要,之后進行 Base64 編碼,將結果做為 “Sec-WebSocket-Accept” 頭的值,返回給客戶端。如此操作,可以盡量避免普通 HTTP 請求被誤認為 WebSocket 協議。

          Sec-WebSocket-Extensions 用于協商本次連接要使用的 WebSocket 擴展:客戶端發送支持的擴展,服務器通過返回相同的首部確認自己支持一個或多個擴展。

          Origin 字段是可選的,通常用來表示在瀏覽器中發起此 WebSocket 連接所在的頁面,類似于 Referer。但是,與 Referer 不同的是,Origin 只包含了協議和主機名稱。

          3.1.2 服務端響應

          HTTP/1.1 101 Web Socket Protocol Handshake ①

          Connection: Upgrade ②

          Upgrade: websocket ③

          Sec-WebSocket-Accept: 52Rg3vW4JQ1yWpkvFlsTsiezlqw= ④

          備注:已忽略部分 HTTP 響應頭

          ① 101 響應碼確認升級到 WebSocket 協議。

          ② 設置 Connection 頭的值為 "Upgrade" 來指示這是一個升級請求。HTTP 協議提供了一種特殊的機制,這一機制允許將一個已建立的連接升級成新的、不相容的協議。

          ③ Upgrade 頭指定一項或多項協議名,按優先級排序,以逗號分隔。這里表示升級為 WebSocket 協議。

          ④ 簽名的鍵值驗證協議支持。

          介紹完 WebSocket 的握手協議,接下來阿寶哥將使用 Node.js 來開發我們的 WebSocket 服務器。


          3.2 實現握手功能

          要開發一個 WebSocket 服務器,首先我們需要先實現握手功能,這里阿寶哥使用 Node.js 內置的 http 模塊來創建一個 HTTP 服務器,具體代碼如下所示:


          const http = require("http");


          const port = 8888;

          const { generateAcceptValue } = require("./util");


          const server = http.createServer((req, res) => {

           res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });

           res.end("大家好,我是阿寶哥。感謝你閱讀“你不知道的WebSocket”");

          });


          server.on("upgrade", function (req, socket) {

           if (req.headers["upgrade"] !== "websocket") {

             socket.end("HTTP/1.1 400 Bad Request");

             return;

           }

           // 讀取客戶端提供的Sec-WebSocket-Key

           const secWsKey = req.headers["sec-websocket-key"];

           // 使用SHA-1算法生成Sec-WebSocket-Accept

           const hash = generateAcceptValue(secWsKey);

           // 設置HTTP響應頭

           const responseHeaders = [

             "HTTP/1.1 101 Web Socket Protocol Handshake",

             "Upgrade: WebSocket",

             "Connection: Upgrade",

             `Sec-WebSocket-Accept: ${hash}`,

           ];

           // 返回握手請求的響應信息

           socket.write(responseHeaders.join("\r\n") + "\r\n\r\n");

          });


          server.listen(port, () =>

           console.log(`Server running at http://localhost:${port}`)

          );

          在以上代碼中,我們首先引入了 http 模塊,然后通過調用該模塊的 createServer() 方法創建一個 HTTP 服務器,接著我們監聽 upgrade 事件,每次服務器響應升級請求時就會觸發該事件。由于我們的服務器只支持升級到 WebSocket 協議,所以如果客戶端請求升級的協議非 WebSocket 協議,我們將會返回 “400 Bad Request”。


          當服務器接收到升級為 WebSocket 的握手請求時,會先從請求頭中獲取 “Sec-WebSocket-Key” 的值,然后把該值加上一個特殊字符串 “258EAFA5-E914-47DA-95CA-C5AB0DC85B11”,然后計算 SHA-1 摘要,之后進行 Base64 編碼,將結果做為 “Sec-WebSocket-Accept” 頭的值,返回給客戶端。


          上述的過程看起來好像有點繁瑣,其實利用 Node.js 內置的 crypto 模塊,幾行代碼就可以搞定了:


          // util.js

          const crypto = require("crypto");

          const MAGIC_KEY = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";


          function generateAcceptValue(secWsKey) {

           return crypto

             .createHash("sha1")

             .update(secWsKey + MAGIC_KEY, "utf8")

             .digest("base64");

          }

          開發完握手功能之后,我們可以使用前面的示例來測試一下該功能。待服務器啟動之后,我們只要對 “發送普通文本” 示例,做簡單地調整,即把先前的 URL 地址替換成 ws://localhost:8888,就可以進行功能驗證。


          感興趣的小伙們可以試試看,以下是阿寶哥本地運行后的結果:




          從上圖可知,我們實現的握手功能已經可以正常工作了。那么握手有沒有可能失敗呢?答案是肯定的。比如網絡問題、服務器異常或 Sec-WebSocket-Accept 的值不正確。


          下面阿寶哥修改一下 “Sec-WebSocket-Accept” 生成規則,比如修改 MAGIC_KEY 的值,然后重新驗證一下握手功能。此時,瀏覽器的控制臺會輸出以下異常信息:


          WebSocket connection to 'ws://localhost:8888/' failed: Error during WebSocket handshake: Incorrect 'Sec-WebSocket-Accept' header value

          如果你的 WebSocket 服務器要支持子協議的話,你可以參考以下代碼進行子協議的處理,阿寶哥就不繼續展開介紹了。


          // 從請求頭中讀取子協議

          const protocol = req.headers["sec-websocket-protocol"];

          // 如果包含子協議,則解析子協議

          const protocols = !protocol ? [] : protocol.split(",").map((s) => s.trim());


          // 簡單起見,我們僅判斷是否含有JSON子協議

          if (protocols.includes("json")) {

           responseHeaders.push(`Sec-WebSocket-Protocol: json`);

          }

          好的,WebSocket 握手協議相關的內容基本已經介紹完了。下一步我們來介紹開發消息通信功能需要了解的一些基礎知識。


          3.3 消息通信基礎

          在 WebSocket 協議中,數據是通過一系列數據幀來進行傳輸的。為了避免由于網絡中介(例如一些攔截代理)或者一些安全問題,客戶端必須在它發送到服務器的所有幀中添加掩碼。服務端收到沒有添加掩碼的數據幀以后,必須立即關閉連接。


          3.3.1 數據幀格式

          要實現消息通信,我們就必須了解 WebSocket 數據幀的格式:


          0                   1                   2                   3

          0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1

          +-+-+-+-+-------+-+-------------+-------------------------------+

          |F|R|R|R| opcode|M| Payload len |    Extended payload length    |

          |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |

          |N|V|V|V|       |S|             |   (if payload len==126/127)   |

          | |1|2|3|       |K|             |                               |

          +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +

          |     Extended payload length continued, if payload len == 127  |

          + - - - - - - - - - - - - - - - +-------------------------------+

          |                               |Masking-key, if MASK set to 1  |

          +-------------------------------+-------------------------------+

          | Masking-key (continued)       |          Payload Data         |

          +-------------------------------- - - - - - - - - - - - - - - - +

          :                     Payload Data continued ...                :

          + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +

          |                     Payload Data continued ...                |

          +---------------------------------------------------------------+

          可能有一些小伙伴看到上面的內容之后,就開始有點 “懵逼” 了。下面我們來結合實際的數據幀來進一步分析一下:




          在上圖中,阿寶哥簡單分析了 “發送普通文本” 示例對應的數據幀格式。這里我們來進一步介紹一下 Payload length,因為在后面開發數據解析功能的時候,需要用到該知識點。


          Payload length 表示以字節為單位的 “有效負載數據” 長度。它有以下幾種情形:


          如果值為 0-125,那么就表示負載數據的長度。

          如果是 126,那么接下來的 2 個字節解釋為 16 位的無符號整形作為負載數據的長度。

          如果是 127,那么接下來的 8 個字節解釋為一個 64 位的無符號整形(最高位的 bit 必須為 0)作為負載數據的長度。

          多字節長度量以網絡字節順序表示,有效負載長度是指 “擴展數據” + “應用數據” 的長度?!皵U展數據” 的長度可能為 0,那么有效負載長度就是 “應用數據” 的長度。


          另外,除非協商過擴展,否則 “擴展數據” 長度為 0 字節。在握手協議中,任何擴展都必須指定 “擴展數據” 的長度,這個長度如何進行計算,以及這個擴展如何使用。如果存在擴展,那么這個 “擴展數據” 包含在總的有效負載長度中。


          3.3.2 掩碼算法

          掩碼字段是一個由客戶端隨機選擇的 32 位的值。掩碼值必須是不可被預測的。因此,掩碼必須來自強大的熵源(entropy),并且給定的掩碼不能讓服務器或者代理能夠很容易的預測到后續幀。掩碼的不可預測性對于預防惡意應用的作者在網上暴露相關的字節數據至關重要。


          掩碼不影響數據荷載的長度,對數據進行掩碼操作和對數據進行反掩碼操作所涉及的步驟是相同的。掩碼、反掩碼操作都采用如下算法:


          j = i MOD 4

          transformed-octet-i = original-octet-i XOR masking-key-octet-j

          original-octet-i:為原始數據的第 i 字節。

          transformed-octet-i:為轉換后的數據的第 i 字節。

          masking-key-octet-j:為 mask key 第 j 字節。

          為了讓小伙伴們能夠更好的理解上面掩碼的計算過程,我們來對示例中 “我是阿寶哥” 數據進行掩碼操作。這里 “我是阿寶哥” 對應的 UTF-8 編碼如下所示:


          E6 88 91 E6 98 AF E9 98 BF E5 AE 9D E5 93 A5

          而對應的 Masking-Key 為 0x08f6efb1,根據上面的算法,我們可以這樣進行掩碼運算:


          let uint8 = new Uint8Array([0xE6, 0x88, 0x91, 0xE6, 0x98, 0xAF, 0xE9, 0x98,

           0xBF, 0xE5, 0xAE, 0x9D, 0xE5, 0x93, 0xA5]);

          let maskingKey = new Uint8Array([0x08, 0xf6, 0xef, 0xb1]);

          let maskedUint8 = new Uint8Array(uint8.length);


          for (let i = 0, j = 0; i < uint8.length; i++, j = i % 4) {

           maskedUint8[i] = uint8[i] ^ maskingKey[j];

          }


          console.log(Array.from(maskedUint8).map(num=>Number(num).toString(16)).join(' '));

          以上代碼成功運行后,控制臺會輸出以下結果:


          ee 7e 7e 57 90 59 6 29 b7 13 41 2c ed 65 4a

          上述結果與 WireShark 中的 Masked payload 對應的值是一致的,具體如下圖所示:




          在 WebSocket 協議中,數據掩碼的作用是增強協議的安全性。但數據掩碼并不是為了保護數據本身,因為算法本身是公開的,運算也不復雜。那么為什么還要引入數據掩碼呢?引入數據掩碼是為了防止早期版本的協議中存在的代理緩存污染攻擊等問題。


          了解完 WebSocket 掩碼算法和數據掩碼的作用之后,我們再來介紹一下數據分片的概念。


          3.3.3 數據分片

          WebSocket 的每條消息可能被切分成多個數據幀。當 WebSocket 的接收方收到一個數據幀時,會根據 FIN 的值來判斷,是否已經收到消息的最后一個數據幀。


          利用 FIN 和 Opcode,我們就可以跨幀發送消息。操作碼告訴了幀應該做什么。如果是 0x1,有效載荷就是文本。如果是 0x2,有效載荷就是二進制數據。但是,如果是 0x0,則該幀是一個延續幀。這意味著服務器應該將幀的有效負載連接到從該客戶機接收到的最后一個幀。


          為了讓大家能夠更好地理解上述的內容,我們來看一個來自 MDN 上的示例:


          Client: FIN=1, opcode=0x1, msg="hello"

          Server: (process complete message immediately) Hi.

          Client: FIN=0, opcode=0x1, msg="and a"

          Server: (listening, new message containing text started)

          Client: FIN=0, opcode=0x0, msg="happy new"

          Server: (listening, payload concatenated to previous message)

          Client: FIN=1, opcode=0x0, msg="year!"

          Server: (process complete message) Happy new year to you too!

          在以上示例中,客戶端向服務器發送了兩條消息。第一個消息在單個幀中發送,而第二個消息跨三個幀發送。


          其中第一個消息是一個完整的消息(FIN=1 且 opcode != 0x0),因此服務器可以根據需要進行處理或響應。而第二個消息是文本消息(opcode=0x1)且 FIN=0,表示消息還沒發送完成,還有后續的數據幀。該消息的所有剩余部分都用延續幀(opcode=0x0)發送,消息的最終幀用 FIN=1 標記。


          好的,簡單介紹了數據分片的相關內容。接下來,我們來開始實現消息通信功能。


          3.4 實現消息通信功能

          阿寶哥把實現消息通信功能,分解為消息解析與消息響應兩個子功能,下面我們分別來介紹如何實現這兩個子功能。


          3.4.1 消息解析

          利用消息通信基礎環節中介紹的相關知識,阿寶哥實現了一個 parseMessage 函數,用來解析客戶端傳過來的 WebSocket 數據幀。出于簡單考慮,這里只處理文本幀,具體代碼如下所示:


          function parseMessage(buffer) {

           // 第一個字節,包含了FIN位,opcode, 掩碼位

           const firstByte = buffer.readUInt8(0);

           // [FIN, RSV, RSV, RSV, OPCODE, OPCODE, OPCODE, OPCODE];

           // 右移7位取首位,1位,表示是否是最后一幀數據

           const isFinalFrame = Boolean((firstByte >>> 7) & 0x01);

           console.log("isFIN: ", isFinalFrame);

           // 取出操作碼,低四位

           /**

            * %x0:表示一個延續幀。當 Opcode 為 0 時,表示本次數據傳輸采用了數據分片,當前收到的數據幀為其中一個數據分片;

            * %x1:表示這是一個文本幀(text frame);

            * %x2:表示這是一個二進制幀(binary frame);

            * %x3-7:保留的操作代碼,用于后續定義的非控制幀;

            * %x8:表示連接斷開;

            * %x9:表示這是一個心跳請求(ping);

            * %xA:表示這是一個心跳響應(pong);

            * %xB-F:保留的操作代碼,用于后續定義的控制幀。

            */

           const opcode = firstByte & 0x0f;

           if (opcode === 0x08) {

             // 連接關閉

             return;

           }

           if (opcode === 0x02) {

             // 二進制幀

             return;

           }

           if (opcode === 0x01) {

             // 目前只處理文本幀

             let offset = 1;

             const secondByte = buffer.readUInt8(offset);

             // MASK: 1位,表示是否使用了掩碼,在發送給服務端的數據幀里必須使用掩碼,而服務端返回時不需要掩碼

             const useMask = Boolean((secondByte >>> 7) & 0x01);

             console.log("use MASK: ", useMask);

             const payloadLen = secondByte & 0x7f; // 低7位表示載荷字節長度

             offset += 1;

             // 四個字節的掩碼

             let MASK = [];

             // 如果這個值在0-125之間,則后面的4個字節(32位)就應該被直接識別成掩碼;

             if (payloadLen <= 0x7d) {

               // 載荷長度小于125

               MASK = buffer.slice(offset, 4 + offset);

               offset += 4;

               console.log("payload length: ", payloadLen);

             } else if (payloadLen === 0x7e) {

               // 如果這個值是126,則后面兩個字節(16位)內容應該,被識別成一個16位的二進制數表示數據內容大??;

               console.log("payload length: ", buffer.readInt16BE(offset));

               // 長度是126, 則后面兩個字節作為payload length,32位的掩碼

               MASK = buffer.slice(offset + 2, offset + 2 + 4);

               offset += 6;

             } else {

               // 如果這個值是127,則后面的8個字節(64位)內容應該被識別成一個64位的二進制數表示數據內容大小

               MASK = buffer.slice(offset + 8, offset + 8 + 4);

               offset += 12;

             }

             // 開始讀取后面的payload,與掩碼計算,得到原來的字節內容

             const newBuffer = [];

             const dataBuffer = buffer.slice(offset);

             for (let i = 0, j = 0; i < dataBuffer.length; i++, j = i % 4) {

               const nextBuf = dataBuffer[i];

               newBuffer.push(nextBuf ^ MASK[j]);

             }

             return Buffer.from(newBuffer).toString();

           }

           return "";

          }

          創建完 parseMessage 函數,我們來更新一下之前創建的 WebSocket 服務器:


          server.on("upgrade", function (req, socket) {

           socket.on("data", (buffer) => {

             const message = parseMessage(buffer);

             if (message) {

               console.log("Message from client:" + message);

             } else if (message === null) {

               console.log("WebSocket connection closed by the client.");

             }

           });

           if (req.headers["upgrade"] !== "websocket") {

             socket.end("HTTP/1.1 400 Bad Request");

             return;

           }

           // 省略已有代碼

          });

          更新完成之后,我們重新啟動服務器,然后繼續使用 “發送普通文本” 的示例來測試消息解析功能。以下發送 “我是阿寶哥” 文本消息后,WebSocket 服務器輸出的信息。


          Server running at http://localhost:8888

          isFIN:  true

          use MASK:  true

          payload length:  15

          Message from client:我是阿寶哥

          通過觀察以上的輸出信息,我們的 WebSocket 服務器已經可以成功解析客戶端發送包含普通文本的數據幀,下一步我們來實現消息響應的功能。


          3.4.2 消息響應

          要把數據返回給客戶端,我們的 WebSocket 服務器也得按照 WebSocket 數據幀的格式來封裝數據。與前面介紹的 parseMessage 函數一樣,阿寶哥也封裝了一個 constructReply 函數用來封裝返回的數據,該函數的具體代碼如下:


          function constructReply(data) {

           const json = JSON.stringify(data);

           const jsonByteLength = Buffer.byteLength(json);

           // 目前只支持小于65535字節的負載

           const lengthByteCount = jsonByteLength < 126 ? 0 : 2;

           const payloadLength = lengthByteCount === 0 ? jsonByteLength : 126;

           const buffer = Buffer.alloc(2 + lengthByteCount + jsonByteLength);

           // 設置數據幀首字節,設置opcode為1,表示文本幀

           buffer.writeUInt8(0b10000001, 0);

           buffer.writeUInt8(payloadLength, 1);

           // 如果payloadLength為126,則后面兩個字節(16位)內容應該,被識別成一個16位的二進制數表示數據內容大小

           let payloadOffset = 2;

           if (lengthByteCount > 0) {

             buffer.writeUInt16BE(jsonByteLength, 2);

             payloadOffset += lengthByteCount;

           }

           // 把JSON數據寫入到Buffer緩沖區中

           buffer.write(json, payloadOffset);

           return buffer;

          }

          創建完 constructReply 函數,我們再來更新一下之前創建的 WebSocket 服務器:


          server.on("upgrade", function (req, socket) {

           socket.on("data", (buffer) => {

             const message = parseMessage(buffer);

             if (message) {

               console.log("Message from client:" + message);

               // 新增以下

          停止犯下這5個JavaScript風格錯誤,使你的代碼可讀和可維護的快速提示

          seo達人

          使你的代碼可讀和可維護的快速提示。


          有多少次,你打開一個舊的項目,發現混亂的代碼,當你添加一些新的東西時,很容易崩潰?我們都有過這樣的經歷。


          為了減少難以讀懂的javascript的數量,我提供了以下示例。這些都是我過去所犯過的錯誤。


          對具有多個返回值的函數使用數組解構

          假設我們有一個返回多個值的函數。一種可能的實現是使用數組解構,如下所示:


          const func = () => {

           const a = 1;

           const b = 2;

           const c = 3;

           const d = 4;

           return [a,b,c,d];

          }

          const [a,b,c,d] = func();

          console.log(a,b,c,d); // 1,2,3,4

          盡管上面的方法很好用,但確實引入了一些復雜性。


          當我們調用函數并將值分配給 a,b,c,d 時,我們需要注意返回數據的順序。這里的一個小錯誤可能會成為調試的噩夢。


          此外,無法確切指定我們要從函數中獲取哪些值,如果我們只需要 c 和 d 怎么辦?


          相反,我們可以使用對象解構。


          const func = () => {

           const a = 1;

           const b = 2;

           const c = 3;

           const d = 4;

           return {a,b,c,d};

          }

          const {c,d} = func();

          現在,我們可以輕松地從函數中選擇所需的數據,這也為我們的代碼提供了未來的保障,允許我們在不破壞東西的情況下增加額外的返回變量。


          不對函數參數使用對象分解

          假設我們有一個函數,該函數將一個對象作為參數并對該對象的屬性執行一些操作。一種幼稚的方法可能看起來像這樣:


          // 不推薦

          function getDaysRemaining(subscription) {

           const startDate = subscription.startDate;

           const endDate = subscription.endDate;

           return endDate - startDate;

          }

          上面的方法按預期工作,但是,我們創建了兩個不必要的臨時引用 startDate 和 endDate。


          一種更好的實現是對 subscription 對象使用對象解構來在一行中獲取 startDate 和 endDate。


          // 推薦

          function getDaysRemaining(subscription) {

           const { startDate, endDate } = subscription;

           return startDate - endDate;

          }

          我們可以更進一步,直接對參數執行對象析構。


          // 更好

          function getDaysRemaining({ startDate, endDate }) {

           return startDate - endDate;

          }

          更優雅,不是嗎?


          在不使用擴展運算符的情況下復制數組

          使用 for循環遍歷數組并將其元素復制到新數組是冗長且相當丑陋的。


          可以以簡潔明了的方式使用擴展運算符來達到相同的效果。


          const stuff = [1,2,3];


          // 不推薦

          const stuffCopyBad = []

          for(let i = 0; i < stuff.length; i++){

           stuffCopyBad[i] = stuff[i];

          }


          // 推薦

          const stuffCopyGood = [...stuff];

          使用var

          使用 const 保證不能重新分配變量。這樣可以減少我們代碼中的錯誤,并使其更易于理解。


          // 不推薦

          var x = "badX";

          var y = "baxY";


          // 推薦

          const x = "goodX";

          const y = "goodX";

          果你確實需要重新分配變量,請始終選擇 let 而不是 var。


          這是因為 let 是塊作用域的,而 var 是函數作用域的。


          塊作用域告訴我們,只能在定義它的代碼塊內部訪問變量,嘗試訪問塊外部的變量會給我們提供ReferenceError。


          for(let i = 0; i < 10; i++){

           //something

          }

          print(i) // ReferenceError: i is not defined

          函數作用域告訴我們,只能在定義其的函數內部訪問變量。


          for(var i = 0; i < 10; i++){

           //something

          }

          console.log(i) // 10

          let 和 const 都是塊范圍的。


          不使用模板字面值

          手動將字符串連接在一起相當麻煩,而且輸入時可能會造成混淆。這是一個例子:


          // 不推薦

          function printStartAndEndDate({ startDate, endDate }) {

           console.log('StartDate:' + startDate + ',EndDate:' + endDate)

          }

          模板文字為我們提供了一種可讀且簡潔的語法,該語法支持字符串插值。


          // 推薦

          function printStartAndEndDate({ startDate, endDate }) {

           console.log(`StartDate: ${startDate}, EndDate: ${endDate}`)

          }

          模板文字也提供了嵌入新行的簡便方法,你所需要做的就是照常按鍵盤上的Enter鍵。


          // 兩行打印

          function printStartAndEndDate({ startDate, endDate }) {

           console.log(`StartDate: ${startDate}

           EndDate: ${endDate}`)

          }

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務

          如何推進多方合作的A/B測試項目?

          濤濤

          在多方合作的項目中,我們需要規劃項目的合理落地方案,并在執行過程中和各方有效溝通。結合近期羚瓏商家合作測試項目的實踐經歷,聊聊我在項目中的心得體會。

          項目背景

          我們想了解京東首焦banner中,設計因素對點擊效果的影響情況。做這件事最大的限制在于投放數據會受多種因素影響,不僅是一個設計因素的選擇,還有比如人群、出價、品牌、類目等因素的不同,都能影響到最終的投放結果。因此,從總體樣本中取樣進行分析的意義不大。為了解決上述問題,我們招募了一些能控制其他因素的品牌廣告主,與有能力支持投放的測試投放系統合作,共同完成A/B測試。

          需求拆解與規劃

          不難發現這個需求會涉及到多個項目角色,意味著將有復雜的推進流程,需要提前拆解與規劃,才能保證項目進度可控。

          如何推進多方合作的A/B測試項目?來看京東的實戰案例!

          △ 測試項目流程與分工

          我們可以分三步來進行:

          1. 找到項目關鍵步驟

          關鍵步驟是將一個項目的推進拆解成多個階段,找到關鍵步驟,也就知道了每個階段的主要任務,可以進一步分工來推進項目。

          如何找到關鍵步驟?我們可以從項目目標中提取。測試項目的目標是找到設計因素與投放點擊效果的影響關系。這個目標里提取重要的關鍵詞為:設計因素、投放點擊效果、影響關系。面對這些關鍵詞我們會有疑問產生如「測試設計因素是什么?有哪些?」、「如何投放」、「如何判斷影響關系」,再進行串聯梳理,得到完整步驟流程。

          2. 明確角色任務與產出

          每個階段的主要任務,可以拆解分工給項目角色,項目角色也就有了各自的任務,角色產出物則是任務執行的結果,將直接推動項目進入下一環節。所以為了順利推進項目,我們需要根據項目實際情況評估角色任務分工是否合理,產出是否滿足項目要求。

          比如在「設計測試圖片」這個步驟中,有兩個分工方案,讓廣告主或我們自己設計圖片。我們從項目時長、溝通成本、潛在風險等維度進行評估,判斷廣告主把控變量不嚴謹會導致測試數據無效,且外部合作溝通成本較高時間不夠,所以最后將設計圖片的任務分給了內部。

          3. 預判項目風險

          對流程規劃得越詳細,在后續合作過程中越容易把控項目的節奏,項目風險越低。

          再如「設計測試圖片」這個關鍵步驟,詳細展開執行流程會發現涉及到交互設計師、兩方視覺設計師、廣告主四個角色。對于項目執行角色,產出物的質量及按時交付尤為重要,所以重點把控輸出準確性與完成時間。對于項目決策角色,也就是決策最終投放哪些圖片的廣告主,溝通配合尤為重要。所以基于規避風險也對項目流程進行了優化,前置在項目之初,要求廣告主提前準備交付內容,規定交付時間及格式,以及充分溝通說明圖片設計產出之后不能進行較大改動。

          結論推導的重要前提與方法

          1. 保證數據有效

          測試結論來源于測試數據,我們首先應該保證回收數據的有效性。測試項目的關鍵指標是點擊率,即點擊量與曝光量的比值。當圖片本身曝光量低時,我們認為隨著曝光量增加,點擊率比值波動范圍仍然較大,數據還未穩定在某個區間段,會影響結論準確性,判定為無效數據。發現類似這種問題,我們會和廣告主商量繼續投放,延長測試時間來增加曝光。

          2. 充分測試

          要想得到可靠普適的結論,需要對比多組樣本的測試結果。對于某個設計因素,我們先進行了單一廣告主的投放數據對比,可以找到投放效果最優和最差的設計水平。然后又將多個廣告主的結果進行對比,會發現存在不一致的情況,驗證了單組樣本結論不能作為類目結論輸出。如果多個廣告主結果一致,或呈現某一趨勢,則結論在該類目可以認為較為普適。

          3. 差異分析

          對于多組樣本結果不一致的情況,可以從組間因素差異著手分析。

          如何推進多方合作的A/B測試項目?來看京東的實戰案例!

          △ 背景設計因素測試結果

          上圖是兩個廣告主分別測試背景設計因素得到的結果。第一組的投放結果為實景設計效果更好,第二組則為普通平面設計效果更好。產生這樣差異的原因是什么呢?我們先找出組間可能導致這一結果的因素,分別是顏色和產品。從顏色上看,是否平面+黑色效果更好呢?我們看了其他廣告主結果,否定了這一猜想。再從產品圖來看,兩個產品的識別度是不同的,我們將其他廣告主該因素測試圖按產品是否容易識別分組,最終得出兩組不同的結論:當產品圖易識別時,背景設計對效果影響不大;當產品圖不易識別時,實景圖效果更好。這也就解釋了上圖結果不一致的原因。

          回顧整個項目,我個人認為項目中最重要的工作是溝通。下面我來分別談談內外部溝通的經驗。

          雙贏是外部合作溝通的基石

          與外部團隊、廣告主合作推進項目,需要及時有效的溝通,什么是有效的溝通呢?

          1. 圍繞對方利益談配合

          廣告主只看轉化結果,我們如果只談設計不談轉化,廣告主是不會想要出錢參與項目的,那么項目也將停滯不前。所以在合作前,我們做了項目及團隊包裝,用真實的案例讓廣告主快速理解參與項目所能帶來的價值,并用團隊以往的作品、能力展示讓廣告主了解與我們合作的優勢。廣告主越認同項目價值及我們的專業度,配合度就會越高。

          如何推進多方合作的A/B測試項目?來看京東的實戰案例!

          △ 招募PPT中的真實案例介紹

          2. 圍繞對方目標來「推銷」方案

          在推進項目這一點上,找到目標一致的點更容易促成各方意見達成一致。

          我們合作的廣告主很多會選擇外包banner設計,所以廣告主們習慣了做傳說中的「甲方爸爸」,難免會對視覺設計方案有各種主觀意見。比如,某電視廣告主不喜歡紅色圖片設計,想要藍色。對于這樣的分歧,我個人喜歡用引導式的溝通方法。首先,不要急于表達自己或否定對方的意見,可以以疑問句式來猜測對方的目的?!赶矚g藍色是希望用沉穩的顏色來表現產品的高級感嗎?」,如果猜測正確,對方會因自己的需求被理解而更容易溝通,這時再繼續闡述用紅色如何表達高級感;如果猜測不正確,對方會順勢說出原因,我們再圍繞對方的目標進一步溝通。如果對方毫無理由「不喜歡這個設計」,而設計師并不覺得有設計問題,那么我們需要停止專業角度的溝通,從設計是否滿足當前階段項目需求的角度來溝通,方案則更容易被接受。

          內部合作各盡所能發揮優勢

          內部合作分交互、視覺、用研三種不同的崗位角色,在項目中都發揮著自己重要的作用。

          交互擅長統籌與拆解問題,團隊的內部溝通能產生多種解題思路,對項目的推進至關重要;在遇到如需要統計學分析、樣本設置等專業問題時,用研可以提供更專業的分析方法與幫助,為我們實現科學分析提供有力支持;視覺設計師對banner設計有豐富的經驗,可以在各個環節中發揮優勢。從項目之初就參與設計因素拆解、測試方案設置,可以補充視覺相關的設計因素,在優化測試方案、風險預期方面都可以提供重要的建議。在結論推導中,對圖中的因素差異敏感度高,可以根據數據提出關鍵猜想。

          文章來源:優設    作者:京東設計中心JDC

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務

          如何為你的UI制定一套色彩系統?

          濤濤

          色彩在UI設計中的作用:加深品牌印象與品牌感、引導用戶視覺凹增加易讀性、區分信息交互的狀態、營造氛圍傳遞熱度……

          前言

          不管是做 UI 設計還是畫插畫,有很多同學覺得自己是因為天賦不夠所以對色彩的敏感度不夠,其實不然。一個可能是大家總結的太少,從來都是憑感覺和運氣去配色,但配色都是有講究的。

          一個設計作品呈現到用戶面前,第一眼進入眼簾的就是產品的視覺表現,而產品的色彩在其中起到了舉足輕重的作用,毫無疑問色彩搭配對于設計師來說是非常重要的。那么具體到實際項目中該使用什么怎樣的色彩,需要怎么做呢?

          用戶界面是一個設計師用理性思維解決用戶感性需求的窗口。如果對色彩的運用不加以克制,界面可能會顯得花哨而沒有主次;但過于拘謹又容易使界面保守,難以激發用戶情緒,下面以Bee express項目的實例來理性推導制定一套色彩系統。

          切勿直奔主題

          做過設計的同學應該都知道顏色模式:RGB、CMYK、Lab 等等,這里不做過多的解釋了。另外每個顏色具有一定的性格特征和表達方式,而且都會有正反兩面。雖然每種色彩都有正向性格特征,但是我們在定位主體色之前一定要知道所選擇色彩的負面特征對企業是否會帶來負面的影響,

          開始之前我們需要了解在配色過程中需要避免出現的問題,如果你經常出現下列的問題,保證你在試用期內一次性就能拿到全部薪資,emmm……

          • 高飽和度的色彩會造成我們的視覺疲勞及視幻;
          • 灰部使用過多的配色會使界面有一種臟兮兮、霧蒙蒙的感覺,甚至心情低落;
          • 沒有規律且過多的配色。如果你不是做五彩斑斕的黑,建議6、3、1的色彩配比,輔助色不超過3種;
          • 熒光色。使用這種色彩的,建議跟色彩對視,看誰堅持的更久,除非是你贏了;
          • 太輕柔的顏色-沒有重點且輕飄飄的感覺;
          • 現在很火的新擬物化設計對于部分(沒有絕對)產品可能會造成信息識別性很差;
          • 不要將對抗色重疊,否則你會很浮躁。

          定位品牌色

          雖然設計是相通的,但是在不同的設計領域進行配色時,依然會存在巨大的區別。更換品牌的主體色,都不會是因為設計師自己的決定,而是公司在商業策略上優先做出了調整,然后通過品牌視覺上的變更將這個信息傳遞給消費者。

          Bee Express快遞、速遞柜業務為主,前期的主色及視覺形象以橙黃色為主,為了避免視覺跳躍性太大,以及后期IP形象(蜜蜂吉祥物)打造,本次品牌色彩升級在原有基礎上優化了色調,以保證后期產品的易用性和延展性,并利用最科學、最適用的方式推導出輔助色,以提升應用視覺的豐富性和感官體驗。

          express原主色:

          如何為你的UI制定一套色彩系統?來看這個實戰案例!

          為了不影響原有色調前期的視覺傳播,即在原有主體色的基礎上調整SHB的數值,讓色彩更具視覺沖擊力,在色彩襯托(字體、圖標)更清晰。

          • H(Hue:色相)
          • S(Saturation:飽和度)
          • B(Brightness:明度)

          如何為你的UI制定一套色彩系統?來看這個實戰案例!

          通過調整后的主體色也能看出,明亮清晰的主體色(品牌色)也更適合在界面中的運用,為信息傳遞、引導操作、品牌價值帶來更大的提升。

          • 信息傳遞:產品的首要目的是傳遞用戶所需要的信息,這就需要界面有清晰的層級關系,明確、舒適的閱讀體驗。
          • 引導操作:清晰合理的操作引導,讓用戶能夠準確地根據引導進行下一步操作。
          • 品牌價值:很多同學會忽略這一點,導致產品的界面與品牌關聯性差,整體界面沒有品牌感。

          根據主體色推理同色系

          同色系為統一的色相,使用中可以加深品牌色的感知,可以讓界面更有層次,同時可以讓界面保持色彩上的一致性,整體感較強,產生低對比度的和諧美感,給人協調統一的感覺。

          具體是指與品牌色 H(色相)一致,通過改變 S(飽和度)與 B(明度)變化產生的色組。分別往淺色/深色方向按均勻數據增減,各產生5個坐標值。

          如何為你的UI制定一套色彩系統?來看這個實戰案例!

          綜上能看出,使用同一色系即可完成一個項目,但是對于中大型項目來說實在是過于單調,沒有太多的層次感,因此我們需要多色搭配為輔。多色的輔助顏色可設定不同的任務屬性和情感表達,再搭配中性色黑白灰,能賦予更多的變化和層次。

          提取24色-鋪墊輔助色

          根據主體色 H(色相)為基礎,不斷地遞增、遞減 15,在 0-360 之間可以得出 24 個顏色,也就是將 360° 色環分割為 24 份,(24份在360°色環上,每一個色相的角度為15°),最終得到下圖24色。

          如何為你的UI制定一套色彩系統?來看這個實戰案例!

          選取輔助色

          輔助色需要滿足的兩個條件:

          和品牌色有明顯區分:避免所選輔助色感官上給用戶視覺區別與品牌色差距不大,傳遞的調性太過一致;

          不能過于突兀:根據色彩原理,互補色是最能與品牌色本色產生視覺感官對比的顏色,但可能會有些突兀。為了讓顏色的輔助起到豐富畫面的作用,而不是反而讓整個版面顯得不和諧,所以選擇互補色的鄰近色作為輔助色,避免直接使用互補色。

          • 鄰近色:色相差值 15° 以內的顏色為鄰近色;
          • 類似色:色相差值 30° 以內的顏色為類似色;
          • 互補色:色相差值 180° 的顏色為互補色。

          基于品牌色可衍生出 3 個輔助色:一個與品牌色傳遞調性有明顯區分的類似色;兩個互補色的鄰近色。

          如何為你的UI制定一套色彩系統?來看這個實戰案例!

          類似色搭配:使用色相相近的顏色,頁面元素不會相互沖突,更加協調有質感。

          互補色搭配:選擇使用互補色,最佳搭配是一種作為主色,另一種用于強調。它們有著非常強烈的對比度,用在需要特別強調某個元素時會非常有效。

          視覺統一感官校準

          每一種顏色都有自己的「感官明度」,也就是發光度。根據現有的使用場景,類似色和互補色大都用在同層級的信息展示上,而當我們將最終得到的輔助色擺放在一起之后發現,雖然我們提取出的輔助色明度色值都一致,因為顏色本身自帶的感官明度屬性有所區別,導致視覺上會有明顯的明暗差別。需要通過發光度來進行最終的顏色校正。

          校準方式:依次在輔助色上疊加一層純黑圖層,將該純黑圖層顏色模式調整為 Hue(色相),就可以通過無彩色系下的明度色值,進行對比,使色彩視覺感官保持一致(青色和藍色屬冷色調,固需加深)。

          如何為你的UI制定一套色彩系統?來看這個實戰案例!

          全色系輸出

          根據上面同色系的明度、純度對比規則,對所有定義的輔助色進行明度和純度的輔助色彩輸出,最終得到輔助色色板。H(色相)一致,通過改變 S(飽和度)與 B(明度)變化產生色組。分別往淺色/深色方向按均勻數據增減,各產生5個坐標值

          如何為你的UI制定一套色彩系統?來看這個實戰案例!

          刪除最左側的3種同色系,因明度過低時,顏色已經非常接近于黑色,色相在肉眼上幾乎已經趨于一致。最后得到基于品牌色推導出的全色系色階色板。

          如何為你的UI制定一套色彩系統?來看這個實戰案例!

          如何為你的UI制定一套色彩系統?來看這個實戰案例!

          總結

          配色常常從確定主色開始,根據行業類型和視覺訴求的需要,選擇一種居于支配的色彩作為主調色彩,構成畫面的整體色彩傾向。然后選擇輔色,添加點綴色,最后按照色彩組合的原則完成設計中的需求。

          雖然有了以上的配色方式,但一套標準的色彩系統還會包含中性色規范、顏色的使用規范等等。相信解決了大部分的需求,剩下的工作也難不倒大家了。畢竟以上的方式只是給大家提供了一個理性科學的方法,如果想更加優秀,還需要進一步深入地去學習色彩理論知識,多看優秀的配色作品提升審美,總之要多看、多實踐和多思考。

          文章來源:優設    作者:能量眼球

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務

          你可能不需要在 JavaScript 使用 switch 語句!

          seo達人

          沒有 switch 就沒有復雜的代碼塊

          switch很方便:給定一個表達式,我們可以檢查它是否與一堆case子句中的其他表達式匹配。 考慮以下示例:


          const name = "Juliana";


          switch (name) {

           case "Juliana":

             console.log("She's Juliana");

             break;

           case "Tom":

             console.log("She's not Juliana");

             break;

          }

          當 name 為“Juliana”時,我們將打印一條消息,并立即中斷退出該塊。 在switch函數內部時,直接在 case 塊使用 return,就可以省略break。


          當沒有匹配項時,可以使用 default 選項:


          const name = "Kris";


          switch (name) {

           case "Juliana":

             console.log("She's Juliana");

             break;

           case "Tom":

             console.log("She's not Juliana");

             break;

           default:

             console.log("Sorry, no match");

          }

          switch在 Redux reducers 中也大量使用(盡管Redux Toolkit簡化了樣板),以避免產生大量的if。 考慮以下示例:


          const LOGIN_SUCCESS = "LOGIN_SUCCESS";

          const LOGIN_FAILED = "LOGIN_FAILED";


          const authState = {

           token: "",

           error: "",

          };


          function authReducer(state = authState, action) {

           switch (action.type) {

             case LOGIN_SUCCESS:

               return { ...state, token: action.payload };

             case LOGIN_FAILED:

               return { ...state, error: action.payload };

             default:

               return state;

           }

          }

          這有什么問題嗎?幾乎沒有。但是有沒有更好的選擇呢?


          從 Python 獲得的啟示

          來自 Telmo 的這條 Tweet引起了我的注意。 他展示了兩種“switch”風格,其中一種非常接近Python中的模式。


          Python 沒有開關,它給我們一個更好的替代方法。 首先讓我們將代碼從 JavaScript 移植到Python:


          LOGIN_SUCCESS = "LOGIN_SUCCESS"

          LOGIN_FAILED = "LOGIN_FAILED"


          auth_state = {"token": "", "error": ""}



          def auth_reducer(state=auth_state, action={}):

             mapping = {

                 LOGIN_SUCCESS: {**state, "token": action["payload"]},

                 LOGIN_FAILED: {**state, "error": action["payload"]},

             }


             return mapping.get(action["type"], state)

          在 Python 中,我們可以使用字典來模擬switch 。 dict.get() 可以用來表示 switch 的 default 語句。


          當訪問不存在的key時,Python 會觸發一個 KeyError 錯誤:


          >>> my_dict = {

             "name": "John",

             "city": "Rome",

             "age": 44

             }


          >>> my_dict["not_here"]


          # Output: KeyError: 'not_here'

          .get()方法是一種更安全方法,因為它不會引發錯誤,并且可以為不存在的key指定默認值:


          >>> my_dict = {

             "name": "John",

             "city": "Rome",

             "age": 44

             }


          >>> my_dict.get("not_here", "not found")


          # Output: 'not found'

          因此,Pytho n中的這一行:


          return mapping.get(action["type"], state)

          等價于 JavaScript中的:


          function authReducer(state = authState, action) {

           ...

             default:

               return state;

           ...

          }

          使用字典的方式替換 switch

          再次思考前面的示例:


          const LOGIN_SUCCESS = "LOGIN_SUCCESS";

          const LOGIN_FAILED = "LOGIN_FAILED";


          const authState = {

           token: "",

           error: "",

          };


          function authReducer(state = authState, action) {

           switch (action.type) {

             case LOGIN_SUCCESS:

               return { ...state, token: action.payload };

             case LOGIN_FAILED:

               return { ...state, error: action.payload };

             default:

               return state;

           }

          }

          如果不使用 switch 我們可以這樣做:


          function authReducer(state = authState, action) {

           const mapping = {

             [LOGIN_SUCCESS]: { ...state, token: action.payload },

             [LOGIN_FAILED]: { ...state, error: action.payload }

           };


           return mapping[action.type] || state;

          }

          這里我們使用 ES6 中的計算屬性,此處,mapping的屬性是根據兩個常量即時計算的:LOGIN_SUCCESS 和 LOGIN_FAILED。

          屬性對應的值,我們這里使用的是對象解構,這里 ES9((ECMAScript 2018)) 出來的。


          const mapping = {

           [LOGIN_SUCCESS]: { ...state, token: action.payload },

           [LOGIN_FAILED]: { ...state, error: action.payload }

          }

          你如何看待這種方法?它對 switch 來說可能還能一些限制,但對于 reducer 來說可能是一種更好的方案。


          但是,此代碼的性能如何?


          性能怎么樣?

          switch 的性能優于字典的寫法。我們可以使用下面的事例測試一下:


          console.time("sample");

          for (let i = 0; i < 2000000; i++) {

           const nextState = authReducer(authState, {

             type: LOGIN_SUCCESS,

             payload: "some_token"

           });

          }

          console.timeEnd("sample");

          測量它們十次左右,


          for t in {1..10}; do node switch.js >> switch.txt;done

          for t in {1..10}; do node map.js >> map.txt;done

          clipboard.png


          人才們的 【三連】 就是小智不斷分享的最大動力,如果本篇博客有任何錯誤和建議,歡迎人才們留言,最后,謝謝大家的觀看。


          原文:https://codeburst.io/alternat...


          代碼部署后可能存在的BUG沒法實時知道,事后為了解決這些BUG,花了大量的時間進行log 調試,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug。

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務



          vuex管理狀態倉庫詳解

          seo達人

          一.什么是Vuex?

          Vuex 是一個專為 Vue.js 應用程序開發的狀態管理模式。它采用集中式存儲管理應用的所有組件的狀態,并以相應的規則保證狀態以一種可預測的方式發生變化。Vuex 也集成到 Vue 的官方調試工具 devtools extension,提供了諸如零配置的 time-travel 調試、狀態快照導入導出等高級調試功能。采用了全局單例模式,將組件的共享狀態抽離出來管理,使得組件樹中每一個位置都可以獲取共享的狀態或者觸發行為。
          那么什么是狀態呢?我把狀態理解為在沒有使用vuex時,在當前組件中data內需要共用的數據為狀態。
          vuex使得狀態或行為成為了共享的狀態,所共享的狀態或行為可以在各個組件中都可以訪問到,省去了子父或子子之間傳遞變量,提高了開發效率。

          二.不使用vuex時與使用vuex時的差別

          當我們不使用vuex時,對于組件之間傳遞信息會較為麻煩。

          不使用vuex時

          父子之間傳遞信息:

          App.vue文件中:

          <template>
            <div id="app">
                <Fruits :fruitList="fruitList"/>
            </div>
          </template> 
          <script> import Goods from './components/Goods'; export default { name: 'App',
            components:{
              Fruits,
              Goods
            }, data(){
              return{ goodList:[
                {
                  name:'doll',
                  price:12 },
                { name:'glass',
                  price:10 }
              ],
              }
            }
          }
          </script>
          <style>
          </style>

          Good.vue文件中:

          <template>
            <div class="hello">
                <ul>
                  <li v-for="(good,index) in goodList" :key="index"> name:{{good.name}} number: {{good.number}} {{index}}
                  </li>
                </ul>
            </div>
          </template>
          
          <script> export default { props:['goodList'],
          }
          </script>
          <style>
          
          </style>

          兄弟之間傳遞信息:

          首先先創建一個js文件作為兩兄弟之間傳輸的紐扣,這里起名為msg.js

          //創建并暴露vue import Vue from 'vue';
          export default new Vue

          兄弟組件Goods:

          <template>
            <div>
                  <button @click="deliver">點擊</button>
            </div>
          </template>
          
          <script> import MSG from '../msg';
          export default {
            data(){ return{
                msg:'hahah' }
            },
            methods:{
              deliver() {
                  MSG.$emit('showMsg',this.msg)
              }
            }
          
          }
          </script>
          <style>
          
          </style>

          兄弟組件Fruits:

          <template>
            <div>
                <div class="fruit">
                    {{text}}
                </div>
            </div>
          </template>
          <script> import MSG from '../msg';
          export default {
              data(){ return{
                  text:'' }
              },
              created(){ this.getMsg()
              },
              methods:{
                getMsg(){
                  MSG.$on('showMsg',(message)=>{ this.text = message
                  })
                }
              }
          }
          </script>
          <style>
          </style>

          在App組件中的代碼:
          在這里插入圖片描述
          點擊按鈕:


          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務

          Tubik Studio 是怎么為華為定制整套 UI 圖標的?

          濤濤

          一套 UI 界面當中,核心的 APP 圖標是用戶每天都要接觸、經常使用的視覺組件和交互對象。設計圖標的時候,要用到大家最熟悉的元素才能貼合用戶認知,要醒目、統一,也要足夠「隱形」,避免喧賓奪主。今天這篇文章,來自著名的 Tubik Studio 團隊,他們為華為旗下的 EMUI 10 系統設計了核心的圖標系統,來看看他們的設計過程吧。

          Tubik Studio 是怎么為華為定制整套 UI 圖標的?

          我們總不會低估一個操作系統基礎圖標,對于產品的可用性和合意性的影響?;A圖標雖然小巧,但是對于整個操作系統而言,總是極具影響力的,因為他們是用戶界面的核心元素,幫助用戶快速直觀地在系統中定位、瀏覽、導航。但是,對于設計師而言,圖標的設計始終是挑戰,它看起來最為簡單,但實則為最為艱難的工作。

          圖標需要,讓人一目了然,但是也要具備良好的可識別性,在傳統和創新之間達到平衡。這一次,Tubik Studio 設計團隊將要給華為的 EMUI 10 來設計圖標,而這篇文章將會為你揭示背后的設計過程。

          這次的項目主要是由 Sergii Valiukh 、Arthur Avakyan 和 Polina Taran 來負責。這次的設計項目從最初的探索構思入手,逐漸開始繪制草圖,探索樣式,一直到最后打磨,完成設計。

          Tubik Studio 是怎么為華為定制整套 UI 圖標的?

          項目內容

          為華為 EMUI 10 系統的用戶界面設計基礎的圖標

          客戶介紹

          我們在 2019 年夏季,收到了來自華為的邀約,這次的項目要重新設計 EMUI 這套基于 Android 系統的用戶界面基礎圖標,這套圖標會用來適配華為旗下的旗艦手機,這些圖標將會隨著新版的系統部署到這些手機產品當中。我們的任務,是創建總計 54 款符合當下潮流趨勢的圖標,這些圖標要能夠貼合品牌和已有用戶的偏好,并且能夠吸引新的用戶。

          這個圖標設計項目如今已經在當下的華為旗下手機產品中應用且部署好了,最早使用這套圖標的智能手機型號為 Mate 30 ,緊接其后的是 P40。

          Tubik Studio 是怎么為華為定制整套 UI 圖標的?

          設計過程

          在整個操作系統中,這些圖標是始終位于用戶視野以內、最基礎的最核心的交互元素,通常用戶每天都會同這些核心的基礎 APP 進行交互,有時候一天多達幾十次,因此它們應當具備良好的功能性,還應該足夠美觀,給用戶帶來滿足感。同時,這套圖標的設計,也應當足夠統一,以協調的體驗切入到整個 EMUI 的設計系統當中,貼合整體的樣式特征。

          所以,我們使用了一整套圖標網格系統,應對不同需求,在設計的過程中,這套網格有助于確保所有圖標外部尺寸的一致性,也保證了內部元素的統一性。

          Tubik Studio 是怎么為華為定制整套 UI 圖標的?

          為了發掘全新的視角,我們決定從傳統的手繪圖標開始,尋找重新塑造圖標設計的方法。這些圖標所涉及到的元素,早已為全球數百萬用戶所熟知,要重新設計圖標外觀,并且還要成套且統一,這本身就是一個巨大的挑戰。

          Tubik Studio 是怎么為華為定制整套 UI 圖標的?

          比如「電話」圖標所對應的聽筒元素、「信息」圖標所對應的氣泡對話框這樣的元素,是不可能完全拋棄重新創造的,所以我們的真正的切入點是在形態和色彩上尋求解決方案。

          越是簡單的東西,重塑起來就越難。

          在實際的設計過程中,我們嘗試了數以百計的造型變體,從完全放飛、非常規的的外部造型,到極其常規,大家熟知的造型解決方案,我們都逐一試過。而在色彩上的嘗試相對會顯得更加具有實驗性:我們嘗試使用明亮的紫羅蘭色、栗色和淺綠色來進行混搭。

          當然,我們很清楚,這樣的實驗性的工作是探索過程中的一小步,但是它是必須的,是創造新設計的種子,是尋求正確答案的必經之路。

          Tubik Studio 是怎么為華為定制整套 UI 圖標的?

          在設計過程在,有一件有趣的事情發生在設計「相機」圖標的過程中。我們嘗試過很多不同的圖標造型,不同的元素,不同的樣式,但是其中始終有一個細節是不變的,那就是右上角的小紅點。這是為了暗示用戶,華為的攝像頭模組來自徠卡,這個紅色的小點就是向其致敬的標識。

          Tubik Studio 是怎么為華為定制整套 UI 圖標的?

          而下面的概念設計,則是強化了圖標之間的幾何輪廓的差異,從而提升圖標在智能手機屏幕上的對比度和識別度。

          Tubik Studio 是怎么為華為定制整套 UI 圖標的?

          下一步,我們基于幾何圖形外觀差異性,設計了多種造型不同但同樣優雅的圖標。在基于這種風格概念進行延伸的過程中,我們會優先考慮圓形的元素。而「天氣」圖標明顯既不適合圓形也不適合方形來呈現,所以我們使用的是云和太陽兩種元素的組合?!稿X包」圖標使用的是矩形,然后搭配卡片的組合。盡管造型整體上是相對自由的,但是所有的圖標都是遵循圖標網格,并且在視覺權重上盡可能統一。

          Tubik Studio 是怎么為華為定制整套 UI 圖標的?

          在色彩和樣式上,我們則更加傾向于漸變。沒有色彩漸變,純扁平的圖標顯得過于幼稚和「古早」,沒有足夠的品質感,所以,我們在新的圖標設計上,開始加入漸變色彩,提升質感。

          Tubik Studio 是怎么為華為定制整套 UI 圖標的?

          不過,在最終版本當中,我們還是保留了圖標外部的圓角矩形的外輪廓,然后將圖標元素的內徑進行了適當地縮減,漸變和核心的簡約幾何特征依然是整套設計的最高優先級。

          Tubik Studio 是怎么為華為定制整套 UI 圖標的?

          這套設計方案展現了在整套 UI 界面中,圖標這個小元素的設計上所需要投入的精力和潛在的難度。想要圖標足夠協調、美觀、易用還要貼合品牌特征、還要區別于以往,是一件相當不容易的事情。

          Tubik Studio 是怎么為華為定制整套 UI 圖標的?

          細節里藏著魔鬼,任何細小的元素、線條輪廓、色彩的變化都可能會在屏幕上放大、被感知到。

          文章來源:優設    作者:Tubik Studio

          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務

          日歷

          鏈接

          個人資料

          藍藍設計的小編 http://www.syprn.cn

          存檔

          亚洲va欧美va天堂v国产综合