起因是我们App原生和h5都有类似的内容卡片列表,h5版本较老。产品经理提出了需求,h5要做一番更新,与App样式保持一致。本来要求只是基本样式调整,但热情如我,把收藏动画这种细节也复刻了出来(我是一个多么实在又优秀的程序员啊呜呜呜~)
喏,效果就是这么个效果,喜欢的需要的可以直接拿走代码啦~
拆分动作
最难的事情是我只有星星图片,没有动画的设计稿,想要实现只能靠肉眼观察,app上的收藏按钮点了不下百次吧,一边点一边在脑海里拆分动作~ 那么我们观察这个动画,从点击到结束,有几个要素
- 星星填充颜色并放大
- 周围有四个小圆点随星星放大一起扩散
- 从中间扩散的圆环,比放大动画出现的晚一些
- 星星放大后缩小到原位,四周的圆点扩散到最大后消失。
确定结构
首先是需要两张图片,星星线框图和填充图, 当然也可以换成svg、合成雪碧图什么的。
分析动画元素,我准备用一个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看一下效果
更新code
——end