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

React组件:父元素宽度不足时动态修改子菜单内容方案问询

Dynamic Responsive Menu Text in React (No Fixed Breakpoints)

Great question! This is a perfect example of content-driven responsiveness—adjusting text based on actual available space instead of arbitrary screen width breakpoints. Here's how to implement this properly in your React component:

Core Idea

We need to:

  1. Track the width of the parent container holding our menu items.
  2. Calculate the total width required for our menu items in each of their three states (full text → shortened → single letter).
  3. Compare the available container width to these required widths, then update a state variable (like your existing squeeze) to trigger the appropriate text rendering.

Full Implementation

Let's update your component with this logic. I'll use a class component to match your existing code, and also note how to adapt it for function components later.

import React, { Component } from 'react';
import Button from '@material-ui/core/Button'; // Assuming Material-UI usage

class ResponsiveMenu extends Component {
  constructor(props) {
    super(props);
    this.state = {
      squeeze: 0, // 0 = full text, 1 = shortened, 2 = single letter
      btnVar0: '', // Your existing button variant state
      btnVar1: '',
      btnVar2: ''
    };
    this.menuContainerRef = React.createRef();
    // Debounce resize handler to avoid excessive re-renders
    this.debouncedCheckSize = this.debounce(this.checkMenuSize, 100);
  }

  componentDidMount() {
    // Check size on initial render
    this.checkMenuSize();
    // Listen for window resize (and container size changes)
    window.addEventListener('resize', this.debouncedCheckSize);
  }

  componentWillUnmount() {
    // Clean up event listener to prevent memory leaks
    window.removeEventListener('resize', this.debouncedCheckSize);
  }

  // Debounce utility to limit how often we run size checks
  debounce(func, delay) {
    let timeoutId;
    return (...args) => {
      clearTimeout(timeoutId);
      timeoutId = setTimeout(() => func.apply(this, args), delay);
    };
  }

  checkMenuSize() {
    if (!this.menuContainerRef.current) return;

    const containerWidth = this.menuContainerRef.current.offsetWidth;
    // Define all menu text variants
    const menuItems = [
      { full: 'Toy Store', short: 'Toys', single: 'T' },
      { full: 'Configure your Toy', short: 'Configure', single: 'C' },
      { full: 'About Us', short: 'About', single: 'A' }
    ];

    // Calculate total width for each squeeze state
    const calculateTotalWidth = (state) => {
      let totalWidth = 0;
      menuItems.forEach(item => {
        const tempEl = document.createElement('span');
        tempEl.style.visibility = 'hidden';
        tempEl.style.position = 'absolute';
        tempEl.style.whiteSpace = 'nowrap';
        // Apply your button's CSS class for accurate width measurement
        tempEl.className = this.props.classes.menuButtonMed; // Adjust class as needed
        tempEl.textContent = item[state];
        document.body.appendChild(tempEl);
        totalWidth += tempEl.offsetWidth;
        document.body.removeChild(tempEl);
      });
      // Add extra space to account for button gaps/borders
      totalWidth += 40; // Tweak this value based on your button styling
      return totalWidth;
    };

    const fullWidth = calculateTotalWidth('full');
    const shortWidth = calculateTotalWidth('short');
    const singleWidth = calculateTotalWidth('single');

    // Update squeeze state based on available space
    if (containerWidth >= fullWidth) {
      this.setState({ squeeze: 0 });
    } else if (containerWidth >= shortWidth) {
      this.setState({ squeeze: 1 });
    } else {
      this.setState({ squeeze: 2 });
    }
  }

  toggleMenuBtns = (index, e) => {
    // Your existing button toggle logic
    const newState = {};
    newState[`btnVar${index}`] = this.state[`btnVar${index}`] === 'contained' ? 'text' : 'contained';
    this.setState(newState);
  }

  render() {
    const { theme, classes } = this.props;
    const { squeeze } = this.state;

    // Helper to clean up text rendering logic
    const getMenuText = (full, short, single) => {
      switch(squeeze) {
        case 2: return single;
        case 1: return short;
        default: return full;
      }
    };

    return (
      <div ref={this.menuContainerRef} style={{ flex: 1, display: 'flex', gap: '8px' }}>
        <Button 
          color="inherit" 
          variant={this.state.btnVar0} 
          onClick={(e) => this.toggleMenuBtns(0, e)}
        >
          {getMenuText('Toy Store', 'Toys', 'T')}
        </Button>
        <Button 
          color="inherit" 
          variant={this.state.btnVar1} 
          onClick={(e) => this.toggleMenuBtns(1, e)}
        >
          {getMenuText('Configure your Toy', 'Configure', 'C')}
        </Button>
        <Button 
          color="inherit" 
          variant={this.state.btnVar2} 
          onClick={(e) => this.toggleMenuBtns(2, e)}
        >
          {getMenuText('About Us', 'About', 'A')}
        </Button>
      </div>
    );
  }
}

export default ResponsiveMenu;

Key Details Explained

  • Container Ref: We use menuContainerRef to get the actual width of the parent div holding our buttons, ensuring we're measuring the exact space available.
  • Debounced Resize Handler: The debounce function prevents excessive re-renders during window resizing, which keeps performance smooth.
  • Dynamic Width Calculation: Temporary hidden elements let us measure the exact width of each text state using the same CSS classes as your buttons—this ensures accuracy even with custom styling.
  • Clean Render Logic: The getMenuText helper replaces messy conditional spans with a simple, readable function.

Adaptation for Function Components

If you're using React 16.8+, here's how to convert this to a function component with hooks:

import React, { useRef, useState, useEffect } from 'react';
import Button from '@material-ui/core/Button';

const ResponsiveMenu = ({ theme, classes }) => {
  const [squeeze, setSqueeze] = useState(0);
  const [btnVariants, setBtnVariants] = useState(['', '', '']);
  const menuContainerRef = useRef(null);

  const debounce = (func, delay) => {
    let timeoutId;
    return (...args) => {
      clearTimeout(timeoutId);
      timeoutId = setTimeout(() => func(...args), delay);
    };
  };

  const checkMenuSize = () => {
    // Reuse the same width calculation logic from the class component
    // Replace this.setState with setSqueeze
  };

  const debouncedCheckSize = debounce(checkMenuSize, 100);

  useEffect(() => {
    checkMenuSize();
    window.addEventListener('resize', debouncedCheckSize);
    return () => window.removeEventListener('resize', debouncedCheckSize);
  }, [classes]);

  const toggleMenuBtns = (index) => {
    const newVariants = [...btnVariants];
    newVariants[index] = newVariants[index] === 'contained' ? 'text' : 'contained';
    setBtnVariants(newVariants);
  };

  const getMenuText = (full, short, single) => {
    switch(squeeze) {
      case 2: return single;
      case 1: return short;
      default: return full;
    }
  };

  return (
    <div ref={menuContainerRef} style={{ flex: 1, display: 'flex', gap: '8px' }}>
      {/* Button rendering matches the class component version */}
    </div>
  );
};

export default ResponsiveMenu;

Notes

  • Adjust the totalWidth += 40 value to match the actual spacing/padding between your buttons.
  • If your buttons have dynamic styling (like hover states), ensure the temporary elements use the same base styles for accurate width measurements.
  • For better performance, memoize width calculations if your menu items don't change frequently.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 07:23:38