如何让hover的3D Plane元素平滑移动到屏幕中心后停止?
实现3D元素Hover时平滑移动到屏幕中心的方案
需求说明
复刻3D旋转对象,已实现hover暂停旋转,但需要优化交互:鼠标hover到任意plane元素时,该元素从当前位置平滑移动到屏幕中心后停止,而非原地暂停;鼠标移出后,元素平滑回到原位置,同时恢复旋转动画。
实现步骤
- 暂停旋转并标记激活元素:hover时暂停
#shape的旋转动画,同时给当前hover的plane添加.active类 - 计算中心位置的transform:基于屏幕中心和plane自身尺寸,计算出目标位置的
translate3d值,结合原有3D变换,确保元素移动到视觉中心 - CSS过渡控制平滑动画:利用plane已有的
transition属性,实现变换的平滑过渡 - 处理复位逻辑:鼠标移出时移除
.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
相关产品推荐
相关产品推荐

