You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让hover的3D Plane元素平滑移动到屏幕中心后停止?

实现3D元素Hover时平滑移动到屏幕中心的方案

需求说明

复刻3D旋转对象,已实现hover暂停旋转,但需要优化交互:鼠标hover到任意plane元素时,该元素从当前位置平滑移动到屏幕中心后停止,而非原地暂停;鼠标移出后,元素平滑回到原位置,同时恢复旋转动画。

实现步骤

  1. 暂停旋转并标记激活元素:hover时暂停#shape的旋转动画,同时给当前hover的plane添加.active类
  2. 计算中心位置的transform:基于屏幕中心和plane自身尺寸,计算出目标位置的translate3d值,结合原有3D变换,确保元素移动到视觉中心
  3. CSS过渡控制平滑动画:利用plane已有的transition属性,实现变换的平滑过渡
  4. 处理复位逻辑:鼠标移出时移除.active类,恢复plane原位置,同时重启旋转动画

修改后的代码

CSS 部分

body {
  background-color: black;
  font-size: 12px;
}

#stage {
  width: 100%;
  height: 100%;
  -webkit-transition: -webkit-transform 2s;
  -webkit-transform-style: preserve-3d;
}

#shape {
  position: relative;
  top: 160px;
  margin: 0 auto;
  height: 200px;
  width: 200px;
  -webkit-transform-style: preserve-3d;
  -webkit-animation: spin 8s infinite linear;
}

.plane {
  position: absolute;
  height: 200px;
  width: 200px;
  border: 1px solid white;
  -webkit-border-radius: 12px;
  -webkit-box-sizing: border-box;
  text-align: center;
  vertical-align: middle;
  line-height: 200px;
  font-family: Times, serif;
  font-size: 350%;
  color: black;
  background-color: rgba(255, 255, 255, 0.6);
  -webkit-transition: -webkit-transform 0.8s ease, opacity 0.8s ease;
  z-index: 1;
}

#shape.backfaces .plane {
  -webkit-backface-visibility: visible;
}

@-webkit-keyframes spin {
  from { -webkit-transform: rotateY(0); }
  to   { -webkit-transform: rotateY(-360deg); }
}

/* Cube 样式保持不变 */
.cube > .one {
  opacity: 0.5;
  -webkit-transform: scale3d(1.2, 1.2, 1.2) rotateX(90deg) translateZ(100px);
}
.cube > .two {
  opacity: 0.5;
  -webkit-transform: scale3d(1.2, 1.2, 1.2) translateZ(100px);
}
.cube > .three {
  opacity: 0.5;
  -webkit-transform: scale3d(1.2, 1.2, 1.2) rotateY(90deg) translateZ(100px);
}
.cube > .four {
  opacity: 0.5;
  -webkit-transform: scale3d(1.2, 1.2, 1.2) rotateY(180deg) translateZ(100px);
}
.cube > .five {
  opacity: 0.5;
  -webkit-transform: scale3d(1.2, 1.2, 1.2) rotateY(-90deg) translateZ(100px);
}
.cube > .six {
  opacity: 0.5;
  -webkit-transform: scale3d(1.2, 1.2, 1.2) rotateX(-90deg) translateZ(100px) rotate(180deg);
}
.cube > .seven {
  -webkit-transform: scale3d(0.8, 0.8, 0.8) rotateX(90deg) translateZ(100px) rotate(180deg);
}
.cube > .eight {
  -webkit-transform: scale3d(0.8, 0.8, 0.8) translateZ(100px);
}
.cube > .nine {
  -webkit-transform: scale3d(0.8, 0.8, 0.8) rotateY(90deg) translateZ(100px);
}
.cube > .ten {
  -webkit-transform: scale3d(0.8, 0.8, 0.8) rotateY(180deg) translateZ(100px);
}
.cube > .eleven {
  -webkit-transform: scale3d(0.8, 0.8, 0.8) rotateY(-90deg) translateZ(100px);
}
.cube > .twelve {
  -webkit-transform: scale3d(0.8, 0.8, 0.8) rotateX(-90deg) translateZ(100px);
}

/* Ring 样式保持不变 */
.ring > .one {
  -webkit-transform: translateZ(380px);
}
.ring > .two {
  -webkit-transform: rotateY(30deg) translateZ(380px);
}
.ring > .three {
  -webkit-transform: rotateY(60deg) translateZ(380px);
}
.ring > .four {
  -webkit-transform: rotateY(90deg) translateZ(380px);
}
.ring > .five {
  -webkit-transform: rotateY(120deg) translateZ(380px);
}
.ring > .six {
  -webkit-transform: rotateY(150deg) translateZ(380px);
}
.ring > .seven {
  -webkit-transform: rotateY(180deg) translateZ(380px);
}
.ring > .eight {
  -webkit-transform: rotateY(210deg) translateZ(380px);
}
.ring > .nine {
  -webkit-transform: rotateY(-120deg) translateZ(380px);
}
.ring > .ten {
  -webkit-transform: rotateY(-90deg) translateZ(380px);
}
.ring > .eleven {
  -webkit-transform: rotateY(300deg) translateZ(380px);
}
.ring > .twelve {
  -webkit-transform: rotateY(330deg) translateZ(380px);
}

/* 新增激活状态样式 */
#shape.paused {
  -webkit-animation-play-state: paused;
}
.plane.active {
  /* 计算屏幕中心位置:窗口半宽减plane半宽,窗口半高减plane半高再减shape的top值 */
  -webkit-transform: translate3d(calc(50vw - 100px), calc(50vh - 260px), 200px) !important;
  z-index: 10;
  opacity: 1 !important;
}

HTML 部分

<head>
    <link rel="stylesheet" href="styles.css">
</head>

<div id="stage">
    <div onclick="toggleShape()" id="shape" class="cube backfaces">
      <div class="plane one" onmouseover="handlePlaneHover(this)" onmouseout="handlePlaneOut(this)">$3M</div>
      <div class="plane two" onmouseover="handlePlaneHover(this)" onmouseout="handlePlaneOut(this)">$175K</div>
      <div class="plane three" onmouseover="handlePlaneHover(this)" onmouseout="handlePlaneOut(this)">2K</div>
      <div class="plane four" onmouseover="handlePlaneHover(this)" onmouseout="handlePlaneOut(this)">Thousands</div>
      <div class="plane five" onmouseover="handlePlaneHover(this)" onmouseout="handlePlaneOut(this)">30</div>
      <div class="plane six" onmouseover="handlePlaneHover(this)" onmouseout="handlePlaneOut(this)">20+</div>
      <div class="plane seven" onmouseover="handlePlaneHover(this)" onmouseout="handlePlaneOut(this)">25</div>
      <div class="plane eight" onmouseover="handlePlaneHover(this)" onmouseout="handlePlaneOut(this)">Hundreds</div>
      <div class="plane nine" onmouseover="handlePlaneHover(this)" onmouseout="handlePlaneOut(this)">Tens of Thousands</div>
      <div class="plane ten" onmouseover="handlePlaneHover(this)" onmouseout="handlePlaneOut(this)">Dozens</div>
      <div class="plane eleven" onmouseover="handlePlaneHover(this)" onmouseout="handlePlaneOut(this)">15+</div>
      <div class="plane twelve" onmouseover="handlePlaneHover(this)" onmouseout="handlePlaneOut(this)">All </div>
    </div>
  </div>

<script type="text/javascript" charset="utf-8">
    const shape = document.getElementById('shape');
    let activePlane = null;

    function hasClassName(inElement, inClassName) {
        var regExp = new RegExp('(?:^|\\s+)' + inClassName + '(?:\\s+|$)');
        return regExp.test(inElement.className);
    }

    function addClassName(inElement, inClassName) {
        if (!hasClassName(inElement, inClassName))
            inElement.className = [inElement.className, inClassName].join(' ');
    }

    function removeClassName(inElement, inClassName) {
        if (hasClassName(inElement, inClassName)) {
            var regExp = new RegExp('(?:^|\\s+)' + inClassName + '(?:\\s+|$)', 'g');
            var curClasses = inElement.className;
            inElement.className = curClasses.replace(regExp, ' ');
        }
    }
    
    function toggleShape() {
      if (hasClassName(shape, 'cube')) {
        removeClassName(shape, 'cube');
        addClassName(shape, 'ring');
      } 
      var stage = document.getElementById('stage');
      if (hasClassName(shape, 'ring'))
        stage.style.webkitTransform = 'translateZ(-200px)';
      else
        stage.style.webkitTransform = '';
    }

    // 处理plane hover事件
    function handlePlaneHover(plane) {
        addClassName(shape, 'paused');
        // 移除之前激活的plane
        if (activePlane) {
            removeClassName(activePlane, 'active');
        }
        activePlane = plane;
        addClassName(plane, 'active');
    }

    // 处理plane mouseout事件
    function handlePlaneOut() {
        removeClassName(shape, 'paused');
        if (activePlane) {
            removeClassName(activePlane, 'active');
            activePlane = null;
        }
    }
</script>

关键说明

  • .plane.active中的calc(50vw - 100px)是窗口半宽减去plane半宽,calc(50vh - 260px)是窗口半高减去plane半高和#shape的top值,确保元素视觉居中
  • 调整transition时长为0.8s提升流畅度,同时给激活的plane提高z-index避免被遮挡
  • 用.paused类替代原生:hover选择器,精准控制旋转状态,避免鼠标移出shape区域时的异常

内容的提问来源于stack exchange,提问作者zichyboy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 22:40:40