CSS3实现交互式翻书动画

项目内容

利用 HTML 构建书本结构,CSS 设置 3D 场景并设计样式,JavaScript 实现鼠标交互计算与控制,使书本能够沿 Y 轴进行 3D 旋转,配合 CSS animation 实现平滑流畅的翻页动画效果,并协调书影、书内容等组件的变换,实现了一个交互式的 3D 翻书动画,具有较好的技术实现和细节优化。

1. HTML结构

我们可以从结构的角度来描述这个HTML布局:

书本由两个主要部分组成------左侧的正文部分(front-cover)和右侧的书皮部分(book-cover)。

在书皮部分,我们使用一个pic盒子容纳书皮的图片,pic盒子在翻动书的过程中会缓慢立起来并使用一个shadow盒子添加投影效果。

而在正文部分,我们使用一个back盒子放置背面的装饰图片,一个front盒子放置需要展示的正文内容。

这样通过HTML的结构化标签布局,我们分区划分了书本的不同组成部分,为后续的CSS样式设计和JS交互提供了基础。这种语义化的结构化思维,是编写可维护代码的重要方式。

  • book 代表整个书本
  • book-cover 代表书皮部分
  • front-cover 代表正文页部分
  • page 分别代表书皮和正文的前后页
  • shadow 和 pic 代表正文页上的阴影和图片
  • 结构如下代码所示:
xml 复制代码
  <div class="book p3d">

    <div class="book-cover p3d">
      <div class="page back flip"></div>
      <div class="page front p3d">
        <div class="shadow"></div>
        <div class="pic"></div>
      </div>
    </div>

    <div class="front-cover p3d">
      <div class="page front flip p3d">
        <p>

        </p>
      </div>

      <div class="page back"></div>
    </div>

  </div>

2. CSS样式

上面我们已经设置好了该项目的基本结构,接下来我们要使用css来实现翻转动画。具体可以分为以下几个步骤:

1.1 书本样式

  • 设置了书本的大小、位置、旋转角度等
  • transform属性使其呈现开书状态

1.2 书页样式

  • 绝对定位在书本内部构成书页
  • 设置了前后页的背景颜色

1.3 正文样式

  • 设置了transform-origin作为旋转原点
  • 保证翻转时书脊不动

1.4 背面页样式

  • 添加了背景图片
  • 用translateZ使其稍微偏离正面

1.5 动画样式

  • 利用transition实现平滑翻页效果

1.6 书影与内容样式

  • 设置大小、位置、背景等
  • 与书页位置相关联

1.7 3D场景构建

  • perspective属性开启3D场景
  • transform-style开启子元素3D转换

css代码如下:

css 复制代码
*{
    margin: 0;
    padding: 0;
    border: 0;
    box-sizing: border-box;
  }
  
  html{
    height: 100%;
  }
  body{
    height: 100%;
    font: 100%/1.25 Helvetica, arial, helvetica;
    color: #fff;
    perspective: 1000px;
    background: linear-gradient(to bottom, #444, #999);
  }
  
  .book{
    width: 300px;
    height: 300px;
    position: absolute;
    left: 50%;
    margin-left: -150px;
    top: 50%;
    margin-top: -150px;
    cursor: pointer;
    user-select: none;
    transform: rotateX(60deg);
  }
  
  .page{
    width: 300px;
    height: 300px;
    padding: 1em;
    position: absolute;
    left: 0;
    top: 0;
    text-indent: 2em;
  }
  .front{
    background-color: rgb(50, 86, 163);
  }
  .back{
    background-color: #fff;
  }
  .front-cover{
    transform-origin: 0 50%;
    transform: rotateY(0deg);
  }
  .p3d{
    transform-style: preserve-3d;
  }
  .front-cover .back{
    background-image: url(https://ts1.cn.mm.bing.net/th/id/R-C.b130c87b75a17febd7f7ff8934238178?rik=sVqYsZWZ9sOmVA&riu=http%3a%2f%2fpic.baike.soso.com%2fp%2f20131221%2f20131221174024-912240027.jpg&ehk=TiyBsTIzWYVfwoi8BrKTPVZeble44GPuqAXOrQdC2Hk%3d&risl=&pid=ImgRaw&r=0);
    background-size: cover; /* 将背景图片缩放以完全包含在元素内 */
      transform: translateZ(3px);
  }
  .flip{
    transform: rotateY(180deg);
  }
  
  .shadow,
  .pic{
    width: 196px;
    height: 132px;
    position: absolute;
    left: 60px;
    top: 60px;
    transform-origin: 0 100%;
  }
  
  .pic{
    background: url(https://tse1-mm.cn.bing.net/th/id/OIP-C.YptwK-yAXqzoIxrBYtbc2wAAAA?rs=1&pid=ImgDetMain);
    background-size: cover;
  }
  .shadow{
    background-color: rgba(0, 0, 0, 0.5);
  }

3. JavaScript 实现 3D翻页效果

在使用JavaScript实现3D翻页效果主要分为以下几个步骤:

  • 创建监听鼠标事件,定义公式计算旋转角度
  • 控制元素的transform角度产生3D旋转效果
  • 配合CSS动画实现翻书交互

js代码如下:

xml 复制代码
<script>
    // 鼠标摁住事件
    // 鼠标移动事件
    var hold = false
    var page = document.querySelector('.front-cover')
    var pic = document.querySelector('.pic')
    var shadow = document.querySelector('.shadow')
    var clamp = function(val, min, max) {
      return Math.max(min, Math.min(val, max))
    }


    page.onmousedown = function () {
      hold = true
    }
    window.onmouseup = function () {
      hold = false
    }

    window.onmousemove = function(e) {  // 摁住才能执行
      if (hold == true) {
        console.log(e.pageX);
        var angle = clamp((window.innerWidth / 2 - e.pageX + 300) / 300 * -90, -180, 0)

        page.style.transform = `rotateY(${angle}deg)`

        // pic 要立起来 饶x轴旋转  angle / 2
        pic.style.transform = `rotateX(${angle / 2}deg)`
        // shadow 要倾斜x  angle / 8
        shadow.style.transform = `skewX(${angle / 8}deg)`

      }
    }

  </script>

4. 项目运行效果

5. 项目源码

html 与js 部分:index.html

xml 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link rel="stylesheet" href="./style.css">
</head>
<body>
  <div class="book p3d">

    <div class="book-cover p3d">
      <div class="page back flip"></div>
      <div class="page front p3d">
        <div class="shadow"></div>
        <div class="pic"></div>
      </div>
    </div>

    <div class="front-cover p3d">
      <div class="page front flip p3d">
        <p>
          亲爱的客户们,

          我代表整个团队向您送上最诚挚的祝福!感谢您选择我们作为您的汽车销售伙伴。您的信任和支持对我们来说意义重大。
          
          在您选择购买一辆汽车时,我们深知这是一项重要的决定。因此,我们始终致力于为您提供最优质的产品和最专业的服务。您的满意是我们工作的最大动力,也是我们最大的荣耀。
        
          
          再次感谢您选择我们,成为我们的尊贵客户。我们期待着与您共同开启汽车之旅,为您提供无与伦比的驾驶体验。
          
          祝您驾驶愉快,一路平安!
        </p>
      </div>

      <div class="page back"></div>
    </div>

  </div>

  <script>
    // 鼠标摁住事件
    // 鼠标移动事件
    var hold = false
    var page = document.querySelector('.front-cover')
    var pic = document.querySelector('.pic')
    var shadow = document.querySelector('.shadow')
    var clamp = function(val, min, max) {
      return Math.max(min, Math.min(val, max))
    }


    page.onmousedown = function () {
      hold = true
    }
    window.onmouseup = function () {
      hold = false
    }

    window.onmousemove = function(e) {  // 摁住才能执行
      if (hold == true) {
        console.log(e.pageX);
        var angle = clamp((window.innerWidth / 2 - e.pageX + 300) / 300 * -90, -180, 0)

        page.style.transform = `rotateY(${angle}deg)`

        // pic 要立起来 饶x轴旋转  angle / 2
        pic.style.transform = `rotateX(${angle / 2}deg)`
        // shadow 要倾斜x  angle / 8
        shadow.style.transform = `skewX(${angle / 8}deg)`

      }
    }

  </script>
</body>
</html>

css 部分 : style.css

css 复制代码
*{
    margin: 0;
    padding: 0;
    border: 0;
    box-sizing: border-box;
  }
  
  html{
    height: 100%;
  }
  body{
    height: 100%;
    font: 100%/1.25 Helvetica, arial, helvetica;
    color: #fff;
    perspective: 1000px;
    background: linear-gradient(to bottom, #444, #999);
  }
  
  .book{
    width: 300px;
    height: 300px;
    position: absolute;
    left: 50%;
    margin-left: -150px;
    top: 50%;
    margin-top: -150px;
    cursor: pointer;
    user-select: none;
    transform: rotateX(60deg);
  }
  
  .page{
    width: 300px;
    height: 300px;
    padding: 1em;
    position: absolute;
    left: 0;
    top: 0;
    text-indent: 2em;
  }
  .front{
    background-color: rgb(50, 86, 163);
  }
  .back{
    background-color: #fff;
  }
  .front-cover{
    transform-origin: 0 50%;
    transform: rotateY(0deg);
  }
  .p3d{
    transform-style: preserve-3d;
  }
  .front-cover .back{
    background-image: url(https://ts1.cn.mm.bing.net/th/id/R-C.b130c87b75a17febd7f7ff8934238178?rik=sVqYsZWZ9sOmVA&riu=http%3a%2f%2fpic.baike.soso.com%2fp%2f20131221%2f20131221174024-912240027.jpg&ehk=TiyBsTIzWYVfwoi8BrKTPVZeble44GPuqAXOrQdC2Hk%3d&risl=&pid=ImgRaw&r=0);
    background-size: cover; /* 将背景图片缩放以完全包含在元素内 */
      transform: translateZ(3px);
  }
  .flip{
    transform: rotateY(180deg);
  }
  
  .shadow,
  .pic{
    width: 196px;
    height: 132px;
    position: absolute;
    left: 60px;
    top: 60px;
    transform-origin: 0 100%;
  }
  
  .pic{
    background: url(https://tse1-mm.cn.bing.net/th/id/OIP-C.YptwK-yAXqzoIxrBYtbc2wAAAA?rs=1&pid=ImgDetMain);
    background-size: cover;
  }
  .shadow{
    background-color: rgba(0, 0, 0, 0.5);
  }
相关推荐
kovli20 分钟前
红宝书第十一讲:超易懂版「ES6类与继承」零基础教程:用现实例子+图解实现
前端·javascript
eason_fan20 分钟前
解决nvm安装指定版本node失败的方法
前端·node.js
bst@微胖子21 分钟前
Flutter项目之设置页
android·javascript·flutter
作业逆流成河23 分钟前
🔥🔥🔥 enum-plus:前端福利!介绍一个天花板级的前端枚举库
前端
尘寰ya31 分钟前
如何实现一个“纯净”的空对象(无原型链属性)?
javascript·面试·原型模式
DXM052132 分钟前
牟乃夏《ArcGIS Engine地理信息系统开发教程》学习笔记2
开发语言·javascript·笔记·学习·arcgis·ae
D哈迪斯38 分钟前
vue动态组件实现动态表单的方法
前端·javascript·vue.js
KeyNG_Jykxg38 分钟前
🎨Element Plus X 上新! 组件升级🥳
前端·javascript·vue.js
Monly2144 分钟前
Uniapp:列表选择提示框
开发语言·javascript·uni-app
火星思想1 小时前
React为何选择宏任务而非微任务进行任务调度?
前端