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

如何选择特定元素四周的所有元素?类似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>

方案说明

  1. 列数计算:通过容器宽度除以单个box的总占用宽度(box宽+gap),得到每行容纳的box数量,这是定位上下元素的核心依据。
  2. 索引定位:利用box的索引,上方元素索引=当前索引-列数,下方=当前索引+列数;左右元素需判断是否在同一行(通过索引除以列数的整数部分是否相等来验证)。
  3. 响应式适配:监听窗口resize事件,重新计算列数并初始化box,保证不同屏幕宽度下都能正确定位元素。
  4. 样式解耦:用类替代原生hover选择器,JS仅负责类的添加移除,样式逻辑完全留在CSS中,便于后续维护调整。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 03:28:13