Taro实现展开收起组件
ExpandableContent.tsx
import React, { useEffect, useState, useRef, useMemo } from 'react';
import { View, Text, Image } from '@tarojs/components';
import Taro, { pxTransform } from '@tarojs/taro';
import arrowDown from '@/assets/image/common/arrow-down-grey.png';
import cn from 'classnames';
import './index.less';
interface ExpandableContentProps {
/** 标题 */
title: string;
/** 唯一标识符,用于高度检测(可选,不传入时自动生成) */
id?: string;
/** 内容,支持任意React节点 */
children: React.ReactNode;
/** 每行的高度(px)*/
lineHeight?: number;
/** 收起状态的最大行数,默认4行 */
maxLines?: number;
/** 是否强制全部展示,不提供展开收起功能 */
alwaysExpanded?: boolean;
}
/**
* 可展开收起的内容组件
* 支持智能检测内容高度,自动显示/隐藏展开按钮
*/
const ExpandableContent: React.FC<ExpandableContentProps> = ({
title,
id,
children,
lineHeight = 48,
maxLines = 4,
alwaysExpanded = false
}) => {
const [isExpanded, setIsExpanded] = useState(true);
const [needsExpansion, setNeedsExpansion] = useState(false);
// 生成唯一 ID,使用 useRef 确保组件生命周期内保持稳定
const contentId = useRef(id || `expandable-content-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`).current;
// 计算最大高度
const maxHeight = lineHeight * maxLines;
// 使用Taro API检测内容是否超出限制高度
useEffect(() => {
if (alwaysExpanded) {
return;
}
// 使用 nextTick 确保DOM已经渲染完成
Taro.nextTick(() => {
const query = Taro.createSelectorQuery();
query.select(`#${contentId}`).boundingClientRect((rect) => {
if (rect && rect.height) {
const realHeight = rect.height*2;
console.log('真实高度:', realHeight, '限制高度:', maxHeight);
const shouldShowButton = realHeight > maxHeight;
setNeedsExpansion(shouldShowButton);
// 根据检测结果设置初始状态
setIsExpanded(!shouldShowButton); // 如果不需要展开按钮,保持展开状态
}
}).exec();
})
}, [children, lineHeight, maxLines, alwaysExpanded, maxHeight]);
// 是否显示展开按钮
const shouldShowExpandButton = useMemo(() => {
if (alwaysExpanded) return false;
return needsExpansion;
}, [alwaysExpanded, needsExpansion]);
// 是否限制高度
const shouldLimitHeight = useMemo(() => {
if (alwaysExpanded) return false;
return !isExpanded;
}, [alwaysExpanded, isExpanded]);
// 切换展开收起状态
const toggleExpanded = () => {
setIsExpanded(!isExpanded);
};
// 内容容器样式
const contentStyle = shouldLimitHeight
? {
maxHeight: pxTransform(maxHeight),
overflow: 'hidden',
}
: {};
return (
<View className="expandable-content">
{/* 标题 */}
<View className="expandable-content__title">
{title}
</View>
{/* 内容区域 */}
<View
id={contentId}
style={contentStyle}
>
{children}
</View>
{/* 展开收起按钮 */}
{shouldShowExpandButton && (
<View className="expandable-content__button" onClick={toggleExpanded}>
<Text className="expandable-content__button-text">
{isExpanded ? '收起' : '展开全部'}
</Text>
<Image src={arrowDown} className={cn("expandable-content__button-icon", {
'expandable-content__button-icon-rotate': isExpanded
})} />
</View>
)}
</View>
);
};
export default ExpandableContent; index.less
.expandable-content {
padding: 32px 24px;
background-color: #fff;
border-radius: 12px;
&__title {
font-weight: 500;
font-size: 32px;
color: #333333;
line-height: 44px;
margin-bottom: 16px;
}
&__button {
margin-top: 16px;
flex-direction: row;
justify-content: center;
align-items: center;
gap: 4px;
&-text {
font-weight: 400;
font-size: 24px;
color: #999999;
line-height: 36px;
}
&-icon {
width: 24px;
height: 24px;
}
&-icon-rotate {
transform: rotate(180deg);
}
}
}
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果