React组件:父元素宽度不足时动态修改子菜单内容方案问询
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:
- Track the width of the parent container holding our menu items.
- Calculate the total width required for our menu items in each of their three states (full text → shortened → single letter).
- 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
menuContainerRefto get the actual width of the parent div holding our buttons, ensuring we're measuring the exact space available. - Debounced Resize Handler: The
debouncefunction 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
getMenuTexthelper 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 += 40value 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
相关产品推荐
相关产品推荐

