• 巧用 mask-image 实现简单进度加载界面


    最近给 nzoo 折腾官网,拿 angular2.0 + webpack 实现SPA,然后觉得最终打包后的出口文件有点大,用户首次访问会有一个时间较长的白屏等候界面,感觉体验不太好。

    于是希望在用户下载整个 bundle 时能够先看到一个“加载中”的UI做过度,鉴于 nzoo 的LOGO也较简洁,便舍弃笨重的雪碧图+step动画的形式,转以 mask-image + transition动画来实现。

    整体最终交互如下(模拟的是 2G 网速):

    虽然界面简单,但整个动画仅仅使用了一张3.5kb大小的图片(戳我查看,注意是全白的会跟背景混一体):

    如交互截图所示,我们希望在用户刚进入页面时,开始从底部给 logo 填色,持续10秒的 easeout 动画然后停在距离顶部还有一小部分未填色的地方。

    接着在用户下载完 bundle 后,用 300ms 时间填完整个logo再执行 angular 应用启动脚本。

    什么是 mask-image

    擅长捣弄 Flash 甚至 AE 的朋友相信对“遮罩层”的概念会很清楚,都是指定某层的元件的轮廓/alpha通道来作为自己剪影的依据。在 Flash 中遮罩层只支持矢量,而AE则支持多种形式的遮罩(毕竟人家用来后期的嘛)。

    另外FW和PS都支持alpha遮罩(PS中称为“蒙版”)。而今天要聊的css3中的 mask-image 则是以指定图片的透明度作为剪影依据的。

    介个怎么理解呢?我们来张简单的示意图:

    相信玩 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

  • 相关阅读:
    【科普杂谈】计算机按下电源后发生了什么
    【VS开发】使用WinPcap编程(1)——获取网络设备信息
    【VS开发】使用WinPcap编程(1)——获取网络设备信息
    微信公众平台消息接口PHP版
    编码gbk ajax的提交
    mysql 查询
    js cookie
    js同域名下不同文件下使用coookie
    去掉A标签的虚线框
    jquery切换class
  • 原文地址:https://www.cnblogs.com/vajoy/p/5095511.html
Copyright © 2020-2023  润新知