CSS3实现类似翻书效果的过渡动画的示例代码


Posted in HTML / CSS onSeptember 06, 2019

在VUE实战项目的中有一个加载推荐书籍的过渡动画,在项目中是使用JS实现。

CSS3实现类似翻书效果的过渡动画的示例代码

当时看视频大概一个小时作用,拆分动画逻辑,写代码更是耗时。后来自己想着能不能用CSS动画直接写出来,折腾了半天,终于算是实现了。
可以查看加载动画地址

/*首先是DOM结构,不能像js一样分左右两边,正面翻为反面还要改变z-index,按照同样的布局也尝试过,没有用CSS动画写出来,逻辑太复杂。
这是一个类似翻书一样的动画效果,想着结构分为一页一页,一页旋转180°,完成时再改变“这一页”的z-index。每个page类的before伪类为正面,after伪类为反面;分解5页的在每一秒的动画,写出各自的animation。
*/
<div class="card">
    <div class="page"></div>
    <div class="page"></div>
    <div class="page"></div>
    <div class="page"></div>
    <div class="page"></div>
</div>



        .card{
            width: 50px;
            height: 50px;
            opacity: 0;
            position: relative;
            animation: .5s all;
        }
        .card .page{
            width: 50%;
            height: 100%;
            position: absolute;
            left: 50%;
            top: 0;
            transform-style: preserve-3d;
            transform-origin: left;
        }
        .card .page::before, .card .page::after{
            content: '';
            position: absolute;
            left: 0;
            top: 0;
            width: 100%;
            height: 100%;
            border-radius: 0px 50px 50px 0px;
        }
        .card .page::after{
            border-radius: 50px 0px 0px 50px;
        }
        .card .page:nth-child(1){
            animation: animation1 2s linear infinite;
        }
        .card .page:nth-child(2){
            animation: animation2 2s linear infinite;
        }
        .card .page:nth-child(3){
            animation: animation3 2s linear infinite;
        }
        .card .page:nth-child(4){
            animation: animation4 2s linear infinite;
        }
        .card .page:nth-child(5){
            animation: animation5 2s linear infinite;
        }
        .card .page:nth-child(1)::before{
            background: aqua url("./images/star-right.png") center left/50% 50% no-repeat;
        }
        .card .page:nth-child(1)::after{
            background: hotpink url("./images/compass-left.png") center right/50% 50% no-repeat;
            transform: rotateY(180deg);
        }
        .card .page:nth-child(2)::before{
            background: hotpink url("./images/compass-right.png") center left/50% 50% no-repeat;
        }
        .card .page:nth-child(2)::after{
            background: coral url("./images/crown-left.png") center right/50% 50% no-repeat;
            transform: rotateY(180deg);
        }
        .card .page:nth-child(3)::before{
            background: coral url("./images/crown-right.png") center left/50% 50% no-repeat;
        }
        .card .page:nth-child(3)::after{
            background: cyan url("./images/gift-left.png") center right/50% 50% no-repeat;
            transform: rotateY(180deg);
        }
        .card .page:nth-child(4)::before{
            background: cyan url("./images/gift-right.png") center left/50% 50% no-repeat;
        }
        .card .page:nth-child(4)::after{
            background: yellowgreen url("./images/heart-left.png") center right/50% 50% no-repeat;
            transform: rotateY(180deg);
        }
        .card .page:nth-child(5)::before{
            background: yellowgreen url("./images/heart-right.png") center left/50% 50% no-repeat;
        }
        .card .page:nth-child(5)::after{
            background: aqua url("./images/star-left.png") center right/50% 50% no-repeat;
            transform: rotateY(180deg);
        }
@keyframes animation1 {
            0%{
                z-index: 1;
                transform: rotateY(180deg);
            }
            20%{
                z-index: 1;
                transform: rotateY(180deg);
            }
            40%{
                z-index: 3;
                transform: rotateY(360deg);
            }
            60%{
                z-index: 4;
                transform: rotateY(360deg);
            }
            60.0001%{
                z-index: 4;
                transform: rotateY(0deg);
            }
            80%{
                z-index: 5;
                transform: rotateY(0deg);
            }
            100%{
                z-index: 4;
                transform: rotateY(180deg);
            }
        }
        @keyframes animation2 {
            0%{
                z-index: 5;
                transform: rotateY(0deg);
            }
            20%{
                z-index: 4;
                transform: rotateY(180deg);
            }
            40%{
                z-index: 1;
                transform: rotateY(180deg);
            }
            60%{
                z-index: 3;
                transform: rotateY(360deg);
            }
            80%{
                z-index: 4;
                transform: rotateY(360deg);
            }
            100%{
                z-index: 5;
                transform: rotateY(360deg);
            }
        }
        @keyframes animation3 {
            0%{
                z-index: 4;
                transform: rotateY(0deg);
            }
            20%{
                z-index: 5;
                transform: rotateY(0deg);
            }
            40%{
                z-index: 4;
                transform: rotateY(180deg);
            }
            60%{
                z-index: 1;
                transform: rotateY(180deg);
            }
            80%{
                z-index: 3;
                transform: rotateY(360deg);
            }
            100%{
                z-index: 4;
                transform: rotateY(360deg);
            }
        }
        @keyframes animation4 {
            0%{
                z-index: 3;
                transform: rotateY(0deg);
            }
            20%{
                z-index: 4;
                transform: rotateY(0deg);
            }
            40%{
                z-index: 5;
                transform: rotateY(0deg);
            }
            60%{
                z-index: 4;
                transform: rotateY(180deg);
            }
            80%{
                z-index: 1;
                transform: rotateY(180deg);
            }
            100%{
                z-index: 3;
                transform: rotateY(360deg);
            }
        }
        @keyframes animation5 {
            0%{
                z-index: 2;
                transform: rotateY(0deg);
            }
            20%{
                z-index: 3;
                transform: rotateY(0deg);
            }
            40%{
                z-index: 4;
                transform: rotateY(0deg);
            }
            60%{
                z-index: 5;
                transform: rotateY(0deg);
            }
            80%{
                z-index: 4;
                transform: rotateY(180deg);
            }
            100%{
                z-index: 1;
                transform: rotateY(180deg);
            }
        }

再贴出JS实现:

<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>
data() {
  return {
    front: 0,
    back: 1,
  }
},
methods: {
    // flapCardList是存储着图片信息的对象数组,通过v-for循环和semiCircleStyle方法设置5组图片的背景。
    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) {
        //卡牌翻转,"front"选择右边卡片,否则选择左边卡片;然后改变dom元素的样式
        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() {
    //首先是翻转函数,每次翻转分正反两面。当动画至90°时,改变背面的z-index值,每一帧动画完毕执行rotate函数以改变样式。
    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()
    }
  },
  prepare() {
    const backFlapCard = this.flapCardList[this.back]
    backFlapCard.rotateDegree = 180
    backFlapCard._g = backFlapCard.g - 5 * 9
    this.rotate(this.back, 'back')
  },
  next() {
    const frontFlapCard = this.flapCardList[this.front]
    const backFlapCard = this.flapCardList[this.back]
    frontFlapCard.rotateDegree = 0
    backFlapCard.rotateDegree = 0
    frontFlapCard._g = frontFlapCard.g
    backFlapCard._g = backFlapCard.g
    this.rotate(this.front, 'front')
    this.rotate(this.back, 'back')
    this.front++
    this.back++
    const len = this.flapCardList.length
    if (this.front >= len) {
      this.front = 0
    }
    if (this.back >= len) {
      this.back = 0
    }
    // 动态设置zIndex
    // 100 -> 96
    // 99 -> 100
    // 98 -> 99
    // 97 -> 98
    // 96 -> 97
    // (0 - 1 + 5) % 5 = 4
    // (1 - 1 + 5) % 5 = 0
    this.flapCardList.forEach((item, index) => {
      item.zIndex = 100 - ((index - this.front + len) % len)
    })
    this.prepare()
  },
  startFlapCardAnimation() {
    this.prepare()
    this.task = setInterval(() => {
      this.flapCardRotate()
    }, this.intervalTime)
  },
  stopAnimation() {
    //动画停止时,清除所有setTimeout;否则再次进入动画出现错误。
    if (this.task) {
      clearInterval(this.task)
    }
    if (this.timeout) {
      clearTimeout(this.timeout)
    }
    if (this.timeout2) {
      clearTimeout(this.timeout2)
    }
    this.reset()
  },
  runAnimation() {
    //点击推荐,则动画开始(vuex中定义了flapCardVisible,watch监听其变化,为TRUE则动画开始)
    this.runFlapCardAnimation = true
    this.timeout = setTimeout(() => {
      this.startFlapCardAnimation()
      this.startPointAnimation()
    }, 300)
    this.timeout2 = setTimeout(() => {
      this.stopAnimation()
      //2500ms过渡动画结束,推荐图书显示
      this.runBookCardAnimation = true
    }, 2500)
  },
  watch: {
      flapCardVisible(v) {
        if (v) {
          this.runAnimation()
        }
      }
  },
}

写在最后:初次写CSS动画的总结,而且感觉自己的实现并不是很好,应该有更简单些的实现方法。从DOM结构设计,到动画过程分析还欠缺很多。并且一个动画如此费时,在实际项目中应该得不偿失。还是需要大量的练习,经常练手才可熟能生巧。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

HTML / CSS 相关文章推荐
IE滤镜与CSS3效果(详细整理分享)
Jan 25 HTML / CSS
css3隔行变换色实现示例
Feb 19 HTML / CSS
css3+jq创作含苞待放的荷花
Feb 20 HTML / CSS
网页切图的CSS和布局经验与要点
Apr 09 HTML / CSS
css3 border旋转时的动画应用
Jan 22 HTML / CSS
用css3实现转换过渡和动画效果
Mar 13 HTML / CSS
详解CSS3+JS完美实现放大镜模式
Dec 03 HTML / CSS
HTML5 对各个标签的定义与规定:body的介绍
Jun 21 HTML / CSS
HTML5视频支持检测(检查浏览器是否支持视频播放)
Jun 08 HTML / CSS
Canvas与Image互相转换示例代码
Aug 09 HTML / CSS
举例详解HTML5中使用JSON格式提交表单
Jun 16 HTML / CSS
CSS浮动引起的高度塌陷问题
Aug 05 HTML / CSS
CSS3动画之利用requestAnimationFrame触发重新播放功能
Sep 11 #HTML / CSS
css3 响应式媒体查询的示例代码
Sep 25 #HTML / CSS
移动端rem布局的两种实现方法
Jan 03 #HTML / CSS
css3实现波纹特效、H5实现动态波浪效果
Jan 31 #HTML / CSS
css3动画过渡实现鼠标跟随导航效果
Feb 08 #HTML / CSS
CSS3 calc()会计算属性详解
Feb 27 #HTML / CSS
CSS3 clip-path 用法介绍详解
Mar 01 #HTML / CSS
You might like
PHP 生成的XML以FLASH获取为乱码终极解决
2009/08/07 PHP
详解WordPress中提醒安装插件以及隐藏插件的功能实现
2015/12/25 PHP
PHP数组函数知识汇总
2016/05/12 PHP
游戏人文件夹程序 ver 3.0
2006/07/14 Javascript
基于jquery的finkyUI插件与Ajax实现页面数据加载功能
2010/12/03 Javascript
Javascript学习笔记二 之 变量
2010/12/15 Javascript
Jquery 数据选择插件Pickerbox使用介绍
2012/08/24 Javascript
JavaScript中的常见问题解决方法(乱码,IE缓存,代理)
2013/11/28 Javascript
jQuery之简单的表单验证实例
2016/07/07 Javascript
jquery精度计算代码 jquery指定精确小数位
2017/02/06 Javascript
vue+vuex+axios+echarts画一个动态更新的中国地图的方法
2017/12/19 Javascript
Node批量爬取头条视频并保存方法
2018/09/20 Javascript
微信小程序自定义纯净模态框(弹出框)的实例代码
2020/03/09 Javascript
微信小程序利用button控制条件标签的变量问题
2020/03/15 Javascript
记一次用ts+vuecli4重构项目的实现
2020/05/21 Javascript
vue Treeselect下拉树只能选择第N级元素实现代码
2020/08/31 Javascript
[00:35]DOTA2上海特级锦标赛 EG战队宣传片
2016/03/04 DOTA
python中执行shell命令的几个方法小结
2014/09/18 Python
python关键字and和or用法实例
2015/05/28 Python
python 实现将小图片放到另一个较大的白色或黑色背景图片中
2019/12/12 Python
python解压zip包中文乱码解决方法
2020/11/27 Python
英国领先的酒杯和水晶玻璃器皿制造商:Dartington Crystal
2019/06/23 全球购物
斯图尔特·韦茨曼鞋加拿大官网:Stuart Weitzman加拿大
2019/10/13 全球购物
波兰在线杂货店:Polski Koszyk
2019/11/02 全球购物
Loreto Gallo英国:欧洲领先的在线药房
2021/01/21 全球购物
店长岗位的工作内容
2013/11/12 职场文书
班级入场式解说词
2014/02/01 职场文书
感恩寄语大全
2014/04/11 职场文书
大学学生会竞选演讲稿
2014/04/25 职场文书
企业财务总监岗位职责
2015/04/03 职场文书
2015年质量管理工作总结范文
2015/05/18 职场文书
闪闪的红星观后感
2015/06/08 职场文书
python 下载文件的几种方式分享
2021/04/07 Python
go 原生http web 服务跨域restful api的写法介绍
2021/04/27 Golang
HTML5+CSS+JavaScript实现捉虫小游戏设计和实现
2021/10/16 HTML / CSS
gojs实现蚂蚁线动画效果
2022/02/18 Javascript