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

CSS Grid调整项尺寸时防止换行并保留两侧间隙的实现方案

问题:CSS Grid布局动态调整项尺寸导致换行与定位问题

我有一个6列10行的CSS Grid图片布局。当网格项滚动到中心位置时,会通过修改span值调整尺寸,这会导致最后一列的项换行,进而改变图片位置。这种换行效果很突兀,让导航或滚动到下一张图片的操作变得不可预测。

我希望在保持6×10布局的前提下,实现网格项居中对齐,同时在网格两侧留有间隙。

目前尝试的方案是在每行的首尾向图片数组中添加网格项,并在图片调整尺寸时缩小这些额外项,但尚未成功。

另外,图片分为横版(1×1)和竖版(1×2)两种规格。竖版图片会将下方图片推挤到下一行,这是预期效果,但我希望调整图片宽度时避免行发生换行。

网格布局尺寸差异示意图

以下是实现图片尺寸调整逻辑的代码:

import React from "react";
import { useEffect, useState, useRef} from "react";
import styled from "styled-components";


const PhotoGrid = (props) => {

  const sortPhotos = (a, b) => a.index - b.index;
  const [photos, setPhotos] = useState(null)
  const [imgCount, setImgCount] = useState(0)
  const gridRef = useRef(null);
  const gridWrapperRef = useRef(null)
  const [imagesLoaded, setImagesLoaded] = useState(false);
  const [photoId, setPhotoId] = useState(null)

  const imgCounter = () => {
    setImgCount(imgCount + 1)
    console.log("images loaded", imgCount, props.photos.length, photos.length)
    if (imgCount + 1 === 60){
      setImagesLoaded(true)
      console.log("images loaded")
    }
  }
  
  const scrollPosition = () => {

    if (props.mobile){
    const grid = gridRef.current;
    const photos = grid.children
    const gridWrapper = gridWrapperRef.current;
    const gridWrapperRect = gridWrapper.getBoundingClientRect()
    const gridWrapperWidth = gridWrapperRect.width
    const height = gridWrapperRect.height
    
      for (let i = 0; i < photos.length; i++) {

      let photo = photos[i]; 
      const gridItem = photo.getBoundingClientRect();
      const top = Math.abs(gridItem.top)
      const left = Math.abs(gridItem.left)
      const centered = (top >= height * .25 && top < height * .4) && (left >= gridWrapperWidth * .3 && left <= gridWrapperWidth * .5)
    
      const findCenter = () => {
        if (centered && !!photo){
          return eval(photo.id)
        }
      }

      const centeredId = findCenter()

      const changeCells = (centeredId) => {
      if (!!centeredId){
        const centeredType = !!photos[centeredId].getElementsByClassName("tile").length
        const centeredOrientation = !!photos[centeredId].getElementsByClassName("portrait").length
        const first = Math.floor(centeredId/6) * 6
        const range = centeredOrientation ? 16 + first : 11 + first
   
        for (let i = first; i < range; i++){ 
          let rangePhoto = photos[i];
          const rangeOrientation = !!rangePhoto?.getElementsByClassName("portrait").length
          const rangeType = !!photos[i].getElementsByClassName("tile").length
          if (!!rangePhoto){

            if (!centeredType){   
                  
              if (eval(rangePhoto.id) === centeredId && photoId !== centeredId){
  
                setPhotoId(eval(rangePhoto.id))
              if (!rangeType){
                if (!rangeOrientation){
                  console.log("rangeType", rangeType, rangePhoto.firstChild, rangePhoto, rangePhoto.style)
                  rangePhoto.style.gridColumnEnd = `span 80`
                  rangePhoto.style.gridRowEnd = `span 80`
                  rangePhoto.firstChild.style.height = '220px'
                  rangePhoto.firstChild.style.width = '240px'
                  rangePhoto.firstChild.style.top = '50%'
                  setPhotoId(null)
                }
                else {
                  rangePhoto.style.gridColumnEnd = `span 80`
                  rangePhoto.style.gridRowEnd = `span 120`
                  rangePhoto.firstChild.style.height = '340px'
                  rangePhoto.firstChild.style.width = '240px'
                  rangePhoto.firstChild.style.top = '50%'
                  setPhotoId(null)
                }}
                else{
                  console.log("rangePhoto", rangePhoto.firstChild, rangePhoto, rangePhoto.style)
                  rangePhoto.style.gridColumnEnd = `span 1`
                  rangePhoto.style.gridRowEnd = `span 1`
                }
              }
              if (eval(rangePhoto.id) !== centeredId){
                if (!rangeOrientation){
                rangePhoto.style.gridColumnEnd = `span 32`
                rangePhoto.style.gridRowEnd = `span 40`
                rangePhoto.firstChild.style.height = '100px'
                rangePhoto.firstChild.style.top = '50%'
              }
              else {
                rangePhoto.style.gridColumnEnd = `span 32`
                rangePhoto.style.gridRowEnd = `span 80`
                rangePhoto.firstChild.style.height = '220px'
                rangePhoto.firstChild.style.top = '50%'
              }
            }
          }
          else{
              if (!rangeOrientation){
                rangePhoto.style.gridColumnEnd = `span 40`
                rangePhoto.style.gridRowEnd = `span 40`
                rangePhoto.firstChild.style.width = '120px'
                rangePhoto.firstChild.style.height = '100px'
              }
              else {
              rangePhoto.style.gridColumnEnd = `span 40`
              rangePhoto.style.gridRowEnd = `span 80`
              rangePhoto.firstChild.style.width = '120px'
              rangePhoto.firstChild.style.height = '220px'
              rangePhoto.firstChild.style.top = '50%'
              }
            }      
          }
        }

        return [first, range]
      }
    }

const rowState = changeCells(centeredId)


const reset = () => { 
  for (let i = 0; i < photos.length; i++) {
    let photo = photos[i]; 
    const orientation = !!photo.getElementsByClassName("portrait").length
    const type = !!photo.getElementsByClassName("tile").length

  if (!!rowState){
    if((eval(photo.id) > rowState[1]) || (eval(photo.id) < rowState[0])){
    if (!orientation){
      photo.style.gridColumnEnd = `span 40`
      photo.style.gridRowEnd = `span 40`
      photo.firstChild.style.height = '100px'
      photo.firstChild.style.width = '120px'
    }
    else {
      photo.style.gridColumnEnd = `span 40`
      photo.style.gridRowEnd = `span 80`
      photo.firstChild.style.height = '220px'
      photo.firstChild.style.width = '120px'
    }
  }}


}}
reset(rowState)

    }
  }
}



useEffect(() => {
  const grid = gridRef.current;
  const gridWrapper = document.getElementById("grid-wrapper");
  const gridRect = grid?.getBoundingClientRect() 
  if (!!gridRect){  
    const center = gridRect.width/2
    gridWrapper.scrollLeft = center
  }
}, [!!gridRef.current === true])


useEffect(() => {    
  !!props.photos && setPhotos([...props.photos])
  }, [props.photos])




  return (
    <article >
        <>
          <div 
          id="grid-wrapper"
          className="grid-wrapper"
          onScroll={() => scrollPosition()}
          ref={gridWrapperRef}
          >
            <Grid
              ref={gridRef}
              className="grid"
              style={{ opacity: imagesLoaded ? 1 : 0 }}
              >

              {!!photos && !photos !== !null && photos !== undefined && photos.sort(sortPhotos).map((photo) => (
              <GridItem
                    className="grid-item"
                    id={photo.index}
                    mobile={props.mobile}
                    edit={props.edit}
                    key={photo.index}
                    orientation={photo.orientation}
                    url={!!photo.url}
                    photo={photo}
                    collaborator={!!photo.u_id && props.folderCollaborators.filter(user => user.uuid === photo.u_id)}
                    colorArr={props.colorArr}
                    highlight={photo.color}
                  >
                    <PictureFrame
                    className={!!photo.url ? photo.orientation ? "picture landscape" : "picture portrait" : 'tile landscape'}
                      mobile={props.mobile}
                      url={photo.url}
                      highlight={photo.color}
                      contentSizing={!!photo.name || !!photo.details}
                      enableDelete={props.enableDelete}
                      orientation={photo.orientation}
                      >  

                      <img
                        className="photo"
                        onLoad={() => imgCounter() }
                        src={
                          !!photo.thumbnail_url
                          ? photo.url
                          : require('../assets/100x135.png')
                        }
                        />

                    </PictureFrame>
                  </GridItem>
                ))}
            </Grid>
          </div>
      </>
      </article>
  );
};
export default PhotoGrid;


const Grid = styled.div`
  display: grid;
  grid-gap: 2px;
  grid-auto-rows: 1px;
  background: black;
  grid-template-columns: repeat(241, minmax(1px, 1px)) ;
  position: relative;
  width: fit-content;

  @media (max-width: 700px) {
    padding-block-start: 20vh;
    padding-block-end: 40vh;
    padding-inline: 40vw;
  };
  .grid-item {
    position: relative;
    grid-row-end: span 40;
    grid-column-end: span 40;
    };
`
const GridItem = styled.div`
z-index: ${({url}) => url ? "1" : "-5"};
`       
const PictureFrame = styled.div`
    position: relative;
    left: 50%;
    top: ${({orientation}) => orientation ? '50%' : '100%' };
    transform: translate(-50%, -50%);
    padding: 0px;
    overflow: hidden;
    height: ${({orientation}) => orientation ? '100px' : '220px'};
    display: flex;
    justify-content: center;
    border-radius: 13px;
    width: 120px ;
    max-width: calc(100% - 15px);
    transition: 
    height .1s ease-in,
    width .1s ease-in,
    border-radius .2s ease-out, 
    background-color 0s linear 1s, 
    max-width .3s ease-out .2s, 
    padding-right .2s ease-out .1s, 
    padding-left .2s ease-out .1s, 
    padding-block .2s ease-out .1s, 
    ${({url}) => url ? 'box-shadow .2s ease-in .6s' : 'box-shadow .3s ease-in'};

  .photo {
    max-height: 110%;
}
`

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 21:20:56