css:灵动岛

299 阅读1分钟

一、效果

动图.gif

二、实现代码

<!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>灵动岛</title>
    <style>
      :root {
        --color: deeppink;
        --time: 800ms;
      }
      body {
        margin: 0;
        height: 100vh;
      }
      .wrapper {
        height: 100%;
        background-size: contain;
        background-repeat: no-repeat;
        background-image: url(https://www.apple.com.cn/v/iphone-14-pro/a/images/overview/dynamic-island/dynamic_hw__btl4fomgspyu_large.png);
        display: flex;
        flex-direction: column;
        align-items: center;
      }
      .wrapper .capsule {
        display: inline-block;
        width: 104px;
        height: 29px;
        background-color: var(--color);
        border-radius: 15px;
        margin-top: 28px;
        position: relative;
      }
      .longer {
        animation: longer var(--time) ease-in-out forwards;
      }

      /* 变长 */
      @keyframes longer {
        0% {
        }
        60% {
          width: 208px;
        }
        80% {
          transform: scaleX(1.06);
        }
        100% {
          transform: scaleX(1);
          width: 208px;
        }
      }

      .wrapper .capsule::after {
        content: '';
        width: 29px;
        height: 29px;
        position: absolute;
        right: 0;
        border-radius: 50%;
        background-color: var(--color);
      }

      .separate {
        animation: separate-left var(--time) ease-in-out forwards;
      }
      .separate::after {
        animation: separate-right var(--time) ease-in-out forwards;
      }

      /* 分离 */
      @keyframes separate-left {
        0% {
          width: 208px;
        }
        40% {
          transform: scaleX(1.06);
          width: 104px;
        }
        100% {
          transform: scaleX(1);
          width: 104px;
        }
      }

      @keyframes separate-right {
        0% {
          right: 0;
        }
        40% {
          transform: scaleX(1.06);
        }
        100% {
          transform: scaleX(1);
          right: -35px;
        }
      }

      /* 合并 */
      .merge {
        animation: merge-left var(--time) ease-in-out forwards;
      }
      .merge:after {
        animation: merge-right var(--time) ease-in-out forwards;
      }

      @keyframes merge-left {
        0% {
        }
        40% {
          transform: scaleX(1.06);
        }
        100% {
          transform: scaleX(1);
        }
      }

      @keyframes merge-right {
        0% {
          right: -35px;
        }
        40% {
          transform: scaleX(1.06);
        }
        100% {
          transform: scaleX(1);
          right: 29px;
        }
      }

      /* 变大-1 */
      .larger-1 {
        animation: larger-1 var(--time) ease-in-out forwards;
      }
      @keyframes larger-1 {
        0% {
        }
        60% {
          width: 298px;
          height: 44px;
          border-radius: 20px;
        }
        80% {
          transform: scaleX(1.04);
        }
        100% {
          transform: scaleX(1);
          width: 298px;
          height: 44px;
          border-radius: 20px;
        }
      }

      /* 变大-2 */
      .larger-2 {
        animation: larger-2 var(--time) ease-in-out forwards;
      }
      @keyframes larger-2 {
        0% {
        }
        60% {
          width: 298px;
          height: 160px;
          border-radius: 40px;
        }
        80% {
          transform: scaleX(1.04);
        }
        100% {
          transform: scaleX(1);
          width: 298px;
          height: 168px;
          border-radius: 40px;
        }
      }
    </style>
  </head>
  <body>
    <div class="wrapper">
      <div class="capsule"></div>
      <div class="btn-wrapper">
        <button id="longerBtn">变长</button>
        <button id="separateBtn">分离</button>
        <button id="mergeBtn">合并</button>
        <button id="larger1Btn">变大1</button>
        <button id="larger2Btn">变大2</button>
        <button id="reset">重置</button>
        <button id="start">start</button>
      </div>
    </div>
    <script>
      const capsule = document.querySelector('.capsule')
      const longerBtn = document.querySelector('#longerBtn')
      const separateBtn = document.querySelector('#separateBtn')
      const mergeBtn = document.querySelector('#mergeBtn')
      const larger1Btn = document.querySelector('#larger1Btn')
      const larger2Btn = document.querySelector('#larger2Btn')
      const reset = document.querySelector('#reset')
      const start = document.querySelector('#start')
      longerBtn.addEventListener('click', function () {
        capsule.classList.add('longer')
      })

      separateBtn.addEventListener('click', function () {
        capsule.classList.add('separate')
      })

      mergeBtn.addEventListener('click', function () {
        capsule.classList.add('merge')
      })

      larger1Btn.addEventListener('click', function () {
        capsule.classList.add('larger-1')
      })

      larger2Btn.addEventListener('click', function () {
        capsule.classList.add('larger-2')
      })

      reset.addEventListener('click', handleReset)
      function handleReset() {
        // capsule.classList.remove('longer')
        // capsule.classList.remove('separate')
        // capsule.classList.remove('merge')
        // capsule.classList.remove('larger-1')
        // capsule.classList.remove('larger-2')
        capsule.classList = 'capsule'
      }

      const animationList = [
        'longer',
        'separate',
        'merge',
        'larger-1',
        'larger-2'
      ]
      let index = 0
      let isStart = false
      start.addEventListener('click', function () {
        capsule.classList.add(animationList[index])
        isStart = true
      })

      capsule.addEventListener('animationend', function (e) {
        if (!isStart) return
        if (['separate-right', 'merge-right'].includes(e.animationName)) {
          return
        }
        index++
        let timer = setTimeout(() => {
          if (index <= animationList.length - 1) {
            capsule.classList.add(animationList[index])
          } else {
            index = 0
            isStart = false
            handleReset()
            timer = null
          }
        }, 800)
      })
    </script>
  </body>
</html>

三、思路总结

  1. js可以通过事件对整个动画流程做控制,单个动画的渲染优先选择css3
  2. 灵动岛动画会有一种超出边界继续放大接着又收缩的效果,在选择css3动画时优先考虑animation,animation可以使用百分比定义动画每个阶段的样式,这样我们便可以在80%时放大元素transform: scaleX(1.04),在100%时恢复大小
  3. 多个动画挨个执行,这里对执行动画的元素进行animationend监听,这里可以追加下一个动画直到结束

四、transition如何做超出边界继续放大接着又收缩的效果

    <style>
      .item {
        width: 100px;
        height: 30px;
        background-color: red;
        border-radius: 15px;
        transition: width 0.8s ease-in-out;
      }
      .longer {
        width: 200px;
      }
    </style>
  
  <body>
    <div class="item"></div>
    <button>重置</button>
    <script>
      const item = document.querySelector('.item')
      const button = document.querySelector('button')
      item.addEventListener('click', function () {
        item.classList.add('longer')
      })

      button.addEventListener('click', function () {
        item.classList = 'item'
      })
    </script>
  </body>

手动获取凡尔赛曲线:cubic-bezier(0.64, -0.78, 0.39, 1.73) 动图.gif