需求:参数仅支持一行展示,参数params是个数组,要求每个参数 完整展示,展示不全不展示不做截断。
UI:
难点: 1、行宽度是动态的 2、item 的宽度也是动态的,随字数改变
基本思路: 1、使用 ref 获取到真实的行元素,通过 offsetWidth 拿到行宽度 2、创建临时 div 元素测量文本宽度,首先遍历 params 数组,设置元素的 textContent ,然后通过offsetWidth 拿到临时 div 的宽度 3、设置一个 count 用于计数,当临时 div 的宽度小于等于行宽度时,则 count++ ,当临时 div 的宽度等于行宽度时,则 count 停止累加,最后,得到 count 4、params slice 出 count 长度的数据, map 得到最终的展示
遇到的问题: 1、获取行宽度打印出来是0
原因:考虑到可能是因为在 useEffect 执行时,DOM 元素还没有完全渲染或布局完成,导致通过 ref 获取 offsetWidth 为 0。
解决: 1、创建 ResizeObserver 监听元素大小变化; 2、使用 getBoundingClientRect 更精确的获取尺寸,因为它的精度是小数,并且他是在任何时机都可以返回的
结合 offsetWidth 和 getBoundingClientRect().width 提高获取元素尺寸的可靠性。 适用于动态内容、动画过程中的元素测量,或在不同浏览器环境下的兼容性处理。
完整代码:
<div ref={rightRef} className={styles.right}>
<div className={styles.productParams}>
{displayItems[0]?.bindProps
?.slice(0, visibleParamCount)
.map((item, index, array) => (
<>
<div key={item?.propertyText}>{item?.valueText}</div>
{index < array.length - 1 && <div />}
</>
))
}
</div>
<div>
.right {
width: calc(100vw - 154px);
max-width: 600px;
flex: auto;
display: flex;
flex-direction: column;
justify-content: center;
gap: 3px;
}
const rightRef = useRef<HTMLDivElement>(null);
const [visibleParamCount, setVisibleParamCount] = useState<number>(1);
// 计算可显示参数数量
useEffect(() => {
if (!rightRef.current || !displayItems[0]?.bindProps) return;
const { bindProps = [] } = displayItems[0] || {};
if (bindProps.length === 0) return;
let isCancelled = false;
const calculateParams = () => {
if (isCancelled || !rightRef.current) return;
// 创建临时元素来测量文本宽度
const tempContainer = document.createElement('div');
tempContainer.style.position = 'absolute';
tempContainer.style.visibility = 'hidden';
tempContainer.style.whiteSpace = 'nowrap';
tempContainer.style.fontSize = '12px';
tempContainer.style.fontFamily = 'Arial, sans-serif';
document.body.appendChild(tempContainer);
try {
// 获取可用宽度
const availableWidth = rightRef.current.offsetWidth;
// 如果宽度仍然为0,尝试使用 getBoundingClientRect
const rectWidth = rightRef.current.getBoundingClientRect().width;
const finalWidth = availableWidth > 0 ? availableWidth : rectWidth;
if (finalWidth <= 0) {
// 如果仍然为0,稍后重试
setTimeout(() => {
if (!isCancelled) {
calculateParams();
}
}, 100);
return;
}
console.log('获取可用宽度', finalWidth);
let currentWidth = 0;
let count = 0;
for (let i = 0; i < bindProps.length; i++) {
const currentBindProp = bindProps[i];
// 安全访问属性
const valueText = currentBindProp?.valueText || '';
tempContainer.textContent = valueText;
const paramWidth = tempContainer.offsetWidth;
console.log('测量每一个参数宽度==', paramWidth);
// 测量分隔符宽度(如果不是最后一个元素)
let separatorWidth = 0;
if (i < bindProps.length - 1) {
separatorWidth = 13;
}
// 检查加上当前参数和分隔符后是否会超出可用宽度
if (currentWidth + paramWidth + separatorWidth <= finalWidth || i === 0) {
currentWidth += paramWidth + separatorWidth;
count++;
} else {
// 超出宽度,停止添加
break;
}
}
console.log('可以显示的参数个数', Math.max(1, count));
// 至少显示一个参数
if (!isCancelled) {
setVisibleParamCount(Math.max(1, count));
}
} finally {
document.body.removeChild(tempContainer);
}
};
// 立即尝试计算
calculateParams();
// 创建 ResizeObserver 监听元素大小变化
const resizeObserver = new ResizeObserver(() => {
if (rightRef.current && rightRef.current.offsetWidth > 0) {
calculateParams();
}
});
if (rightRef.current) {
resizeObserver.observe(rightRef.current);
}
return () => {
isCancelled = true;
resizeObserver.disconnect();
};
}, [displayItems[0]?.bindProps, displayItems[0]?.spuTitle]);