以下为实际效果:
具体的实现过程如下: 因为我这个是给一个图书应用做的,所以给很多标签起名字都是book开头的,在这里先解释一下。 看这个动画我们先分析拆解一下:
1.弹出卡片
2.卡片翻转动画(难点)
3.烟花动画(难点)
4.弹出推荐图书
其中第一次和第四次的弹出动画是使用的CSS3的keyframes属性做出来的。
动画的弹出是一个逐渐变大并且又快到慢的过程,我们称之为缓入动画开头慢结尾快。
他的实现使用了animation里面的ease-in属性动画的最终状态保持使用了both属性,both属性很有意思,它集成了forwards即被应用目标会保留执行过程中的属性,backwards的目标在执行时立即应用被定义的值。并在animation-delay期间保留。
下面的css代码大部分集成了一些基本的布局设置,可以专注于&.animation{}部分的代码
我给1和4弹出卡片应用的动画:
.book-card{
position: relative;
width: 65%;
box-sizing: border-box;
border-radius: px2rem(15);
background: white;
/*animation-fill-mode:
动画的最后(达到100%)时的状态,
取值有:backward(回到初始状态)、forwards(停在最终状态)、none、both。
动画将遵循forwards和backwards的规则,从而在两个方向上扩展动画属性。*/
&.animation {
animation: scale .3s ease-in both;
@keyframes scale {
0% {
transform: scale(0);
opacity: 0;
}
100% {
transform: scale(1);
opacity: 1; }
}
}
.book-card-wrapper{
width: 100%;
height: 100%;
margin-bottom: px2rem(30);
@include columnTop;
.img-wrapper{
width: 100%;
margin-top: px2rem(20);
@include center;
.img {
width: px2rem(90);
height: px2rem(130);
}
}
.content-wrapper{
padding: 0 px2rem(20);
margin-top: px2rem(20);
.content-title{
color: #333;
font-weight: bold;
font-size: px2rem(18);
line-heigth:px2rem(20);
max-height: px2rem(40);
text-align: center;
@include ellipsis2(2)
}
.content-author{
margin-top: px2rem(10);
text-align: center;
}
.content-category{
color: #999;
font-size: px2rem(14);
margin-top: px2rem(10);
text-align: center;
}
}
.read-btn{
position: absolute;
bottom: 0;
left: 0;
z-index: 1100;
width: 100%;
border-radius: 0 0 px2rem(15) px2rem(15);
padding: px2rem(15) 0;
text-align: center;
color: white;
font-size: px2rem(14);
background: $color-blue;
}
}
}
我给2卡片翻转动画弹出使用的动画:
.flap-card-bg {
position: relative;
width: px2rem(64);
height: px2rem(64);
border-radius: px2rem(5);
background: white;
transform: scale(0);
opacity: 0;
&.animation{
animation: flap-card-move .3s ease-in both;
}
@keyframes flap-card-move {
0% {
transform: scale(0);
opacity: 0;
}
50% {
transform: scale(1.2);
opacity: 1;
}
75% {
transform: scale(.9);
opacity: 1;
}
100% {
transform: scale(1);
opacity: 1;
}
}
.flap-card{
width: px2rem(48);
height: px2rem(48);
@include absCenter;
.flap-card-circle{
display: flex;
width: 100%;
height: 100%;
.flap-card-semi-circle{
flex: 0 0 50%;
width: 50%;
height: 100%;
background-repeat: no-repeat;
backface-visibility: hidden;
}
.flap-card-semi-circle-left{
border-radius: px2rem(24) 0 0 px2rem(24);
background-position: center right;
transform-origin: right;
}
.flap-card-semi-circle-right{
border-radius: 0 px2rem(24) px2rem(24) 0;
background-position: center left;
transform-origin: left;
}
}
}
.point-wrapper{
z-index: 1500;
@include absCenter;
.point{
border-radius: 50%;
@include absCenter;
&.animation {
@for $i from 1 to length($moves) {
&:nth-child(#{$i}) {
@include move($i);
}
}
}
}
}
}
我给3烟花弹出使用的动画:
.point-wrapper{
z-index: 1500;
@include absCenter;
.point{
border-radius: 50%;
@include absCenter;
&.animation {
@for $i from 1 to length($moves) {
&:nth-child(#{$i}) {
@include move($i);
}
}
}
}
}
可以看到那些小卡片的翻转,这个实现的背后是非常复杂的,我将五张卡片,让它们按照一定的逻辑进行顺时针翻转,颜色的由浅入深,还有一个接一个。这个到底怎么实现呢?请看下文:
这里是标签代码,绑定了伪类和ref,可以使用$refs对象来操作dom
<div class="flap-card" v-for="(item, index) in flapCardList" :key="index"
:style="{zIndex: item.zIndex}">
<div class="flap-card-circle">
<div class="flap-card-semi-circle flap-card-semi-circle-left" :style="semiCircleStyle(item, 'left')" ref="left"></div>
<div class="flap-card-semi-circle flap-card-semi-circle-right" :style="semiCircleStyle(item, 'right')" ref="right"></div>
</div>
</div>
可以看到我是用了v-for循环展示flapCardList中的图片:
export const flapCardList = [
{
r: 255,
g: 102,
_g: 102,
b: 159,
imgLeft: 'url(' + require('@/assets/images/gift-left.png') + ')',
imgRight: 'url(' + require('@/assets/images/gift-right.png') + ')',
backgroundSize: '50% 50%',
zIndex: 100,
rotateDegree: 0
},
{
r: 74,
g: 171,
_g: 171,
b: 255,
imgLeft: 'url(' + require('@/assets/images/compass-left.png') + ')',
imgRight: 'url(' + require('@/assets/images/compass-right.png') + ')',
backgroundSize: '50% 50%',
zIndex: 99,
rotateDegree: 0
},
{
r: 255,
g: 198,
_g: 198,
b: 102,
imgLeft: 'url(' + require('@/assets/images/star-left.png') + ')',
imgRight: 'url(' + require('@/assets/images/star-right.png') + ')',
backgroundSize: '50% 50%',
zIndex: 98,
rotateDegree: 0
},
{
r: 255,
g: 102,
_g: 102,
b: 159,
imgLeft: 'url(' + require('@/assets/images/heart-left.png') + ')',
imgRight: 'url(' + require('@/assets/images/heart-right.png') + ')',
backgroundSize: '50% 50%',
zIndex: 97,
rotateDegree: 0
},
{
r: 59,
g: 201,
_g: 201,
b: 22,
imgLeft: 'url(' + require('@/assets/images/crown-left.png') + ')',
imgRight: 'url(' + require('@/assets/images/crown-right.png') + ')',
backgroundSize: '50% 50%',
zIndex: 96,
rotateDegree: 0
}
]
每一张图片都是用左右两部分拼接起来的。
可以关注到使用伪类给图片绑定上它的zIndex,出场顺序
:style="{zIndex: item.zIndex}"
基本布局代码如下:
.flap-card{
width: px2rem(48);
height: px2rem(48);
@include absCenter;
.flap-card-circle{
display: flex;
width: 100%;
height: 100%;
.flap-card-semi-circle{
flex: 0 0 50%;
width: 50%;
height: 100%;
background-repeat: no-repeat;
backface-visibility: hidden;
}
.flap-card-semi-circle-left{
border-radius: px2rem(24) 0 0 px2rem(24);
background-position: center right;
transform-origin: right;
}
.flap-card-semi-circle-right{
border-radius: 0 px2rem(24) px2rem(24) 0;
background-position: center left;
transform-origin: left;
}
}
}
一些methods对图像进行操作:
// 前面先展示图片在动的时候,后面的图片也需要做准备工作就是 也要动起来
prepare() {
const backFlapCard = this.flapCardList[this.back]
backFlapCard.rotateDegree = 180
backFlapCard._g = backFlapCard - 5 * 9
this.rotate(this.back, 'back')
},
颜色变化
semiCircleStyle(item, dir) {
return {
backgroundColor: `rgb(${item.r},${item.g},${item.b})`,
backgroundSize: item.backgroundSize,
backgroundImage: dir === 'left' ? item.imgLeft : item.imgRight
}
},
角度变化
rotate(index, type) {
const item = this.flapCardList[index]
let dom
if (type === 'front') {
dom = this.$refs.right[index]
} else {
dom = this.$refs.left[index]
}
dom.style.transform = `rotateY(${item.rotateDegree}deg)`
dom.style.backgroundColor = `rgb(${item.r},${item._g},${item.b})`
},
卡片转动起来以后属性的变化
flapCardRotate() {
const frontFlapCard = this.flapCardList[this.front]
const backFlapCard = this.flapCardList[this.back]
frontFlapCard.rotateDegree += 10
frontFlapCard._g -= 5
backFlapCard.rotateDegree -= 10
if (backFlapCard.rotateDegree < 90) {
backFlapCard._g += 5
}
if (frontFlapCard.rotateDegree === 90 && backFlapCard.rotateDegree === 90) {
backFlapCard.zIndex += 2
}
this.rotate(this.front, 'front')
this.rotate(this.back, 'back')
if (frontFlapCard.rotateDegree === 180 && backFlapCard.rotateDegree === 0) {
this.next()
}
},
卡片运行起来的逻辑
startFlapCardAnimation() {
this.prepare()
this.task = setInterval(() => {
this.flapCardRotate()
}, this.intervalTime)
},