巧用 mask-image 實現簡單進度加載界面

最近給 nzoo 折騰官網,拿 angular2.0 + webpack 實現SPA,而後以爲最終打包後的出口文件有點大,用戶首次訪問會有一個時間較長的白屏等候界面,感受體驗不太好。css

因而但願在用戶下載整個 bundle 時可以先看到一個「加載中」的UI作過分,鑑於 nzoo 的LOGO也較簡潔,便捨棄笨重的雪碧圖+step動畫的形式,轉以 mask-image + transition動畫來實現。webpack

總體最終交互以下(模擬的是 2G 網速):css3

雖然界面簡單,但整個動畫僅僅使用了一張3.5kb大小的圖片(戳我查看,注意是全白的會跟背景混一體):git

如交互截圖所示,咱們但願在用戶剛進入頁面時,開始從底部給 logo 填色,持續10秒的 easeout 動畫而後停在距離頂部還有一小部分未填色的地方。github

接着在用戶下載完 bundle 後,用 300ms 時間填完整個logo再執行 angular 應用啓動腳本。web

什麼是 mask-imagechrome

擅長搗弄 Flash 甚至 AE 的朋友相信對「遮罩層」的概念會很清楚,都是指定某層的元件的輪廓/alpha通道來做爲本身剪影的依據。在 Flash 中遮罩層只支持矢量,而AE則支持多種形式的遮罩(畢竟人家用來後期的嘛)。bootstrap

另外FW和PS都支持alpha遮罩(PS中稱爲「蒙版」)。而今天要聊的css3中的 mask-image 則是以指定圖片的透明度做爲剪影依據的。瀏覽器

介個怎麼理解呢?咱們來張簡單的示意圖:angular2

相信玩 flash 的童鞋會不屑地一笑,以爲是個好簡單的事兒—— 底部搞個填滿色的DOM由下往上運動,頂部固定放個輪廓層(png)剪影整個動畫就好了嘛。

然而現實比較骨感—— mask-image 所指定的遮罩圖會死死地固定在被遮罩元素上,能夠理解爲若元素動了那麼遮罩圖也會隨着動。也就是說 flash 的那一套不適用於css3上。

此路不通換條道走—— 把動畫改成 transition + background-position 來實現,而不靠元素自己瞎運動了。

咱們如今手頭有個 nzoo 的剪影,先看看填滿整個logo 顏色須要怎麼作,一共就倆步:

⑴ 放個DOM,給它配上 -webkit-mask-image 的樣式指定遮罩圖片;

⑵ 給 DOM上漸變色(得屢次微調讓漸變的角度、位置到位)。

因而初始化樣式是這樣的:

<style>
        mask-bg{
            mask-image: url(src/image/common/mask.png);
            -webkit-mask-image: url(src/image/common/mask.png);
            position: absolute;
            width:409px;height:158px;
            background-image: linear-gradient(353deg,#89C027,#89C027 28%,#E96036 28%,#E96036 49%,#FEF158 49%,#FEF158 72%,#76C5EE 72%);
            background-image: -webkit-linear-gradient(353deg,#89C027,#89C027 28%,#E96036 28%,#E96036 49%,#FEF158 49%,#FEF158 72%,#76C5EE 72%);
            background-image: -moz-linear-gradient(353deg,#89C027,#89C027 28%,#E96036 28%,#E96036 49%,#FEF158 49%,#FEF158 72%,#76C5EE 72%);
        }
</style>

<mask-bg></mask-bg>

鑑於 firefox 還不支持在樣式中配置 mask-image 特性,因此代碼中咱們沒寫 -moz-mask-image。(firefox的兼容後面說)

總之與 mask-image 樣式結合先後的是醬子的:

留意 nzoo 的字樣是有傾斜角度的,因此咱們在 liner-gradient 中加了個 353deg 用於線性傾斜填充,這裏填充的角度以及位置,均是後期微調得出的數據。

接着咱們在其頂部安放另外一個DOM(<mask-top>),用做徹底未填色的 logo (底色爲#EEE):

<style>
        mask-bg, mask-top{
            position: absolute;
            width:409px;height:158px;
            mask-image: url(src/image/common/mask.png);
            -webkit-mask-image: url(src/image/common/mask.png);
        }
        mask-bg{
            background-image: linear-gradient(353deg,#89C027,#89C027 28%,#E96036 28%,#E96036 49%,#FEF158 49%,#FEF158 72%,#76C5EE 72%);
            background-image: -webkit-linear-gradient(353deg,#89C027,#89C027 28%,#E96036 28%,#E96036 49%,#FEF158 49%,#FEF158 72%,#76C5EE 72%);
            background-image: -moz-linear-gradient(353deg,#89C027,#89C027 28%,#E96036 28%,#E96036 49%,#FEF158 49%,#FEF158 72%,#76C5EE 72%);
        }
        mask-top{
            background-image: linear-gradient(bottom,#EEEEEE,#EEEEEE 60%,rgba(0,0,0,0) 60%);
            background-image: -webkit-linear-gradient(bottom,#EEEEEE,#EEEEEE 60%,rgba(0,0,0,0) 60%);
            background-image: -moz-linear-gradient(bottom,#EEEEEE,#EEEEEE 60%,rgba(0,0,0,0) 60%);
        }
</style>

<mask-bg></mask-bg>
<mask-top></mask-top>

爲實現動畫再加上 background-position、background-size 和 transition 定義:

<style>
        mask-bg, mask-top{
            position: absolute;
            width:409px;height:158px;
            mask-image: url(src/image/common/mask.png);
            -webkit-mask-image: url(src/image/common/mask.png);
        }
        mask-bg{
            background-image: linear-gradient(353deg,#89C027,#89C027 28%,#E96036 28%,#E96036 49%,#FEF158 49%,#FEF158 72%,#76C5EE 72%);
            background-image: -webkit-linear-gradient(353deg,#89C027,#89C027 28%,#E96036 28%,#E96036 49%,#FEF158 49%,#FEF158 72%,#76C5EE 72%);
            background-image: -moz-linear-gradient(353deg,#89C027,#89C027 28%,#E96036 28%,#E96036 49%,#FEF158 49%,#FEF158 72%,#76C5EE 72%);
        }
        mask-top{
            background-image: linear-gradient(bottom,#EEEEEE,#EEEEEE 60%,rgba(0,0,0,0) 60%);
            background-image: -webkit-linear-gradient(bottom,#EEEEEE,#EEEEEE 60%,rgba(0,0,0,0) 60%);
            background-image: -moz-linear-gradient(bottom,#EEEEEE,#EEEEEE 60%,rgba(0,0,0,0) 60%);
            background-size: auto 300% ;
            -webkit-background-size: auto 300% ;
            -moz-background-size: auto 300% ;
            background-position: 0 -50%;
            transition: all 10s cubic-bezier(0, 0, 0.28, 1) 1s;
        }
</style>

<mask-bg></mask-bg>
<mask-top></mask-top>

其中 background-size 的配置用於拉長線性填充的漸進線,並初始化 background-position 的垂直距離爲 -50%(即恰好整個剪影區域都是有填滿#EEE的,剪影底部如下才則爲rgba(0,0,0,0)的透明填充)。

因此後續咱們經過動態改變 background-position 的垂直定位,把<mask-top>的漸進性由底部往上平移,從而逐步展現出其下方的<mask-bg>元素的內容,就能實現整個加載動畫界面。

爲了方便理解 <mask-top> 原理,我作了個效果圖:

另外要留意的是咱們給 transition 動畫加了個1秒延遲,主要是爲了方便客戶端先下載遮罩圖片再執行動畫。

至於如何觸發 transition 就不廢話了,仍是按老套數給父層元素動態加個 class 來實現:

        app.loading mask-top{
            background-position: 0 -8%;
        }
        app.loading-done mask-top{
            background-position: 0 0;
            transition: all 0.3s;
        }
    setTimeout(function(){
        document.querySelector('app').className='loading';
    },10);

注意這裏的 app 是我給 <mask-top> 和 <mask-top> 外部過了一層自定義DOM <app></app>,本來只是用做後續掛載 angular 組件,如今咱把它用於存放掛載組件前先執行的加載交互元素。

在用戶下載好 bundle 腳本以後(這時說明一切都loading好了),咱們給 <app> 換上名爲「loading-done」的類觸發「把logo所有填滿色」的 300ms 動畫,也順道延遲 300ms 再啓動angular:

import {App} from '../component/App';
import {ROUTER_PROVIDERS} from 'angular2/router';
import {bootstrap} from 'angular2/platform/browser';

document.querySelector('app').className='loading-done';
setTimeout(bootstrap.bind(this, App, [ROUTER_PROVIDERS]), 300);

因而在 webkit 瀏覽器中一切就如同一開始的交互動畫同樣順利運行。

Firefox 和 Edge

Firefox 與 chrome 不一樣,對 mask-image 有另外一套標準,須要 svg 加持,咱們看下示例

  <!-- SVG begins -->
  <svg>

    <!-- Definition of a mask begins -->
    <defs>
      <mask id="mask" maskUnits="userSpaceOnUse" maskContentUnits="userSpaceOnUse">
        <image width="400px" height="300px" xlink:href="mouse.png"></image>
      </mask>
    </defs>
    <!-- Definition of a mask ends -->

    <foreignObject width="400px" height="300px" style="mask: url(#mask);">

      <!-- HTML begins -->
      <div class="element">
        <p>Lorem ipsum dolor sit … amet.</p>
      </div>
      <!-- HTML ends -->

    </foreignObject>
  </svg>
  <!-- SVG ends -->

說白了就是往 svg 裏嵌入 XHTML 來實現,細心看看其實也不復雜。咱們能夠稍微改下代碼(主要是DOM結構)來作兼容:

    <style>
        app>div.loading-mask{
            position: absolute;
            top: 150px;
            left: 50%; margin-left: -204px;
            width:409px;height:158px;
            overflow: hidden;
        }
        .loading-mask mask-bg,.loading-mask mask-top{
            position: absolute;
            width:409px;height:158px;
            mask-image: url(src/image/common/mask.png);
            -webkit-mask-image: url(src/image/common/mask.png);
        }
        .loading-mask mask-bg{
            background-image: linear-gradient(353deg,#89C027,#89C027 28%,#E96036 28%,#E96036 49%,#FEF158 49%,#FEF158 72%,#76C5EE 72%);
            background-image: -webkit-linear-gradient(353deg,#89C027,#89C027 28%,#E96036 28%,#E96036 49%,#FEF158 49%,#FEF158 72%,#76C5EE 72%);
            background-image: -moz-linear-gradient(353deg,#89C027,#89C027 28%,#E96036 28%,#E96036 49%,#FEF158 49%,#FEF158 72%,#76C5EE 72%);
        }
        .loading-mask mask-top{
            background-image: linear-gradient(bottom,#EEEEEE,#EEEEEE 60%,rgba(0,0,0,0) 60%);
            background-image: -webkit-linear-gradient(bottom,#EEEEEE,#EEEEEE 60%,rgba(0,0,0,0) 60%);
            background-image: -moz-linear-gradient(bottom,#EEEEEE,#EEEEEE 60%,rgba(0,0,0,0) 60%);
            background-size: auto 300% ;
            -webkit-background-size: auto 300% ;
            -moz-background-size: auto 300% ;
            background-position: 0 -50%;
            transition: all 10s cubic-bezier(0, 0, 0.28, 1) 1s;
        }
        app.loading mask-top{
            background-position: 0 -8%;
        }
        app.loading-done mask-top{
            background-position: 0 0;
            transition: all 0.3s;
        }
    </style>

<app>
    <div class="loading-mask">
        <svg width="409px" height="158px">
            <defs>
                <mask id="mask">
                    <image width="409px" height="158px" xlink:href="src/image/common/mask.png"></image>
                </mask>
            </defs>
            <foreignObject width="409px" height="158px" style="mask: url(#mask);">

                <mask-bg></mask-bg>
                <mask-top></mask-top>

            </foreignObject>
        </svg>

    </div>
</app>

這樣在 Firefox 中也能正常運行咱們的加載動畫了。

不過有趣的是,我在 caniuse 看到巨硬 Edge 是不支持 mask-image 的:

而實際用上 Firefox 這套後發現竟然能在 Edge 上順利運行了。

ok 大週末半夜三更的就跟你們嘮嗑到這,想來我也許久沒寫樣式軟文了,共勉~

donate

相關文章
相關標籤/搜索