一个简单的收藏动画

3,699 阅读3分钟

起因是我们App原生和h5都有类似的内容卡片列表,h5版本较老。产品经理提出了需求,h5要做一番更新,与App样式保持一致。本来要求只是基本样式调整,但热情如我,把收藏动画这种细节也复刻了出来(我是一个多么实在又优秀的程序员啊呜呜呜~)

喏,效果就是这么个效果,喜欢的需要的可以直接拿走代码啦~

未命名.gif

拆分动作

最难的事情是我只有星星图片,没有动画的设计稿,想要实现只能靠肉眼观察,app上的收藏按钮点了不下百次吧,一边点一边在脑海里拆分动作~ 那么我们观察这个动画,从点击到结束,有几个要素

  • 星星填充颜色并放大
  • 周围有四个小圆点随星星放大一起扩散
  • 从中间扩散的圆环,比放大动画出现的晚一些
  • 星星放大后缩小到原位,四周的圆点扩散到最大后消失。

确定结构

首先是需要两张图片,星星线框图和填充图, 当然也可以换成svg、合成雪碧图什么的。

image.png

image.png

分析动画元素,我准备用一个div元素来放置星星,使用伪元素::before::after分别实现四个圆点和带透明度的圆环,结构如下。

<div id="star">
   ::before
   ::after
</div>

div主体给上宽高,用星星图片做背景填充。点击时切换背景图片。 半透明的圆比较好做,就是 绝对定位+圆角+rgba背景色。四个圆点相对复杂些,先写一个小圆点,用到box-shadow多重阴影复制四个圆, 这四个经过尝试调整到合适的大小和位置。

#star{
  position: relative;
  display: inline-block;
  width: 30px;
  height: 30px;
  background-image: url(star.png);
  background-repeat: no-repeat;
  background-size: 100%;
}
#star::before{
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  border-radius: 50%;
  background: rgba(254, 208, 1, 0.1);
}
#star::after{
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 10%;
  height: 10%;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: transparent;
  box-shadow: 15px -15px 0 #fed001, 15px 15px 0 #fed001,
    -15px -15px 0 #fed001, -15px 15px 0 #fed001;
}

添加动画

设计动画的过程就是根据拆分的动作,大概规划一下关键帧,然后反复调试参数,得到一个满意的效果收手。这里直接放代码吧

/* 放大 */
@keyframes banuce {
  0% {
    transform: scale(0.8);
  }
  100% {
    transform: scale(1.35);
  }
}
/* 缩放加透明度变化 */
@keyframes circle {
  0% {
    transform: scale(0.2);
    opacity: 0.8;
  }
  100% {
    transform: scale(1.5);
    opacity: 1;
  }
}
/* 出现。用opacity来控制元素隐藏显示 */
@keyframes show {
  0% {
    opacity: 1;
  }
}
    
/* 星星整体放大,并反向播放一次,实现弹出缩回效果 */
#star{
  animation: banuce 0.2s ease 0s 2 alternate;
}
/* 给半透明圆环加额外的扩散效果,加一点延迟。 */
#star::before{
  opacity: 0;
  animation: circle 0.3s ease 0.02s 1 alternate;
}
/* 两个伪元素都给一个初始透明度0,让它们在自身动画完成后消失 */
#star::after{
  opacity: 0;
  animation: show 0.2s steps(1,end) 0s 1;
}

加入点击事件

将动画样式拆出一个class .fill, 在点击时给元素添加类,从而实现状态变化。现在放上完整代码

<!DOCTYPE html>
<html lang="en">
<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>Document</title>
  <style>
    @keyframes banuce {
      0% {
        transform: scale(0.8);
      }
      100% {
        transform: scale(1.35);
      }
    }
    @keyframes circle {
      0% {
        transform: scale(0.2);
        opacity: 0.8;
      }
      100% {
        transform: scale(1.5);
        opacity: 1;
      }
    }
    @keyframes show {
      0% {
        opacity: 1;
      }
    }
    .wrap {
      width: 100vw;
      height: 100vh;
      display: flex;
      justify-content: center;
      align-items: center;
    }
    #star {
      display: inline-block;
      width: 30px;
      height: 30px;
      background-image: url(star.png);
      background-repeat: no-repeat;
      background-size: 100%;
      background-position: center;
    }
    .fill {
      position: relative;
      animation: banuce 0.2s ease 0s 2 alternate;
      background-image: url(star_fill.png) !important;
    }
    .fill::before {
      opacity: 0;
      content: '';
      position: absolute;
      left: 50%;
      top: 50%;
      width: 10%;
      height: 10%;
      transform: translate(-50%, -50%);
      border-radius: 50%;
      background: transparent;
      box-shadow: 15px -15px 0 #fed001, 15px 15px 0 #fed001,
        -15px -15px 0 #fed001, -15px 15px 0 #fed001;
      animation: show 0.2s steps(1, end) 0s 1;
    }
    .fill::after {
      opacity: 0;
      content: '';
      position: absolute;
      left: 0;
      right: 0;
      top: 0;
      bottom: 0;
      animation: circle 0.3s ease 0.02s 1 alternate;
      border-radius: 50%;
      background: rgba(254, 208, 1, 0.1);
    }
  </style>
</head>
<body>
  <div class="wrap">
    <div id="star"></div>
  </div>
  <script>
    window.onload = ()=> {
      const starEl = document.getElementById('star');
      starEl.addEventListener('click', (e)=>{
        starEl.className = 'fill';
        setTimeout(()=>{
          starEl.className = '';
        }, 1000)
      })
    }
  </script>
</body>
</html>

再来一个慢动作,把时间都*2看一下效果

收藏演示_1.gif

更新code

——end