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

React中Masonic布局侧边栏关闭后列无法自适应的解决方法

问题:Masonic瀑布流布局侧边栏切换后列数无法自适应

在React项目中使用Masonic库实现瀑布流布局(该库在同类工具中性能最优),但遇到侧边栏切换适配问题:打开侧边栏时,列数能正常适配容器宽度;关闭侧边栏后,列数无法重新调整以利用新增的容器空间。效果如下:

当前效果

代码示例:

import React, { useState, useEffect} from 'react'
import { Masonry, } from 'masonic';
import { useWindowSize } from '@react-hook/window-size';
import '../MasonryGridLayout.css';


export const GrideView = () => {
  const [isMobile, setIsMobile] = useState(false)
  const [refHeight, setheight] = useState(null)
  const [sideBar, setSideBar] = useState(false)
  const [windowWidth, height] = useWindowSize()

  const handleSidebar = () => {
    setSideBar(!sideBar)
    if (ref.current) {
      console.log('current')
    }
  }

  useEffect(() => {
    const handleMobile = () => {
      if (window.innerWidth <= 720) {
        setIsMobile(true)
      } else {
        setIsMobile(false)
      }
    }
    handleMobile()
  }, [])

  return (
    <div style={{
      display: 'flex',
      flexDirection: 'column'
    }}>
      <div className='header-style'>
        <button style={{
          width: 'auto',
          height: '20px',
          backgroundColor: 'teal',
          color: 'white',
          marginLeft: '20px',
         
        }}
          onClick={handleSidebar}
        >
          toggle Sidebar
        </button>
        <button style={{
          width: 'auto',
          height: '20px',
          backgroundColor: 'tan',
          color: 'white',
          marginLeft: '20px',
         
        }}
        >
          Upload
        </button>
      </div>
      <div className="wrapper">
        {
          isMobile ? null : sideBar ? <div className='side-bar-style'>
            this is a side bar
          </div> : null
        }
        <div className='masonic' style={{ height: `${refHeight}` }}>
          <Masonry
            // Provides the data for our grid items
            items={items}
            // Adds 8px of space between the grid cells
            columnGutter={8}
            maxColumnCount={sideBar ? 7 : 8}
            columnCount={windowWidth < 600 && 2}
            // Sets the minimum column width to 172px
            columnWidth={172}
            // Pre-renders 5 windows worth of content
            overscanBy={2}
            // This is the grid item component
            render={FakeCard}
          />
        </div>

      </div>
    </div>
  );
}


const FakeCard = ({ data: { postID, post, description } }) => (
  <div className='card-style' style={{ height: `${description}` }}>
    <img
      className='img-style'
      alt="kitty"
      src={post}
    />
  </div>
);
解决方案

问题根源

Masonic默认依赖窗口尺寸变化触发列数重新计算,但侧边栏切换属于容器内部宽度变化(窗口尺寸未改变),useWindowSize无法监听到该变化,导致Masonic不会自动调整列数。

解决方法

方法1:添加动态key强制重渲染

当侧边栏状态变化时,修改Masonic组件的key,让React重新挂载组件,触发Masonic重新计算列数。

修改Masonic组件部分:

<Masonry
  key={sideBar} // 新增:侧边栏状态变化时强制重渲染
  items={items}
  columnGutter={8}
  maxColumnCount={sideBar ? 7 : 8}
  columnCount={windowWidth < 600 ? 2 : undefined} // 修正逻辑:窗口宽度≥600时让Masonic自动计算
  columnWidth={172}
  overscanBy={2}
  render={FakeCard}
/>

方法2:监听容器宽度变化(更优雅)

使用useResizeObserver钩子监听Masonic容器的宽度变化,当容器宽度改变时触发列数重新计算。

  1. 安装依赖:
npm install @react-hook/resize-observer
  1. 修改后的完整代码:
import React, { useState, useEffect, useRef } from 'react'
import { Masonry } from 'masonic';
import { useWindowSize } from '@react-hook/window-size';
import { useResizeObserver } from '@react-hook/resize-observer';
import '../MasonryGridLayout.css';


export const GrideView = () => {
  const [isMobile, setIsMobile] = useState(false)
  const [refHeight, setheight] = useState(null)
  const [sideBar, setSideBar] = useState(false)
  const [windowWidth, height] = useWindowSize()
  const masonryContainerRef = useRef(null)
  const [containerWidth, setContainerWidth] = useState(window.innerWidth)

  // 监听容器宽度变化
  useResizeObserver(masonryContainerRef, (entry) => {
    setContainerWidth(entry.contentRect.width)
  })

  const handleSidebar = () => {
    setSideBar(!sideBar)
  }

  useEffect(() => {
    const handleMobile = () => {
      setIsMobile(window.innerWidth <= 720)
    }
    handleMobile()
    // 添加窗口resize监听并清理
    window.addEventListener('resize', handleMobile)
    return () => window.removeEventListener('resize', handleMobile)
  }, [])

  return (
    <div style={{ display: 'flex', flexDirection: 'column' }}>
      <div className='header-style'>
        <button 
          style={{
            width: 'auto',
            height: '20px',
            backgroundColor: 'teal',
            color: 'white',
            marginLeft: '20px',
          }}
          onClick={handleSidebar}
        >
          toggle Sidebar
        </button>
        <button style={{
          width: 'auto',
          height: '20px',
          backgroundColor: 'tan',
          color: 'white',
          marginLeft: '20px',
        }}>
          Upload
        </button>
      </div>
      <div className="wrapper">
        {!isMobile && sideBar && <div className='side-bar-style'>this is a side bar</div>}
        <div className='masonic' style={{ height: refHeight }} ref={masonryContainerRef}>
          <Masonry
            items={items}
            columnGutter={8}
            maxColumnCount={sideBar ? 7 : 8}
            columnCount={windowWidth < 600 ? 2 : undefined}
            columnWidth={172}
            overscanBy={2}
            render={FakeCard}
          />
        </div>
      </div>
    </div>
  );
}


const FakeCard = ({ data: { postID, post, description } }) => (
  <div className='card-style' style={{ height: `${description}` }}>
    <img className='img-style' alt="kitty" src={post} />
  </div>
);

额外优化点

  • 修正columnCount逻辑:原代码中windowWidth < 600 && 2在窗口宽度≥600时会返回false,导致Masonic无法自动计算列数,改为windowWidth < 600 ? 2 : undefined。
  • 给移动端监听添加清理函数,避免内存泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 17:27:02