如何选择特定元素四周的所有元素?类似has()选择器全方向生效
实现悬停元素全方向(上下左右)相邻元素特效的方案
由于CSS选择器无法直接定位flex换行后的上下元素(它们属于同一父元素的平级子元素,但不在视觉同一行,没有原生CSS选择器能直接关联),所以必须通过JavaScript计算元素视觉位置,找到目标元素的上下左右相邻元素,再通过类来应用样式。以下是具体实现:
步骤1:重构CSS样式,用类替代原生hover选择器
把原来基于:hover和相邻兄弟选择器的样式改成类选择器,方便JS动态控制:
* { margin: 0; padding: 0; box-sizing: border-box; } body { display: flex; justify-content: center; align-items: center; height: 100vh; } #container { display: flex; justify-content: center; align-items: center; flex-wrap: wrap; gap: 5px; width: 100%; perspective: 1000px; } .box { background-color: red; height: 50px; width: 50px; transform-style: preserve-3d; transition: transform 0.5s, filter 0.3s; } /* 自身悬停样式 */ .box.hover-target { transform: translateZ(150px); filter: brightness(1); background-color: #87F1FF; } /* 右侧元素层级样式 */ .box.hover-right-1 { transform: translateZ(100px) rotateY(60deg); filter: brightness(0.8); } .box.hover-right-2 { transform: translateZ(50px) rotateY(30deg); filter: brightness(0.6); } .box.hover-right-3 { transform: translateZ(25px) rotateY(15deg); filter: brightness(0.4); } /* 左侧元素层级样式 */ .box.hover-left-1 { transform: translateZ(100px) rotateY(-60deg); filter: brightness(0.8); } .box.hover-left-2 { transform: translateZ(50px) rotateY(-30deg); filter: brightness(0.6); } .box.hover-left-3 { transform: translateZ(25px) rotateY(-15deg); filter: brightness(0.4); } /* 上方元素层级样式 */ .box.hover-up-1 { transform: translateZ(100px) rotateX(60deg); filter: brightness(0.8); } .box.hover-up-2 { transform: translateZ(50px) rotateX(30deg); filter: brightness(0.6); } /* 下方元素层级样式 */ .box.hover-down-1 { transform: translateZ(100px) rotateX(-60deg); filter: brightness(0.8); } .box.hover-down-2 { transform: translateZ(50px) rotateX(-30deg); filter: brightness(0.6); }
步骤2:修改JavaScript,实现元素定位与类控制
通过计算每行容纳的box数量,找到目标元素的上下左右对应索引,动态添加/移除样式类:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Hovering Boxes</title> <link rel="stylesheet" href="style.css"> </head> <body> <div id="container"></div> <script> const container = document.getElementById('container'); const boxCount = 100; const boxSize = 50; // box的宽高 const gap = 5; // box之间的间距 let columns = 0; let boxes = []; // 初始化box function initBoxes() { container.innerHTML = ''; boxes = []; // 计算当前每行能容纳的box数量 const containerWidth = container.offsetWidth; columns = Math.floor(containerWidth / (boxSize + gap)); for(let i = 0; i < boxCount; i++) { const box = document.createElement('div'); box.className = 'box'; box.dataset.index = i; // 给每个box添加索引标记 container.appendChild(box); boxes.push(box); // 绑定事件 box.addEventListener('mouseenter', handleMouseEnter); box.addEventListener('mouseleave', handleMouseLeave); } } // 鼠标进入时添加样式类 function handleMouseEnter(e) { const targetIndex = parseInt(e.target.dataset.index); // 标记自身为悬停目标 e.target.classList.add('hover-target'); // 处理右侧元素 for(let i = 1; i <=3; i++) { const rightIndex = targetIndex + i; // 确保右侧元素和目标元素在同一行 if(Math.floor(rightIndex / columns) === Math.floor(targetIndex / columns) && rightIndex < boxCount) { boxes[rightIndex].classList.add(`hover-right-${i}`); } } // 处理左侧元素 for(let i = 1; i <=3; i++) { const leftIndex = targetIndex - i; // 确保左侧元素和目标元素在同一行且索引有效 if(leftIndex >=0 && Math.floor(leftIndex / columns) === Math.floor(targetIndex / columns)) { boxes[leftIndex].classList.add(`hover-left-${i}`); } } // 处理上方元素 for(let i = 1; i <=2; i++) { const upIndex = targetIndex - columns * i; if(upIndex >=0) { boxes[upIndex].classList.add(`hover-up-${i}`); } } // 处理下方元素 for(let i = 1; i <=2; i++) { const downIndex = targetIndex + columns * i; if(downIndex < boxCount) { boxes[downIndex].classList.add(`hover-down-${i}`); } } } // 鼠标离开时移除所有样式类 function handleMouseLeave(e) { const targetIndex = parseInt(e.target.dataset.index); // 移除自身标记 e.target.classList.remove('hover-target'); // 移除右侧元素类 for(let i = 1; i <=3; i++) { const rightIndex = targetIndex + i; if(Math.floor(rightIndex / columns) === Math.floor(targetIndex / columns) && rightIndex < boxCount) { boxes[rightIndex].classList.remove(`hover-right-${i}`); } } // 移除左侧元素类 for(let i = 1; i <=3; i++) { const leftIndex = targetIndex - i; if(leftIndex >=0 && Math.floor(leftIndex / columns) === Math.floor(targetIndex / columns)) { boxes[leftIndex].classList.remove(`hover-left-${i}`); } } // 移除上方元素类 for(let i = 1; i <=2; i++) { const upIndex = targetIndex - columns * i; if(upIndex >=0) { boxes[upIndex].classList.remove(`hover-up-${i}`); } } // 移除下方元素类 for(let i = 1; i <=2; i++) { const downIndex = targetIndex + columns * i; if(downIndex < boxCount) { boxes[downIndex].classList.remove(`hover-down-${i}`); } } } // 窗口大小变化时重新初始化,适配不同宽度 window.addEventListener('resize', initBoxes); // 初始化 initBoxes(); </script> </body> </html>
方案说明
- 列数计算:通过容器宽度除以单个box的总占用宽度(box宽+gap),得到每行容纳的box数量,这是定位上下元素的核心依据。
- 索引定位:利用box的索引,上方元素索引=当前索引-列数,下方=当前索引+列数;左右元素需判断是否在同一行(通过索引除以列数的整数部分是否相等来验证)。
- 响应式适配:监听窗口resize事件,重新计算列数并初始化box,保证不同屏幕宽度下都能正确定位元素。
- 样式解耦:用类替代原生hover选择器,JS仅负责类的添加移除,样式逻辑完全留在CSS中,便于后续维护调整。
内容的提问来源于stack exchange,提问作者Satyadeep Gohil
相关产品推荐
相关产品推荐

