聊聊雙11互動主動法中前端技術亮點

大漠_w3cplus發表於2018-11-14

在上一篇《手淘Web頁面Bar和縱向適配的設計》中聊了互動專案中Bar的工業化標準設計以及對劉海裝置帶來的變化。我把這一點稱為標準化Bar設計給適配帶來的優勢。今天這篇文章中主要想再花點時間聊聊今年手淘“PK贏能量”互動專案中前端有哪些技術亮點和嚐鮮。當然文章涉及到的技術點估計有很多同學都有接觸或者使用過。畢竟CSS僅僅是一門表現層語言。廢話不多說,直接進入主題吧!

面對場景

估計有很多同學已經參與“PK贏能量”互動遊戲中,但還是花一點點時間聊一下技術面對的場景(指的是CSSer),這樣更好開始講述我們今天的故事。

大家不難發現,為了營造雙十一過節的氛圍,設計往往都是非常熱情,奔放的。顏色多,顏色豔等等。這對於Web開發人員是件頭痛的事情,為什麼頭痛呢?比如一個開發面對這些場景時:

undefined

邊框是漸變的都算了,還是七彩的,還是七彩的,想哭!

undefined

上圖的場景相對而言比第一張要簡單多了,但對於帶陰影的Tooltips,還是令人生畏的,特別還是帶漸變的三角。

一個破提示框,除了帶陰影都算了,小三角還是不規則的!暱瑪,不規則都算了,還有漸變!還讓不讓前端活!估計此時前端對視覺設計師應該是這樣的:

undefined

其實前端對視覺還是很有愛的,因為前端耐操。

undefined

既然都這麼皮實了,那就再擼一發,順便用程式碼把下面的也解了:

undefined

圖片能省就省嘛,這些都是錢!說不定老闆一高興,給你加薪晉級!

undefined

其實我們是一群有追求的人,越難的事情,對我們來說越具挑戰性,越有那麼一種衝動。

undefined

誰說不是呢?這叫作!人家說不作死不會死!

其實類似上面的場景,對於互動團隊的同學而言是家常便飯,見怪不怪了!而往往面對這樣的場景,大家第一意識形態就是我用圖片解決一切!現在誰還差那麼一點頻寬呢?包個月,幾十G來了,解決一切

誰說不是呢?但很多時候用圖片也有用圖片難處:

  • 難適應產品多變的需求
  • 難擴充套件,總不可能備好成千上萬種尺寸的圖吧
  • 難維護,這麼多圖,哪是用哪
  • 浪費資源
  • 影響效能
  • 等等… 想到了再繼續加

我還是想打破一下規矩,藉著雙十一大促的活動來驗證一些技術點。因為:

扛得住雙十一的,還有什麼不能扛呢?以後可以說,我們絕對耐操!

漸變邊框

我把七彩的邊框稱為漸變邊框,這樣顯得更為專業一點。通過設計圖,不難發現,邊框採用了漸變顏色,如下圖所示:

undefined

對CSS瞭解的同學,要實現類似這樣的漸變邊框效果,首先會想的是CSS的 border-image 屬性。的確如此,我首先想的也是該屬性,而且該屬性可以很容易實現類似的效果,比如:

.gradient-border{
    border: 5px solid transparent;
    border-image: linear-gradient(to bottom, #0099CC, #F27280);
    border-image-slice: 1;
}

效果如下:

undefined

雖然border-imagelinear-gradient()配合在一起,能實現漸變的邊框效果,但它也有一定的缺陷性,比如我們專案中的按鈕是帶圓角的。那麼對於這種情形,就算是你使用了border-radius也是無用:

undefined

這是因為border-image中引用的是一張不帶圓角的圖片(linear-gradient()就相當於一張背景圖)。也就是說,如果你需要一個帶圓角的漸變邊框,那麼使用border-image是有侷限性的,除非人肉為其準備帶圓的背景圖,或者有更好的辦法通過程式碼繪製一個帶圓角的背景圖~

此路似乎在這個專案中行不通,只能考慮換用別的方法。仔細一想,我可以把帶有漸變邊框的元素分成兩層:

這樣一來似乎要容易的多了,一層一個元素:

<div class="gradient-border">
      <div class="content"></div>
</div>

甚至我們還可以通過偽元素::before::after來模擬一個層。比如下面這個示例:

.gradient-border {
    --borderWidth: 5px;
    border-radius: var(--borderWidth);
    background: #fff;

    &::before {
        content: ``;
        position: absolute;

        top: calc(-1 * var(--borderWidth));
        left: calc(-1 * var(--borderWidth));
        height: calc(100% + var(--borderWidth) * 2);
        width: calc(100% + var(--borderWidth) * 2);
        background: linear-gradient(60deg, #f79533, #f37055, #ef4e7b, #a166ab, #5073b8, #1098ad, #07b39b, #6fba82);
        border-radius: calc(2 * var(--borderWidth));
        z-index: -1;
    }
}

效果如下:

undefined

是不是感覺越來越接近設計稿了:

undefined

加個元素或加個偽元素總是那麼的不舒服,那怎麼辦呢?有沒有更好的方案。其實CSS的世界是很有魅力的,只要你敢去想,有很多東西你意想不到。

既然可以分成元素層讓兩個漸變或兩張圖疊加在一起做一個差值,從而實現效果。那麼為什麼不可以直接在背景中採用兩個層(兩張背景圖)疊加在一起

這是一個很好的方案,也是一個大膽的思路。到目前為止,CSS的多背景方案已經是一個很成熟的技術方案。這樣一來,我們們就可以在background來做我們想要的效果了:

.gradient-border{
    background-image: 
        radial-gradient(circle at 50% 0%, #fff000 50%,#ffcd00 100%),
        linear-gradient(101deg, #ffc46d, #fa0055);
    background-origin: border-box;
    background-clip: padding-box, border-box;
}

每一個關鍵點的顏色我們都可以在設計稿中獲取:

undefined

很多時候都不用這麼複雜,如果你的稿子是Sketch的話,就更簡單了,你可以直接從設計稿中把相應的樣式複製過來。這也就是我為什麼喜歡Sketch的原因。

就是這麼的簡單,效果出來了,只需要設計師點個頭了:

undefined

點選這裡可以檢視Demo的原始碼

看上去很簡單吧!不知道你是否有想到過這樣的方案?方案雖然簡單,但這裡有幾個點需要特別的強調:

  • 運用多背景時,第一個背景的層級最高,顯示在最前面
  • background-origin設定為border-box
  • background-clip設定為padding-box(也可以是content-box),但模擬邊框的部分需要是border-box

這裡最為關鍵的就是 background-originbackground-clip 靈活的配合在一起使用。至於這兩個屬性如何使用,就不在這裡科普了,感興趣的同學可以自己去查閱相關文件。

我們們進一步思考一下,如果有一天,設計師或者需求方想要的效果不是規則圖形,或者說想要的漸變邊框能帶動效的。會不會繼續讓我們矇蔽呢?在CSS中,雖然animationtransition能讓你的元素動起來,而且效果還能不錯,但在CSS中的動畫也是有一定的侷限性的,到目前為止,很難在背景影像(這裡說的是gradient相關屬性繪製的背景圖)做動畫。

簡單地說,使用animationtransition很難改變漸變的狀態。(但藉助CSS Houdini還是可以做到的)。這已經超出我們今天這篇文章探討的範圍。我們還是回到今天的主題上來。

為了儘量的滿足設計師的需求,就算是不規則的漸變邊框或者讓你的漸變邊框動起來,我們也要想辦法。這裡給大家推薦一個未來的CSS特性:clip-path 。這個屬性到目前為止已經得到近80%主流瀏覽的支援。在不久的未來,而對這樣的需求,我們就可以很輕易的實現。比如:

background: linear-gradient(120deg, #00F260, #0575E6, #00F260);
  background-size: 300% 300%;
  clip-path: polygon(
    0% 100%, 
    3px 100%, 
    3px 3px, 
    calc(100% - 3px) 3px, 
    calc(100% - 3px) calc(100% - 3px), 
    3px calc(100% - 3px), 
    3px 100%, 
    100% 100%, 
    100% 0%, 
    0% 0%);

甚至你配上animation可以讓你的漸變邊框動起來:

可以點選這裡檢視Demo原始碼

你也可以藉助Clippy工具,繪製你想要的不規則圖形,再把漸變運用上去,可以得到很多不規則的漸變邊框效果:

undefined

可以點選這裡檢視Demo原始碼

當然,clip-path也有他的侷限性,因為其目前支援的繪製圖形的函式有限,只有polygon()circle()ellipse()rect()等。如果要繪製類似我們設計稿中的按鈕,還是無法達到目標的。

提示框

對於提示框的繪製,其實沒有啥技術含量在裡面,這裡較為蛋疼的是,如查提示框是帶有陰影的,那麼處理起來還是有一些細節的。

把提示框的陰影拆分出來,藉助::before::after的偽元素來模擬box-shadow。另個三角通過一個矩形旋轉來處理:

undefined

通過一個小動畫來回放提示框陰影效果的處理:

可以點選這裡檢視Demo原始碼

上面看到的效果僅僅是純色的,很多時候背景色是漸變的。我們來看一個簡單的小示例,看看漸變的是如何做出來的:

undefined

可以點選這裡檢視Demo原始碼

再一次動畫回放一下實現原理:

此處有一個除錯小細節,就是三角的顏色和主體漸變色的連線,如果從設計稿上不好獲取的話,可以通過瀏覽器除錯工具中的顏色拾得器獲取,像下面這樣操作,拾起主體連線處顏色。

對於陰影的處理,除了box-shadow屬性之外,還可以使用filter:drop-shadow或者配合filter: blur相關屬性也能得到較好的陰影效果。

有關於CSS中的陰影處理的細節,網上有一篇文章介紹得非常詳細,值得花時間閱讀一下

或許很多同學會問,為什麼要用偽元素來做陰影呢?直接在元素上使用box-shadow不就可以?如果你仔細看了上面動畫,不難發現,如果陰影直接在元素上使用box-shadow的話,對於小三角形的陰影是較難處理的。除此之外還有一個最為關鍵的原因,如果在陰影上想做點小動效的話,那麼會有效能問題存在。因為box-shadow的動畫變化會損害效能 。如果要實現最小的重新繪製,應該建立一個偽元素並對其opacity元素進行動畫處理,使其以每秒60幀的動畫模仿運動物體相同的效果。比如像下面這樣使用:

/* 設定更大的陰影並將之隱藏 */ 
.make-it-fast::after { 
    box-shadow: 0 5px 15px rgba(0,0,0,0.3); 
    opacity: 0; 
    transition: opacity 0.3s ease-in-out: 
} 
/* 滑鼠懸停時實現更大陰影的過渡顯示 */ 
.make-it-fast:hover::after { 
    opacity: 1;
}

來看個效果對比:

認真觀察這個例項,比較我們在其中使用的不同技巧。你是不是會說兩者效果看起來一樣。唯一不同的是我們如何應用陰影並對其進行動畫處理。在左邊例項中,我們滑鼠hover(懸浮)時,對box-shadow應用了動畫效果。而在右邊的例項中,我們用::after新增了一個偽元素並對其設定了陰影,並對該元素的opacity元素進行了動畫處理。

如果你使用開發工具嘗試了其中之一,您應該會看到類似這樣的東西 (綠色條表示已經繪製,其越少越好):

undefined

當你懸停在左邊的卡片(在box-shadow上應用動畫)與懸浮在右邊的卡片(對其偽元素的opacity應用動畫)進行相比時,你會很明顯的發現有更多的重新繪製。

上面看到的提示框都是有規則的,但有的時候,我們的提示框下面不是一個小三角形,是其他的形狀,比如像下圖這樣的:

undefined

而對這樣的效果,使用CSS繪製是需要有一定耐力的,只是耗時間,但實現原理並不複雜,最簡單的就是借::before::after繪製兩個不同的矩形疊加在一起,然後使用border-radius讓形狀看起來像我們想要的效果。這裡就不貼程式碼了,感興趣的同學,自己可以動手嘗試一下,或者閱讀@Nicolas Gallagher大神早期寫的一篇部落格《Pure CSS speech bubbles》。

繪製圖形

CSS現在的具備的能力越來越強大,時至今日,我們專案中的很多東西都可以直接通過程式碼來完成,比如下圖中這些東東:

undefined

設計稿中還有一些,上圖沒有全部羅列出來,這裡抽幾個典型的案例來說。

對於電商行業而言,優惠卷是必不可少的一個東東。我們每次做互動專案,涉及到獎品的時候,都會離不開這個東東。

undefined

上圖是不是感覺很眼熟。

以往實現這個效果,很多時候都是直接採用背景圖片來做的。這次不同,同樣採用程式碼來完成背景圖相關的事項。而且這個背景圖是帶有漸變的。程式碼和原理都非常的簡單:

div {
    min-width: 702px;
    min-height: 160px;
    border-radius: 12px;
    background-image:linear-gradient(to bottom, #FF2655 0%, #FF4F26 100%), linear-gradient(to right, #fff, #fff);
    background-size: 210px 100%, cover;
    background-repeat: no-repeat;
    background-position:right center;
    position: relative;

    &::before,
    &::after {
        content: ``;
        position: absolute;
        width: 20px;
        height: 20px;
        background: radial-gradient(circle, #6c00af 50%, transparent 50%),
        radial-gradient(circle, #6c00af 50%, transparent 50%);
      background-size: 20px 20px;
        right: 200px;
    }
    &::before {
        top: -10px;
    }
    &::after {
          bottom: -10px;
    }
}

執行上面的程式碼,你看到的效果將會是像下圖:

undefined

點選這裡可以檢視示例原始碼

基於這個原理,那麼其他形式的背景圖都不難處理了。上面這種方法還不是實現內凹角的最佳方案。隨著CSS的mask和SVG技術越來越成熟之後,我們就可以採用mask和SVG的結合,實現任意形狀的內凹角效果,比如下圖這樣的:

undefined

有關於這方面的介紹可以閱讀@ANA TUDOR的《Scooped Corners in 2018》一文。

再來看PK進度條的效果:

undefined

最早採用的方案是使用漸變來完成:

div  {
    width: 608px;
    height: 90px;
    border-radius: 45px;
    position: relative;

    &::before,
    &::after {
          content: ``;
          position: absolute;
          left: 0;
          right: 0;
          top: 0;
          bottom: 0;
    }

    &::before {
          background: 
            linear-gradient(0deg, #FF1515 0, #FF1515 0) top left, 
            linear-gradient(300deg, transparent 90px, #FF1515 0) bottom right;
        background-size: 51% 100%;
        background-repeat: no-repeat;
        border-radius: 45px 0 0 45px;
        z-index: 2;
    }

    &::after {
          background:
            linear-gradient(0deg, #2F5FFC 0, #2F5FFC 0) top right, 
            linear-gradient(60deg, transparent 90px, #2F5FFC 0) bottom left;
        background-size: 51% 100%;
        background-repeat: no-repeat;
          border-radius: 0 45px 45px 0;
    }
}

點選這裡可以查閱示例原始碼

雖然外形看上去和我們的效果很類似,但實際還是有很大的差距的,仔細對比不難發現,設計稿的漸變是從上往下進行漸變。如果我們按照設計稿的漸變方式來寫的話,就很難使用linear-gradient來繪製帶有透明區域的斜切角。針對這種現象,斜切通過transform來完成:

undefined

點選這裡可以查閱示例原始碼

效果是不是更有質感了。

CSS繪製Icons已經不是新東東了。記得在15年的CSS Conf大會上看到Adobe的設計師@文婷分享的一個話題,就是使用CSS繪製Icons:

undefined

在網際網路上有關於這方面的案例還有很多。至於如何繪製實現,這裡就不做過多的闡述了。回到我們的主題中來。專案中也有對應的一些Icon,而且這些Icon我們也可以使用CSS來繪製。比如:

undefined

類似於這些Icons我們都是可以使用CSS直接繪製出來的。需要注意的一點是,比如繪製箭頭需要注意圓角處理。比如:

undefined

點選這裡可以查閱示例原始碼

狀態控制

這次專案還有另一個特色,就是活動頁底部Bar狀態多樣化:

undefined

除了邏輯複雜之外,展示風格較為複雜。

比如提示框在不同狀態下位置的控制,按鈕位置控制等。以往控制這些展示網格,一般情況下都是結合邏輯一起來處理,在不同的狀態下給容器新增不一樣的類名。但這次和邏輯解耦,直接通過CSS來判斷。比如提示框的展示:

.action {
    margin: 0 30px;
    position: relative;

    &:first-child .tooltip {
        left: 0;
        transform: none;

        &::before {
              left: 60px;
        }
    }

    &:last-child .tooltip {
        left: auto;
        right: 0;
        transform: none;

        &::before {
            left: auto;
            right: 60px;
        }
    }

    &:first-child:last-child .tooltip {
        left: 50%;
        right: auto;
        transform: translate(-50%, 0);

        &::before {
            left: 50%;
            transform: translate(-50%);
        }
    }
}

是的,其實就是這麼簡單,僅僅通過CSS的選擇器來控制了.tooltip展示位置。對於按鈕的展示相對而言沒有那麼複雜,因為我們的佈局採用的是Flexbox佈局,客戶端可以自動幫我們做相應的計算。只不過這裡有一個額外的需求,在貓客不需要展示“進入群聊”按鈕,有這個群聊按鈕的要進行絕對定位,距離螢幕左側有一個固定的值。

undefined

.action-position {
    position: absolute;
    left: 6px;
    top: 30px;
}

.action-position + .is-group-left {
    margin-left: 160px;
    margin-right: 10px;
}

一次失敗性的嘗試

在專案開始的時候,就打算使用CSS的自定義屬性來進行開發,因為CSS的自定義屬性可以幫助我節省很多的程式碼量,特別是在按鈕、提示框和模態彈框上的運用。我只需要宣告幾個自定義屬性,在呼叫的時候區域性修改已定義好的自定義好的屬性即可。這樣一來既好維護,又能省事不少。

直到專案提測之後,發現在iOS8.0的系統下對CSS的自定義屬性會失效。這樣不得不重新將自定義屬性重新覆蓋掉。

在未來不久之後,不需要相容iOS8.0系統的話,我們就可以大膽的在專案中使用CSS自定義屬性了。

除此之外,在Vue專案中,適配iPhoneX劉海機型時,直接使用calc()env()時,在編譯過程中會報錯。如果你碰到這樣的現象,可以採取迂迴戰術。先宣告一個自定義屬性,然後在calc()中和var()結合一起使用即可:

:root {
    --navBarHeight: 88px;
    --footerBarHeight: 150px;
    --safe-area-inset-bottom: env(safe-area-inset-bottom);
    --safe-area-inset-top: env(safe-area-inset-top);
}
.nav-bar ~ .content {
    padding-top: calc(var(--safe-area-inset-top) + var(--navBarHeight));
}
.footer-bar ~ .content {
    padding-bottom: calc(var(--safe-area-inset-bottom) + var(--footerBarHeight))
}

為了避免對自定義屬性不支援的裝置,建議把上面的程式碼放置到@supports()函式中執行。

標準化設計Bar

undefined

根據工業標準化的標準對Bar進行設計,這樣做更有利於對劉海機進行通用適配。有關於這方面的介紹可以閱讀《手淘Web頁面Bar和縱向適配的設計》一文。

縱向適配的嘗試

undefined

使用者終端螢幕縱向適配一直困惑著我自己,並且一直在探討這方面的最佳技術解決方案。可惜的是,直到現在還沒有找到一個最佳或者通用的技術方案。不過在這次專案中,對於一屏示的頁面,為了更好的適配高屏和短屏的裝置,我們在部分元素之間的間距採用了vh做為單位,同時配合不同的媒體查詢對重要元素(位置有明顯差異化)進行特殊處理。比如:

@media 
    only screen and (min-width : 375px) 
    and (min-height : 812px) 
    and (-webkit-device-pixel-ratio : 3){
    @supports (padding-bottom:env(safe-area-inset-bottom)){
        .page-invitation-help {
            --safe-area-inset-top: env(safe-area-inset-top);
            padding-top: var(--safe-area-inset-top);
          }

        .page-invitation {
              margin-top: 140px;
        }

        .page-title {
              top: -80px;
        }

        .page-invitation .page-footer {
              bottom: -90px;
        }
    }
}

來不及嘗試的srcset

移動終端眾多已不是什麼怪事了,不同的終端有不同的解析度,不同的DPR,但我們現在不管針對什麼樣的終端都在採用@2x(DPR為2)資源。比如背景圖片,產品圖片,裝飾元素等。這樣一來,對於還在使用@1x屏的使用者是不友好的,人家本來不需要那麼多頻寬來載入資源,我們就這樣強姦了人家;同時對於高於@2x的使用者(比如@3x@3.5x@4x)也不友好,給人提供的圖片不是最清晰圖片。

那麼給你的使用者提供最佳的圖片資源其實是我們應該探討和思考的問題。原本想在這個專案中使用img元素的新屬性srcsetsizes來,達到真正意義上給使用者終端提供最正確的圖片資源。


<img     srcset="/source-375@1x.jpeg 1x, /source-375@2x.jpeg 2x, /source-375@3x.jpeg 3x" src="/source-375@1x.jpeg" alt="Load the required images" />

由於前期調研不夠充分,錯過了在專案中嘗試srcset技術方案!

除了img中的srcsetsizes方案以外,HTML5的<picture>元素也可以達到類似的效果。

不管使用哪種方案,如果想做到給終端提供最正確的圖片資源,那麼運用在background-image的圖片就要改變使用方式,這也面臨著圖片的適配處理

雖然這次未能嘗試,但機會還是很多的,希望能在下次的專案中嘗試使用,然後再跟大家分享使用心得。

總結

寫到這裡,總算是結束了,零零總總寫了不少。總結了專案中一些自己使用心得,特別是如何利用一些新技術來替代圖片,從而儘可能的減少專案資源的載入,另外有哪些技術的運用能讓我的開發越來越輕鬆。或者將來的技術能給我們或者我們的使用者帶來的一些變化,實實在在的變化。

文章涉及到的點僅是個人觀點,僅供參考。如果有何不對之處,煩請路過的大嬸拍正,如果你有其他想分享的建議或經驗,歡迎在下面的評論中與我一起共享!(^_^)


相關文章