学新通技术网

嘿嘿!让我用 CSS 来实现下 iPhone 14 Pro 灵动岛

juejin 152 1
嘿嘿!让我用 CSS 来实现下 iPhone 14 Pro 灵动岛

前言

最近 iPhone 14 Pro 的灵动岛很火,我个人觉得这个设计算是挺巧妙的,用软件层面掩盖硬件层面的缺陷

今天我们就来用CSS来还原一下灵动岛吧,就以来电状态下的灵动岛为例尝试进行还原

手机本体

我们首先查看一下手机的宽高比参数

image.png

可以看到是71.5:147.5,假设我们将高度定为600px,那么换算到对应的宽度就是291px,然后手机颜色我们就用新出的暗紫色吧,根据广告图慢慢调整样式即可

<!-- 手机本体 -->
<div class="iPhone-14-pro">
  <!-- 手机屏幕 -->
  <div class="screen">
    <div class="wrapper">
      <!-- 灵动岛 -->
      <div class="dynamic-island"></div>
    </div>
  </div>
</div>
/* 手机本体 */
.iPhone-14-pro {
  position: relative;
  width: 291px;
  height: 600px;
  background-color: #998c9e;
  border-radius: 48px;
  overflow: hidden;
}

/* 手机屏幕 */
.iPhone-14-pro .screen {
  position: absolute;
  inset: 2px;
  background-color: black;
  border-radius: 48px;
  /* 边框 */
  padding: 10px;
}

/* 屏幕内容 */
.iPhone-14-pro .screen .wrapper {
  width: 100%;
  height: 100%;
  border-radius: 40px;
  background: url(https://cdn.ytechb.com/wp-content/uploads/2022/09/iPhone-14-Pro-Wallpaper-purple.webp);
  background-size: cover;
}

目前效果如下图所示:

image.png

有点内味儿了吧?(要是以后的iPhone真的就长这样多好)

手机两侧的按键

现在我们只还原了手机本体,但是两侧的按键还没弄上去,iPhone的左侧从上到下依次是静音键、音量增加键、音量减小键,右侧则是一个电源键,我们通过绝对定位的方式将它们加上

<!-- 手机本体 -->
<div class="iPhone-14-pro">
  <!-- 手机屏幕 -->
  <div class="screen">
    <div class="wrapper">
      <!-- 灵动岛 -->
      <div class="dynamic-island"></div>
    </div>
  </div>

  <!-- 静音键 -->
  <div class="mute-btn"></div>

  <!-- 音量增大键 -->
  <div class="volume-up-btn"></div>

  <!-- 音量减小键 -->
  <div class="volume-down-btn"></div>

  <!-- 电源键 -->
  <div class="power-btn"></div>
</div>
/* 静音键 */
.mute-btn {
  position: absolute;
  top: 100px;
  left: -3px;
  height: 20px;
  width: 3px;
  background: radial-gradient(#ccc, #666, #222);
}

/* 音量增大键 */
.volume-up-btn {
  position: absolute;
  top: 145px;
  left: -3px;
  height: 40px;
  width: 3px;
  background: radial-gradient(#ccc, #666, #222);
}

/* 音量减小键 */
.volume-down-btn {
  position: absolute;
  top: 205px;
  left: -3px;
  height: 40px;
  width: 3px;
  background: radial-gradient(#ccc, #666, #222);
}

/* 电源键 */
.power-btn {
  position: absolute;
  top: 175px;
  right: -3px;
  height: 70px;
  width: 3px;
  background: radial-gradient(#ccc, #666, #222);
}

现在的效果如下:

image.png

灵动岛

常规状态下的灵动岛

接下来到我们的重头戏了,先把一个常规状态下的灵动岛给弄出来

/* 灵动岛 */
.dynamic-island {
  position: absolute;
  width: 85px;
  height: 25px;
  left: 50%;
  top: 20px;
  transform: translateX(-50%);
  background: black;
  border-radius: 20px;
}

image.png

鼠标悬浮时的灵动岛

这里为了方便,我们用鼠标悬浮来模拟收到来电的状态,然后点击后灵动岛会变大,并且能够看到来电人的头像

首先先实现一下鼠标悬浮后灵动岛的特效,也就是岛的宽度变长,并且出现来电信息和挂断或者接听电话的按钮

先把html结构调整一下

<!-- 手机本体 -->
<div class="iPhone-14-pro">
  <!-- 手机屏幕 -->
  <div class="screen">
    <div class="wrapper">
      <!-- 灵动岛 -->
      <div class="dynamic-island">
        <!-- 来电人信息 -->
        <div class="caller">
          <!-- 头像 -->
          <div class="avatar"></div>

          <!-- 信息 -->
          <div class="info">
            <span>iPhone</span>
            <p>库克</p>
          </div>
        </div>

        <!-- 接听和挂断电话按钮 -->
        <div class="actions">
          <!-- 挂断电话 -->
          <div class="refuse">
            <ion-icon name="call"></ion-icon>
          </div>

          <!-- 接听电话 -->
          <div class="answer">
            <ion-icon name="call"></ion-icon>
          </div>
        </div>
      </div>
    </div>
  </div>

  <!-- 静音键 -->
  <div class="mute-btn"></div>

  <!-- 音量增大键 -->
  <div class="volume-up-btn"></div>

  <!-- 音量减小键 -->
  <div class="volume-down-btn"></div>

  <!-- 电源键 -->
  <div class="power-btn"></div>
</div>

然后添加上相关初始状态的样式和过渡动画

/* 灵动岛 */
.dynamic-island {
  display: flex;
  justify-content: space-between;
  align-items: center;
  position: absolute;
  width: 85px;
  height: 25px;
  left: 50%;
  top: 20px;
  padding: 0 10px;
  transform: translateX(-50%);
  background: black;
  border-radius: 20px;
  transition: 0.5s ease-in-out;
  cursor: default;
  user-select: none;
}

/* 来电人信息 */
.dynamic-island .caller {
  color: white;
  visibility: hidden;
  opacity: 0;
  transition: 0.5s;
}

.dynamic-island .caller .avatar {
  display: none;
}

.dynamic-island .caller .info span {
  display: none;
}
.dynamic-island .caller .info p {
  font-size: 8px;
}

/* 接听和挂断电话按钮 */
.dynamic-island .actions {
  display: flex;
  gap: 12px;
  visibility: hidden;
  opacity: 0;
  transition: 0.5s;
}

.dynamic-island .actions .refuse {
  color: #ff4438;
  transform: rotate(135deg);
}
.dynamic-island .actions .answer {
  color: #30d258;
}

/* 鼠标悬浮时灵动岛的状态 */
.dynamic-island:hover {
  width: 200px;
}

.dynamic-island:hover .caller {
  visibility: visible;
  opacity: 1;
}

.dynamic-island:hover .actions {
  visibility: visible;
  opacity: 1;
}

具体思路就是先定义悬浮之前,各个元素的初始样式,有的元素在未展开时是不需要显示的,我们就要将它们的visibility设置为hidden,又因为要考虑到元素出现和消失有一个过渡的效果,所以初始时还要将opacity设置为0,即不可见,然后再在悬浮时将其设置为1,让其可见度变为完全可见,这样transition就能有一个过渡动画的效果了

现在效果如下:

灵动岛鼠标悬浮.gif

点击灵动岛后展开详情

点击后整个灵动岛处于一个展开的状态,这里就涉及到状态的变更了,由hover状态变更为展开状态,由于悬浮的逻辑我们可以通过css的伪选择器:hover去实现,就不需要用到js去记录状态

但是展开这个状态不能用纯css实现了,涉及到点击事件的处理,所以我们得编写一个简单的js去记录展开状态,并且css要编写展开状态下的样式,尽量让逻辑和样式之间的职责划分清楚,这样后面遇到问题的概率也会小一些,即便遇到了问题也会相对好维护

(() => {
  const oDynamicIsland = document.getElementById("dynamic-island");
  const oScreenWrapper = document.getElementById("screen-wrapper");

  const init = () => {
    bindEvent();
  };

  const bindEvent = () => {
    // 点击灵动岛时将灵动岛状态变更为展开状态 -- 通过添加一个 .expanded 类名来记录展开状态
    oDynamicIsland.addEventListener("click", (e) => {
      // 阻止事件冒泡到屏幕区域的点击事件监听器中
      // 否则每次点击都会被外层的屏幕元素的点击事件监听器移除 .expanded 类名
      e.stopPropagation();
      oDynamicIsland.classList.add("expanded");
    });

    // 点击屏幕内其他区域时取消灵动岛的展开状态
    oScreenWrapper.addEventListener("click", () => {
      oDynamicIsland.classList.remove("expanded");
    });
  };

  init();
})();

状态的记录逻辑已经解决了,接下来我们只用专心编写展开状态下的样式即可,不需要再理会js部分,这也是职责划分清楚的一个好处

/* 灵动岛展开状态 */
.dynamic-island.expanded {
  width: 200px;
  height: 50px;
}

.dynamic-island.expanded .caller {
  visibility: visible;
  opacity: 1;
}
.dynamic-island.expanded .caller .avatar {
  width: 30px;
  height: 30px;
  margin-right: 8px;
}

.dynamic-island.expanded .caller .info span {
  visibility: visible;
  line-height: 8px;
  opacity: 1;
}

.dynamic-island.expanded .actions {
  visibility: visible;
  opacity: 1;
  transition-delay: 0;
}
.dynamic-island.expanded .actions .refuse {
  color: white;
  background-color: #ff4438;
}
.dynamic-island.expanded .actions .answer {
  color: white;
  background-color: #30d258;
}

具体的样式代码不复杂,就不过多解释了,直接看看效果吧

灵动岛展开.gif

加上息屏特效

当鼠标离开屏幕区域时,我们让屏幕变黑,模拟一个息屏的效果,这个很简单,只需要给wrapper设置默认的opacity为0,而当鼠标悬浮时改为1,再加上transition即可

 
.iPhone-14-pro .screen .wrapper {
  width: 100%;
  height: 100%;
  border-radius: 40px;
  background: url(https://cdn.ytechb.com/wp-content/uploads/2022/09/iPhone-14-Pro-Wallpaper-purple.webp);
  background-size: cover;
  opacity: 0;
  transition: 0.5s;
}
.iPhone-14-pro .screen .wrapper:hover {
  opacity: 1;
}

最终效果如下:
😀CSS 实现 iPhone 14 Pro 灵动岛

本文出至:学新通技术网

标签: