阅读 37832

产品经理:鸿蒙那个开场动画挺帅的 给咱们页面也整一个呗

⚠️本文为掘金社区首发签约文章,未获授权禁止转载

前言

有一天开会,产品经理问:大家都升鸿蒙系统了么?紧接着一群人答:我们都用iPhone… 当然哈,我自己用的是安卓,不过也不是华为(留下了没钱的泪水)…

听了他这么一问我还以为这是要让我们开发鸿蒙应用了,还好我做过功课,有提前了解过它的语法,感觉跟小程序有些类似,这下前端又要开始卷了… 以后面试的时候估计还要问鸿蒙应用的知识…

就在这时产品从他的口袋里掏出了一台搭载了鸿蒙操作系统的华为mate40:来让你们看看我的开机动画:

2021-06-03 12-52-46.2021-06-03 12_53_07.gif

我希望这个字母的动画能够移植到咱们的网站上面去,因为咱们的好多产品字母里同样也有个

害!原来不是要开发鸿蒙应用呀!

分析动画

既然需求明确了下来,那么我们就要开始对这个动画进行分析了。第一次看的时候感觉挺惊艳的,因为感觉就像是一轮空心的明月在水面上升起,水面倒映着月影,让我想起了唐朝著名浪漫主义诗人李白的那句:举杯邀明月,对影成三人

前半秒我居然没看出来这到底是个什么,等这个空心的满月全部升起的时候我才知道原来这是一个字母:

既然是倒映,那么就证明上下的显示是一致的,只不过是倒了过来,那么我们有(包括但不仅限于)如下几种选择:

  • -webkit-box-reflect (专门做倒影或侧影的一个属性)(火狐和IE不支持)
  • -moz-element() (可以将DOM的某部分当作图片渲染)(只有火狐支持)
  • 复制一遍DOM 然后用transform: rotate(180deg);把它给倒过来

想了一下虽然最后那个最麻烦,但最合适的还是它,不仅仅因为它的兼容性最好,而是因为仔细观察了一下鸿蒙的开场动画,那个倒影是有一定的模糊程度的。-webkit-box-reflect只能控制方向及透明度或渐变透明度,但无法添加模糊效果。-moz-element()虽然非常强大,但只有火狐支持那是肯定不行的,要知道目前火狐在浏览器市场的占比已经非常低了,所以只好用不那么优雅的第三个方式了,首先我们需要绘制一个半圆形的圆环,你能想到几种方式?

第一种:

一大一小两个半圆,小半圆的背景色保持与页面背景色一致的颜色,然后盖在大半圆上(就像日环食那样),这样看起来就像是个圆环啦(原理示意图):

2021-06-11 11-06-17.2021-06-11 11_06_37.gif

第二种:

先写出来个半圆,不给加背景色,只给加边框,最后把下边框去掉,于是看起来就是个半圆环啦(原理示意图):

2021-06-11 11-33-25.2021-06-11 11_33_42.gif

第三种:

直接写个圆,然后写上边框,圆环外套个容器,外层容器高度为圆的一半,最后overflow: hidden;隐藏掉露在外面那半部分(原理示意图):

2021-06-11 19-13-21.2021-06-11 19_13_44.gif

第四种:

把第三种的overflow: hidden;换成clip-path(原理示意图):

2021-06-11 19-13-21.2021-06-11 19_13_44.gif

第五种: 直接用SVGCanvas来进行绘制(原理示意图):

2021-06-17 13-27-03.2021-06-17 13_27_20.gif


最终还是选择了overflow: hidden;,因为用它来做圆环升起的效果很合适,把露在外面的那部分圆隐藏掉,然后控制圆的位置,看起来就像是一轮空心的明月从海面上升起来了一样(原理示意图):

2021-06-15 13-40-06.2021-06-15 13_40_23.gif

接下来再把两个半圆环拼接到一起去就可以了(原理示意图):

2021-06-23 15-27-56.2021-06-23 15_28_16.gif

去掉为了向大家展示原理的那些杂七杂八的动画之后,显示出来的最终效果如下:

2021-06-23 15-32-19.2021-06-23 15_32_39.gif

是不是有那么一点点神似了呢?不过在细节上跟鸿蒙的那个开场动画比起来还是差了许多,比方说后来我又找到了一版鸿蒙开场动画,如果跟以前的动画比起来的话,现在这版本在细节上的处理就更加的游刃有余了:

2021-06-04 14-39-35.2021-06-04 14_40_03.gif

首先我们可以看到这个有一个外发光的效果,在黑色背景的衬托下显得格外明亮,鸿蒙第一版的那个动画其实也有外发光,大家可以翻上去仔细对比一下,那一版的外发光没有这一版明显,而且细节处理的也没有这版好。那版是在全部显现之后立刻消除掉外发光,有些略显生硬。而仔细看这版的话可以发现外发光是在不知不觉的过程中消失的。CSS的外发光效果其实很好做,就是在黑色背景下用box-shadow给元素添加一个适当模糊的白色阴影,然后求阴影部分面积:

2021-06-24 13-30-04.2021-06-24 13_30_25.gif

此时溢出隐藏(overflow: hidden;)这个方案的缺点就会被暴露出来,由于我们的阴影部分面积在上下左右四个方向已经超出了外面盒子的宽高,所以被隐藏掉了,我们只好为外面的盒子加入内边距padding来解决掉这个缺陷:

2021-06-24 14-57-07.2021-06-24 14_57_18.gif

我们也把我们的变白变粗,但仔细看又会发现新的问题:那就是box-shadow默认只会在元素的外部添加阴影,我们这个圆圈的内部却没有阴影,好在box-shadow是支持多重阴影和内阴影的:

2021-06-24 15-01-47.2021-06-24 15_02_02.gif

而且这种效果用filter: drop-shadow();也同样可以实现,不过由于在谷歌内核的浏览器中,filter: drop-shadow();在动态变化的元素上渲染效果并不如box-shadow那样理想:

2021-06-24 16-22-48.2021-06-24 16_23_04.gif

所以我们决定还是采用box-shadow内外双阴影的方案,现在看起来已经不错了,但还是少了点什么,少的就是圆环倒映在水面上的模糊效果。要知道在日常生活中,倒映在水面上的图案通常会比真正的视图稍稍模糊一点:

src=http___up.kukudesk.com_pic_360_7b_b7_dc_7bb7dc776bbc8f069f22cd52b528be73.jpg&refer=http___up.kukudesk.jpeg

这是因为水面其实并不是一个完全平整的平面,哪怕再小的风也会导致水面上产生一定的水波:

WechatIMG1.jpeg

正是这些水波导致了倒映在水面上的图案会产生一定的模糊度,水波的波纹越细,模糊程度就会越精致。正如上面那张图一样,水波的波纹不够细,就会导致我们就能够看到水波的纹理,就像鸿蒙的效果图那样:

他这水波的纹理搞得跟指纹一样… 如果要咱们写出这样的一个滤镜的话还是非常困难的,但好在鸿蒙的开场动画并没有能够看到水波的纹理,所以咱们就可以用模糊效果(filter: blur(2px);)来写:

2021-06-24 16-43-52.2021-06-24 16_44_10.gif

这个效果跟鸿蒙的开场效果比起来差距可就不是一星半点了,所以说鸿蒙那个动画虽然看起来简单,好像就是一个圆环从水面上升起来的效果,但实际上蕴含的细节只有亲自动手试一遍才会知道。

模糊细节

我们来把咱们做的圆环升起时的效果和鸿蒙圆环升起时的效果截张图放在一起对比一下:

WX20210624-164933.png

WX20210624-165127.png

发现没有?咱们用的CSS模糊,模糊方向是上下左右东南西北等各个方向的,而鸿蒙的模糊方向是沿着Y轴也就是上下方向的模糊,如果还是看着不太明显的话那咱们再来截一张图看看:

WX20210624-165536.png

这个是圆环未完全升起时的效果,这回应该能比较明显的看出来,水面下的圆环越靠下模糊程度就越高,并且它的模糊主要是沿着上下两个方向来进行模糊的。而且在向下方向的模糊程度要比在向上方向时的模糊程度要高上许多,这样看起来就会比较真实,才能给用户一种在水面上升起的错觉。

如果不分青红皂白的按照各个方向一顿模糊的话,那么圆环看起来的效果就怎么也不像是在水面上的感觉了:

WX20210624-164933.png

对于这种带着方向带着渐变带着不同程度的模糊效果,我们就不能指望CSS了。这种场景下需要用到的是更为底层也更加复杂的SVG滤镜

其实好多CSS属性都是从SVG那里获得的灵感,比如说我们较为常用的pointer-eventsfilter等,还有一些不常用的clip-pathmask等…

由于CSS提供给我们的模糊只能各个方向都模糊,而在目前这种情况下我们需要的是沿着Y轴模糊,那么SVG的代码就可以写成这样:

<svg>
  <filter id="blur">
    <feGaussianBlur in="SourceGraphic" stdDeviation="0 5"/>
  </filter>
</svg>
复制代码

看不懂没关系啊,大家只需要记住stdDeviation这个属性是控制模糊的就可以了,如果只给一个数字的话,就相当于全方位模糊,跟CSS的filter: blur();效果是差不多的。但如果给了两个数字,那么第一个数字就代表X轴模糊程度,第二个数字就代表Y轴模糊程度。在这里我们让X轴的模糊程度为0Y轴的模糊程度为5,注意不要像写CSS的时候给加单位(px),这里只写数字就好了,不要带单位。

由于CSS的滤镜属性filter本来就是从SVG那边吸收过来的,所以在CSS中可以使用SVG滤镜!用法如下:

filter: url(#blur);
复制代码

我们之前不是在SVG的<filter>标签上加了一个id属性么,这个id就可以写在CSS滤镜url里。但也不知是谷歌浏览器的filter在动态变化的元素上渲染不好还是怎么着,总之在Chrome浏览器里显示效果是这样的:

2021-06-24 19-00-39.2021-06-24 19_00_56.gif

而在Safari浏览器里是这样的:

2021-06-24 19-02-24.2021-06-24 19_02_41.gif

火狐浏览器里效果最为完美:

2021-06-24 19-02-57.2021-06-24 19_03_09.gif

那这可不行啊,谷歌浏览器可是市场占有率最高的浏览器了,产品那边肯定通不过的!不过也不是没办法解决啦。在谷歌浏览器那显示的问题不就是一开始会有个缝嘛!那咱们就margin-top: -2px;来让这两个半圆先负距离接触,对于它俩来说也不用-18px-2px就够啦:

2021-06-24 19-19-59.2021-06-24 19_20_10.gif

最后一步,就是把下半圆的模糊效果去掉,让它真正的变成一个字母

2021-06-24 19-24-16.2021-06-24 19_24_32.gif

没想到用了SVGCSS filter居然没有任何的过渡效果,那就只好用requestAnimationFrame来动态改变SVG里的<feGaussianBlur>上的stdDeviation属性啦:

2021-06-24 19-38-40.2021-06-24 19_38_55.gif

把这个效果拿给产品经理看,他很满意并对此赞不绝口。说看看咱们哪个产品名字里带的,全给换上这个动画!

但实际上吧,我觉得这个动画在很多细节的处理上跟鸿蒙的开机动画还是有差距。不得不佩服开发鸿蒙的工程师团队,就在这转瞬即逝的一两秒里居然能蕴含那么多小细节。大家可以找一找细节上的差距,有空的话咱们再优化一下。

完整代码

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>公众号:前端学不动</title>
  <style>
    * {
      padding: 0;
      margin: 0;
    }
    html, body { height: 100% }
    body {
      background: black;
      display: flex;
      align-items: center;
      justify-content: center
    }
    .ul {
      position: relative;
      width: 100px;
      height: 50px;
      padding: 10px;
      list-style: none;
      overflow: hidden
    }
    .ul:first-of-type {
      padding-bottom: 0
    }
    .ul:last-of-type {
      padding-top: 0;
      /* margin-top: -2px; */
      /* animation: container-move .1s 1.2s forwards */
    }

    .harmony {
      position: absolute;
      top: 10px;
      left: 10px;
      width: 70px;
      height: 70px;
      border: 15px solid white;
      border-radius: 50%;
      transform: translateY(50%);
      box-shadow: 0 0 6px white, inset 0 0 6px white;
      animation: move 1.2s forwards
    }
    .ul:last-of-type > .harmony {
      top: auto;
      bottom: 10px;
      transform: translateY(-50%);
      filter: url(#blur)
    }

    svg {
      width: 0;
      height: 0
    }

    @keyframes move {
      to { transform: none }
    }

    /* @keyframes container-move {
      to { margin-top: 0 }
    } */
  </style>
</head>
<body>
  <div class="container">
    <ul class="ul">
      <li class="harmony"></li>
    </ul>
    <ul class="ul">
      <li class="harmony"></li>
    </ul>
  </div>

  <svg>
    <filter id="blur">
      <feGaussianBlur in="SourceGraphic" stdDeviation="0 6"/>
    </filter>
  </svg>

  <script>
    const filter = document.querySelector('feGaussianBlur')
    
    const clearFilter = () => {
      const value = parseFloat(filter.getAttribute('stdDeviation').split(' ')[1]) - 0.06
      
      if (value > 0) {
        filter.setAttribute('stdDeviation', `0 ${value}`)
        requestAnimationFrame(clearFilter)
      } else {
        return
      }
    }

    setTimeout(clearFilter, 1200)
  </script>
</body>
</html>
复制代码

注释的那部分代码就是为了解决谷歌浏览器有缝隙的代码,可以解开注释对比一下在谷歌浏览器里的效果。不解开注释的话拿火狐浏览器打开效果是最好的。最重要的一点是,我们可以通过修改代码里的数字来改变这个动画的效果:

2021-06-24 21-33-15.2021-06-24 21_33_43.gif

2021-06-24 21-36-10.2021-06-24 21_36_26.gif

2021-06-24 19-38-40.2021-06-24 19_38_55.gif

大家觉得这仨哪个更好看呢?当然如果像鸿蒙那样作为开机动画来说,肯定是越快越好。因为这个动画可能也就前两次看着能有点新鲜感。但每次开机都看这么个动画,很快就会审美疲劳了,用户只希望能够快点开机少整点那些花里胡哨的。

不过如果抛开这些应用场景的话,大家觉得是第一张那样让模糊慢慢消失好看,还是最后那张一边升起一边就把模糊度给擦除掉了好看呢?

往期精彩文章

文章分类
前端