画个电池

当然,电池充电,首先得用 CSS 画一个电池,这个不难,随便整一个:

Android 自定义view和属性动画实现充电进度条效果 打开充电动画设置_CSS

欧了,勉强就是它了。有了电池,那接下来直接充电吧。最最简单的动画,那应该是用色彩把整个电池灌满即可。

方法很多,代码也很简单,直接看效果:

Android 自定义view和属性动画实现充电进度条效果 打开充电动画设置_ci_02

有内味了,如果要求不高,这个勉强也就能够交差了。通过蓝色渐变表示电量,通过色块的位移动画实现充电的动画。但是总感觉少了点什么。

<div class="container">
    <div class="battery"></div>
</div>
html,
body {
    width: 100%;
    height: 100%;
    display: flex;
    background: #e4e4e4;
}

.container {
    position: relative;
    width: 140px;
    margin: auto;
}

.battery {
    height: 220px;
    box-sizing: border-box;
    border-radius: 15px 15px 5px 5px;
    filter: drop-shadow(0 1px 3px rgba(0,0,0,0.22));
    background: #fff;
    z-index: 1;
    
    &::before {
        content: "";
        position: absolute;
        width: 26px;
        height: 10px;
        left: 50%;
        top: 0;
        transform: translate(-50%, -10px);
        border-radius: 5px 5px 0 0;
        background: rgba(240, 240, 240, .88);
    }
    
    &::after {
        content: "";
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        top: 90%;
        background: linear-gradient(to bottom, #7abcff 0%, #00BCD4 44%, #2196F3 100%);
        border-radius: 0px 0px 5px 5px;
        box-shadow: 0 14px 28px rgba(33, 150, 243, 0), 0 10px 10px rgba(9, 188, 215, 0.08);
        animation: charging 6s linear infinite;
        filter: hue-rotate(90deg);
    }
}

@keyframes charging {
  50% {
        box-shadow: 0 14px 28px rgba(0, 150, 136, 0.83), 0px 4px 10px rgba(9, 188, 215, 0.4);
    }
    
    95% {
        top: 5%;
        filter: hue-rotate(0deg);
        border-radius: 0 0 5px 5px;
        box-shadow: 0 14px 28px rgba(4, 188, 213, .2), 0 10px 10px rgba(9, 188, 215, 0.08);
    }
    100% {
        top: 0%;
        filter: hue-rotate(0deg);
        border-radius: 15px 15px 5px 5px;
        box-shadow: 0 14px 28px rgba(4, 188, 213, 0), 0 10px 10px rgba(9, 188, 215, 0.4);
    }
}

增加阴影及颜色的变化

如果要继续优化的话,需要添加点细节。

我们知道,低电量时,电量通常表示为红色,高电量时表示为绿色。再给整个色块添加点阴影的变化,呼吸的感觉,让充电的效果看起来确实是在动。 

Android 自定义view和属性动画实现充电进度条效果 打开充电动画设置_滤镜_03

知识点

到这里,其实只有一个知识点:

  • 使用 filter: hue-rotate() 对渐变色彩进行色彩过渡变换动画

我们无法对一个渐变色直接进行 animation ,这里通过滤镜对色相进行调整,从而实现了渐变色的变换动画。

上述例子完整的 Demo: CodePen Demo -- Battery Animation One

添加波浪

ok,刚刚算一个小里程碑,接下来再进一步。电量的顶部为一条直线有点呆呆的感觉,这里我们进行改造一下,如果能将顶部直线,改为波浪滚动,效果会更为逼真一点。

改造之后的效果: 

Android 自定义view和属性动画实现充电进度条效果 打开充电动画设置_css3_04

使用 CSS 实现这种波浪滚动效果,其实只是用了一种障眼法,具体的可以我早期写的这篇文章:

纯 CSS 实现波浪效果!

知识点

这里的一个知识点就是上述说的使用 CSS 实现简易的波浪效果,通过障眼法实现,看看图就明白了:

Android 自定义view和属性动画实现充电进度条效果 打开充电动画设置_CSS_05

上述例子完整的 Demo: CodePen Demo -- Battery Animation Two

<div class="container">
    <div class="header"></div>
    <div class="battery">
    </div>
    <div class="battery-copy">
        <div class="g-wave"></div>
        <div class="g-wave"></div>
        <div class="g-wave"></div>
    </div>
</div>

html,
body {
    width: 100%;
    height: 100%;
    display: flex;
    background: #e4e4e4;
}

.container {
    position: relative;
    width: 140px;
    margin: auto;
}

.header {
    position: absolute;
    width: 26px;
    height: 10px;
    left: 50%;
    top: 0;
    transform: translate(-50%, -10px);
    border-radius: 5px 5px 0 0;
    background: rgba(255, 255, 255, .88);
}

.battery-copy {
    position: absolute;
    top: 0;
    left: 0;
    height: 220px;
    width: 140px;
    border-radius: 15px 15px 5px 5px;
    overflow: hidden;
}

.battery {
    position: relative;
    height: 220px;
    box-sizing: border-box;
    border-radius: 15px 15px 5px 5px;
    box-shadow: 0 0 5px 2px rgba(255, 255, 255, 0.22);
    background: #fff;
    z-index: 1;
    
    &::after {
        content: "";
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        top: 80%;
        background: linear-gradient(to bottom, #7abcff 0%, #00BCD4 44%, #2196F3 100%);
        border-radius: 0px 0px 5px 5px;
        box-shadow: 0 14px 28px rgba(33, 150, 243, 0), 0 10px 10px rgba(9, 188, 215, 0.08);
        animation: charging 10s linear infinite;
        filter: hue-rotate(90deg);
    }
}

.g-wave {
    position: absolute;
    width: 300px;
    height: 300px;
    background: rgba(255, 255, 255, .8);
    border-radius: 45% 47% 44% 42%;
    bottom: 25px;
    left: 50%;
    transform: translate(-50%, 0);
    z-index: 1;
    animation: move 10s linear infinite;
}

.g-wave:nth-child(2) {
    border-radius: 38% 46% 43% 47%;
    transform: translate(-50%, 0) rotate(-135deg);
}

.g-wave:nth-child(3) {
    border-radius: 42% 46% 37% 40%;
    transform: translate(-50%, 0) rotate(135deg);
}

@keyframes charging {
  50% {
        box-shadow: 0 14px 28px rgba(0, 150, 136, 0.83), 0px 4px 10px rgba(9, 188, 215, 0.4);
    }
    
    95% {
        top: 5%;
        filter: hue-rotate(0deg);
        border-radius: 0 0 5px 5px;
        box-shadow: 0 14px 28px rgba(4, 188, 213, .2), 0 10px 10px rgba(9, 188, 215, 0.08);
    }
    100% {
        top: 0%;
        filter: hue-rotate(0deg);
        border-radius: 15px 15px 5px 5px;
        box-shadow: 0 14px 28px rgba(4, 188, 213, 0), 0 10px 10px rgba(9, 188, 215, 0.4);
    }
}

@keyframes move {
    100% {
        transform: translate(-50%, -160px) rotate(720deg);
    }
}

OK,到这,上述效果加上数字变化已经算是一个比较不错的效果了。当然上面的效果看上去还是很 CSS 的,就是一眼看到就觉得用 CSS 是可以做到的。

使用强大的 CSS 滤镜实现安卓充电动画效果

那下面这个呢? 

Android 自定义view和属性动画实现充电进度条效果 打开充电动画设置_CSS_06

用安卓手机的同学肯定不陌生,这个是安卓手机在充电的时候的效果。看到这个我就很好奇,使用 CSS 能做到吗?

经过一番尝试,发现使用 CSS 也是可以很好的模拟这种动画效果: 

Android 自定义view和属性动画实现充电进度条效果 打开充电动画设置_CSS_07

上述 Gif 录制的效果图是完全使用 CSS 模拟的效果。

上述例子完整的 Demo: HuaWei Battery Charging Animation

<div class="g-container">
    <div class="g-number">98.7%</div>
    <div class="g-contrast">
        <div class="g-circle"></div>
        <ul class="g-bubbles">
            <li></li>
            <li></li>
            <li></li>
            <li></li>
            <li></li>
            <li></li>
            <li></li>
            <li></li>
            <li></li>
            <li></li>
            <li></li>
            <li></li>
            <li></li>
            <li></li>
            <li></li>
        </ul>
    </div>
</div>
html,
body {
    width: 100%;
    height: 100%;
    display: flex;
    background: #000;
    overflow: hidden;
}

.g-number {
    position: absolute;
    width: 300px;
    top: 27%;
    text-align: center;
    font-size: 32px;
    z-index: 10;
    color: #fff;
}

.g-container {
    position: relative;
    width: 300px;
    height: 400px;
    margin: auto;
}

.g-contrast {
    filter: contrast(10) hue-rotate(0);
    width: 300px;
    height: 400px;
    background-color: #000;
    overflow: hidden;
    animation: hueRotate 10s infinite linear;
}

.g-circle {
    position: relative;
    width: 300px;
    height: 300px;
    box-sizing: border-box;
    filter: blur(8px);
    
    &::after {
        content: "";
        position: absolute;
        top: 40%;
        left: 50%;
        transform: translate(-50%, -50%) rotate(0);
        width: 200px;
        height: 200px;
        background-color: #00ff6f;
        border-radius: 42% 38% 62% 49% / 45%;
        animation: rotate 10s infinite linear;
    }
    
    &::before {
        content: "";
        position: absolute;
        width: 176px;
        height: 176px;
        top: 40%;
        left: 50%;
        transform: translate(-50%, -50%);
        border-radius: 50%;
        background-color: #000;
        z-index: 10;
    }
}

.g-bubbles {
    position: absolute;
    left: 50%;
    bottom: 0;
    width: 100px;
    height: 40px;
    transform: translate(-50%, 0);
    border-radius: 100px 100px 0 0;
    background-color: #00ff6f;
    filter: blur(5px);
}

li {
    position: absolute;
    border-radius: 50%;
    background: #00ff6f;
}

@for $i from 0 through 15 { 
    li:nth-child(#{$i}) {
        $width: 15 + random(15) + px;
        left: 15 + random(70) + px;
        top: 50%;
        transform: translate(-50%, -50%);
        width: $width;
        height: $width;
        animation: moveToTop #{random(6) + 3}s ease-in-out -#{random(5000)/1000}s infinite;
    }
}

@keyframes rotate {
    50% {
        border-radius: 45% / 42% 38% 58% 49%;
    }
    100% {
        transform: translate(-50%, -50%) rotate(720deg);
    }
}

@keyframes moveToTop {
    90% {
        opacity: 1;
    }
    100% {
        opacity: .1;
        transform: translate(-50%, -180px);
    }
}

@keyframes hueRotate {
    100% {
        filter: contrast(15) hue-rotate(360deg);
    }
}

知识点

拆解一下知识点,最主要的其实是用到了 filter: contrast() 以及 filter: blur() 这两个滤镜,可以很好的实现这种融合效果。

单独将两个滤镜拿出来,它们的作用分别是:

  1. filter: blur(): 给图像设置高斯模糊效果。
  2. filter: contrast(): 调整图像的对比度。

但是,当他们“合体”的时候,产生了奇妙的融合现象。

先来看一个简单的例子:

Android 自定义view和属性动画实现充电进度条效果 打开充电动画设置_css3_08

仔细看两圆相交的过程,在边与边接触的时候,会产生一种边界融合的效果,通过对比度滤镜把高斯模糊的模糊边缘给干掉,利用高斯模糊实现融合效果。

当然,这种效果在之前的文章也多次提及过,更具体的,可以看看:

颜色的变换

当然,这里也是可以加上颜色的变换,效果也很不错: 

Android 自定义view和属性动画实现充电进度条效果 打开充电动画设置_ci_09

上述例子完整的 Demo: HuaWei Battery Charging Animation

容易忽视的点

通过调节 filter: blur() 及 filter: contrast() 属性的值,动画效果其实会有很大程度的变化,好的效果需要不断的调试。当然,经验在其中也是发挥了很重要的作用,说到底还是要多尝试。

最后

本文给出的几个充电动画,效果渐进增强,本文只指出了最核心的知识点。但是在实际输出的过程中有很多小细节是本文没有提及的,感兴趣的同学还是应该点进 Demo 好好看看源码或者自己动手实现一遍。

更多精彩 CSS 技术文章汇总在我的 Github -- iCSS ,持续更新,欢迎点个 star 订阅收藏。