查看原文
其他

动画合成小技巧!CSS 实现动感的倒计时效果

XboxYan SegmentFault思否 2022-08-03

今天为各位小伙伴推荐的是社区作者 XboxYan 的文章,在这篇文章中他为我们介绍了一个 CSS 动画合成小技巧。


话不多说,让我们一起来学习吧~




介绍一个 CSS 动画合成小技巧。先看效果



这是一个非常“动感”的倒计时效果,通常在一些活动开场中比较常见,分析一下整个动画过程,不难发现,有以下几类动画


  1. 数字的变化

  2. 缩小和放大

  3. 透明度变化


不知道小伙伴能否观察出来呢?下面来一起来看看具体实现吧


一、数字的变化



先来看数字的变化。


这个技巧在之前的文章:还在使用定时器吗?CSS 也能实现电子时钟 中首次用到,这里再次介绍一下


文章链接:https://segmentfault.com/a/1190000041661094


在以前,数字的变化可能需要创建多个标签,然后改变位移来实现


<count-down>
    <span>5</span>
  <span>4</span>
  <span>3</span>
  <span>2</span>
  <span>1</span>
</count-down>


这种方式需要创建多个标签,略微繁琐,也不易扩展。现在有更简洁的方式可以实现了,那就是 CSS @property。这是干什么的呢?简单来讲,可以自定义属性,在这个例子中,可以让数字像颜色一样进行过渡和动画,可能不太懂,直接看例子吧


假设 HTML 是这样的


<count-down style="--t: 5"></count-down>


然后我们通过 CSS 变量将数字渲染到页面,这里需要借助伪元素和计数器


有兴趣的可以参考这篇文章:小tips: 如何借助content属性显示CSS var变量值


文章链接:https://www.zhangxinxu.com/wordpress/2019/05/content-css-var/

count-down::after{
  counter-reset: time var(--t);
  content: counter(time);
}


效果如下



如何让这个数字变化呢?可以用到 CSS 动画


@keyframes count {
    to {
        --t: 0
    }
}
count-down::after{
    --t: 5;
    counter-reset: time var(--t);
    content: counter(time);
    animation: count 5s forwards;
}


效果如下



现在的效果仅仅是5秒后,数字从 5 变成了 0,并没有 5 => 4 => 3 => 2 => 1 这种阶段变化。然后最重要的一步来了,加上以下自定义属性


@property --t { 
    syntax: '<integer>';
    inherits: false;
    initial-value: 0;
}


对的,仅仅添加这一小段 CSS,效果就出来了



是不是很神奇?可以这么理解,通过@property定义后,这个变量--t本身可以单独设置动画了,就像颜色变化一样。


另外,使用计数器的好处是可以随意更换类型,比如将上面的阿拉伯数字换成中文计数,只需要更换计数器类型就行了


完整类型可以参考:https://developer.mozilla.org/zh-CN/docs/Web/CSS/list-style-type


count-down::after{
    --t: 5;
    counter-reset: time var(--t);
    content: counter(time, cjk-decimal); /*中日韩十进制数*/
    animation: count 5s forwards;
}


效果如下



是不是非常方便呢?


二、倒计时的终点



上面的计数器最后的终点是“0”,显然我们需要一些特定的提示,比如“Go~”



如何改变最后一帧的状态呢?这里有两种方式:


  1. 通过动画覆盖

  2. 通过计数器覆盖


首先来看第一种方式,这个比较好理解,重新定义一个动画,在倒计时结束后,将最后一帧重置一下


@keyframes stop {
    to {
        content: 'Go~';
    }
}
count-down::after{
    --t: 5;
    counter-reset: time var(--t);
    content: counter(time);
    animation: count 5s forwards,
    stop 5s step-end forwards;
}


·效果如下



注意这里动画函数是step-end,为啥是这个呢?step-end也可写作steps(1,end),你可以理解为在整个动画只有两种状态,在运行过程中,都是初始状态,只有到达最后一帧才改变状态,下面是 MDN 的截图



下面来看第二种方式,通过自定义计数器来实现。原理其实和 JS 思维有些类似,当数字为 0 时,让计数器指定一个特殊的值,具体实现如下


@counter-style stop {
    system: cyclic;
    symbols: "Go~";
    range: 0 0;
}


这里简单解释一下,这里有个range属性,表示计数器的范围,由于这里只需要指定为 0,所以是区间0 0。然后是system,表示计算系统,这里为cyclic,表示循环使用开发者提供的一套字符,字符由symbos定义。然后symbos表示计算符号,也就是具体展示的字符,这里指定为Go~就行了。


这部分自定义计数器内容比较复杂,也比较新,有兴趣的可以参考张鑫旭的这篇文章:CSS @counter-style规则详细介绍


文章链接:https://www.zhangxinxu.com/wordpress/2021/10/css-counter-style/


然后是应用


count-down::after{
      /**/
    counter-reset: time var(--t);
    content: counter(time, stop); /*自定义计数器*/
}


这样也能达到相同的效果,实现也更加优雅



三、缩放和透明度变化



这两个动画其实是同时进行的,可以放在一个动画里


@keyframes shark {
    0%{
        opacity: 1;
        transform: scale(1);
    }
    
    50%{
        opacity: 0;
        transform: scale(0.4);
    }
}


然后设置动画时长为 1s,循环 5 次


count-down::after{
    --t: 5;
    counter-reset: time var(--t);
    content: counter(time);
    animation: count 5s steps(5) forwards,
    shark 1s 5;
}


效果如下



是不是稍微有些突兀?因为数字的变化是突然的,需要将数字的变化隐藏到透明度为 0 的时候,为了达到这种效果,只需要将闪烁动画延迟 0.5 秒即可


count-down::after{
    --t: 5;
    counter-reset: time var(--t);
    content: counter(time);
    animation: count 5s steps(5) forwards,
    shark 1s .5s 5; /*延迟 0.5s*/
}


这样就自然多了



不过还有优化的空间。比如现在数字动画有些太连贯了,如果希望数字出现后稍微停留一小会,或者说希望出现的慢一点,消失的快一点,如何处理呢?其实这比想象中的要容易许多,只需要改一下关键帧位置就行了,如下


@keyframes shark {
    0%{
        opacity: 1;
        transform: scale(1);
    }
    
    20%{
        opacity: 0;
        transform: scale(0.4);
    }
}


同时,延迟的时间也需要改成 0.8 秒,效果如下



这样就实现了文章开头所示效果


下面重点来了~完整代码如下


@property --t { 
    syntax: '<integer>';
    inherits: false;
    initial-value: 0;
}
@counter-style stop {
    system: cyclic;
    symbols: "Go~";
    range: infinite 0;
}
html,body{
    margin: 0;
    height: 100%;
    display: grid;
    place-content: center;
}
count-down{
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: Consolas, Monaco, monospace;
    font-size: 120px;
}
count-down::after{
    --t: 5;
    --dur: 1;
    counter-reset: time var(--t);
    content: counter(time, stop);
    animation: count calc( var(--t) * var(--dur) * 1s ) steps(var(--t)) forwards,
    shark calc(var(--dur) * 1s) calc(var(--dur) * .8s) calc(var(--t));
}
@keyframes count {
    to {
        --t: 0;
    }
}
@keyframes shark {
    0%{
        opacity: 1;
        transform: scale(1);
    }
    
    20%{
        opacity: 0;
        transform: scale(0.4);
    }
}


你也可以访问在线例子:

https://codepen.io/xboxyan/pen/GRxKqPm

https://code.juejin.cn/pen/7113470934589112327


另外,demo 中还有个小彩蛋,点击可以重新运行动画,实现方式如下


count-down:active::after{
    animation: none;
}


四、其他动画效果



除了缩放效果,还可以有一些位移的动画,比如这样的


@keyframes shark {
    0%{
        opacity: 1;
        transform: translateY(0);
    }
    
    20%{
        opacity: 0;
        transform: translateY(100px);
    }
}


效果如下





是不是有点奇怪?动画不够连贯,一会向下一会向上,有没有办法消失和出现都是从上到下的呢?当然也是可以的,实现如下


@keyframes shark {
    0%{
        opacity: 1;
        transform: translateY(0);
    }
    
    20%{
        opacity: 0;
        transform: translateY(100px);
    }

    21%{
        opacity: 0;
        transform: translateY(-100px);
    }
}


这里多加了一个非常“邻近”的关键帧,表示在透明状态下,“迅速”改变位移,这样在数字出现时的动画就感觉是从上到下的,整体更为流畅,效果如下



还可以调整一下前面的缩放效果,让出来的时候更大,效果也更为震撼


@keyframes shark {
    0%{
        opacity: 1;
        transform: scale(1);
    }
    
    20%{
        opacity: 0;
        transform: scale(.4);
    }

    21%{
        opacity: 0;
        transform: scale(5);
    }
}

效果如下



当然还有其他效果,比如旋转,斜切等,这就需要发挥你的想象了~


五、总结和说明



以上就是本文的全部内容了,一个简单的小动画,你学会了吗?下面总结一下实现要点:


  1. 复杂动画可以分解成多个简单的动画
  2. 数字的变化可以通过多个标签,改变位移实现
  3. CSS 计数器可以将数字变量渲染到页面
  4. CSS @property 以将CSS变量设置动画,就像颜色变化一样
  5. CSS 计数器的好处是可以随意更改类型,比如中文计数
  6. 倒计时的终点默认是数字 0 ,可以通过另一个动画重置最后一帧
  7. 可以通过自定义 CSS 计数器,让某个计数符号渲染成指定字符
  8. 缩放和透明的变化是同时进行的,可以放在一个动画里
  9. 数字的变化需要注意安排在透明度为0的时候,不然数字变化很突兀
  10. 数字的出现和消失动画可以添加一个邻近的关键帧来快速归位


文中用了一些比较新的属性,比如 @property,还有自定义计数器,不过没关系,文中也都提到了其他解决方案,动画的整体思路是不变的,如何观察和分解动画,这个才是最重要的。最后,如果觉得还不错,对你有帮助的话,欢迎点赞、收藏、转发❤❤❤




SegmentFault 思否社区小编说


自 2022-07-01 起 SegmentFault 思否公众号改版啦!之后将陆续推出新的栏目和大家见面!(请拭目以待呀~


在「社区精选」栏目中,我们将为广大开发者推荐来自 SegmentFault 思否开发者社区的优质技术文章,这些文章全部出自社区中充满智慧的技术创作者哦!


希望通过这一栏目,大家可以共同学习技术干货,GET 新技能和各种花式技术小 Tips。


欢迎越来越多的开发者加入创作者的行列,我们将持续甄选出社区中优质的内容推介给更多人,让闪闪发光的技术创作者们走到聚光灯下,被更多人认识。


「社区精选」投稿邮箱:pr@segmentfault.com

投稿请附上社区文章地址




点击左下角阅读原文,到 SegmentFault 思否社区 和文章作者展开更多互动和交流,公众号后台回复“ 入群 ”即可加入我们的技术交流群,收获更多的技术文章~
- END -


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存