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

Netflix克隆项目本地JSON映射列表hover布局异常问题求助

问题描述

我正在开发Netflix克隆项目,将本地JSON数据映射为媒体列表组件。当前遇到的问题是:当鼠标悬浮在列表项上时,当前项会触发悬浮展开效果,但下一个列表项会占据该悬浮项的原始位置,导致布局错乱。


项目相关代码

本地JSON数据示例

export const products = [
    {
        name: 'iPhone 11',
        image: '/assets/images (7).jpeg',
        time: '1 hour 14mins',
        age: '+16',
        year: '1999',
        desc: 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Praesentium hic rem eveniet error possimus, neque ex doloribus.',
        genre: 'Action',
        id: 0,
    },
    {
        name: 'iPhone 11',
        image: '/assets/images (13).jpeg',
        time: '1 hour 14mins',
        age: '+16',
        year: '1999',
        desc: 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Praesentium hic rem eveniet error possimus, neque ex doloribus.',
        genre: 'Action',
        id: 1,
    },
    {
        name: 'iPhone 11',
        image: '/assets/images (14).jpeg',
        time: '1 hour 14mins',
        age: '+16',
        year: '1999',
        desc: 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Praesentium hic rem eveniet error possimus, neque ex doloribus.',
        genre: 'Action',
        id: 2,
    }
]

List.js 代码

import React from 'react'
import {
  ArrowBackIosOutlined,
  ArrowForwardIosOutlined,
} from "@material-ui/icons";
import { useRef, useState } from "react";
import ListItem from "../listItem/ListItem";
import "./list.scss";
import { products } from './listdata'

export default function List() {
  const [isMoved, setIsMoved] = useState(false);
  const [slideNumber, setSlideNumber] = useState(0);

  const listRef = useRef();

  const handleClick = (direction) => {
    setIsMoved(true);
    let distance = listRef.current.getBoundingClientRect().x - 50;
    if (direction === "left" && slideNumber > 0) {
      setSlideNumber(slideNumber - 1);
      listRef.current.style.transform = `translateX(${230 + distance}px)`;
    }
    if (direction === "right" && slideNumber < 5.6) {
      setSlideNumber(slideNumber + 1);
      listRef.current.style.transform = `translateX(${-230 + distance}px)`;
    }
  };
  return (
    <div className="list">
      <span className="listTitle">Continue to watch</span>
      <div className="wrapper">
        <ArrowBackIosOutlined
          className="sliderArrow left"
          onClick={() => handleClick("left")}
          style={{ display: !isMoved && "none" }}
        />
        <div className="container" ref={listRef}>
        {products.map((product) => (
          <div key={product.id}>
              <ListItem product={product} />
          </div>
          ))}
        </div>
        <ArrowForwardIosOutlined
          className="sliderArrow right"
          onClick={() => handleClick("right")}
        />
      </div>
    </div>
  );
}

ListItem.js 代码

import React from 'react'
import "./listItem.scss";
import trail from '../../assets/The Imperfects _ Official Trailer _ Netflix.mp4'
import {
  PlayArrow,
  Add,
  ThumbUpAltOutlined,
  Check,
} from "@material-ui/icons";
import { useState } from "react";

export default function ListItem({ product }) {
  const [isHovered, setIsHovered] = useState(false);
  const trailer = trail;

  return (
    <div
      className="listItem"
      style={{left: isHovered && product.id * 225 - 50 + product.id * 2.5}}
      onMouseEnter={() => setIsHovered(true)}
      onMouseLeave={() => setIsHovered(false)}
    >
      <img
        src={product.image}
        alt=""
      />
      {isHovered && (
        <>
          <video height='180px' src={trail} autoPlay={true} loop />
          <div className="itemInfo">
            <div className="icons">
              <PlayArrow className="icon" style={{color: 'black'}} />
              <Check className="icon" />
              <Add className="icon" />
              <ThumbUpAltOutlined className="icon" />
            </div>
            <div className="itemInfoTop">
              <span>{product.time}</span>
              <span className="limit">{product.age}</span>
              <span>{product.year}</span>
            </div>
            <div className="desc">
            {product.desc}
            </div>
            <div className="genre">{product.genre}</div>
          </div>
        </>
      )}
    </div>
  );
}

问题分析

布局错乱的核心原因:

  1. 悬浮时通过动态left属性移动列表项,导致当前项脱离正常布局流,后续元素会自动填补其原始位置
  2. 悬浮展开的内容没有独立的定位容器,直接插入文档流会挤压或位移其他元素

解决方案

1. 重构ListItem组件,改用绝对定位承载悬浮内容

// ListItem.js
export default function ListItem({ product }) {
  const [isHovered, setIsHovered] = useState(false);
  const trailer = trail;

  return (
    <div className="listItem">
      <img src={product.image} alt="" />
      {/* 悬浮内容容器,通过绝对定位覆盖在原列表项上方 */}
      {isHovered && (
        <div className="hoverOverlay">
          <video height='180px' src={trail} autoPlay={true} loop muted />
          <div className="itemInfo">
            <div className="icons">
              <PlayArrow className="icon" style={{color: 'black'}} />
              <Check className="icon" />
              <Add className="icon" />
              <ThumbUpAltOutlined className="icon" />
            </div>
            <div className="itemInfoTop">
              <span>{product.time}</span>
              <span className="limit">{product.age}</span>
              <span>{product.year}</span>
            </div>
            <div className="desc">{product.desc}</div>
            <div className="genre">{product.genre}</div>
          </div>
        </div>
      )}
    </div>
  );
}

2. 调整ListItem.scss样式,固定布局规则

// listItem.scss
.listItem {
  position: relative;
  width: 225px;
  height: 125px;
  margin-right: 2.5px;
  cursor: pointer;
  overflow: visible; // 允许悬浮内容超出容器范围

  img {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  .hoverOverlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 400px;
    height: 180px;
    background-color: #181818;
    z-index: 10;
    padding: 10px;
    display: flex;
    flex-direction: column;
    justify-content: space-between;

    video {
      width: 100%;
      object-fit: cover;
    }

    .itemInfo {
      color: white;
      font-size: 14px;

      .icons {
        display: flex;
        gap: 10px;
        margin-bottom: 8px;
        .icon {
          cursor: pointer;
        }
      }

      .itemInfoTop {
        display: flex;
        gap: 10px;
        margin-bottom: 8px;
        .limit {
          border: 1px solid #ccc;
          padding: 0 4px;
        }
      }

      .desc {
        margin-bottom: 8px;
        overflow: hidden;
        text-overflow: ellipsis;
        display: -webkit-box;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
      }
    }
  }
}

3. 优化List容器样式,确保横向排列稳定

// list.scss
.container {
  display: flex;
  overflow: hidden;
  gap: 2.5px;
  align-items: flex-start; // 避免元素垂直错位
}

优化说明

  • 用relative+absolute定位让悬浮内容覆盖在原列表项上方,不会改变原布局流,避免后续元素填补位置
  • 给悬浮内容设置固定宽高,确保展开时不会挤压其他元素
  • 视频添加muted属性,符合浏览器自动播放规则
  • 调整列表容器的对齐方式,避免元素垂直错位

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 04:50:40