ImageWithText (图文分栏1)
多功能图文展示组件,支持三种运行模式、四种布局方向、图片视频媒体类型、Tab切换和按钮交互。【✅ 已发布】
加载中...
当前视口: 1920px × 600px场景: 图左文右布局
打开链接功能特性
ImageWithText 组件是一个全能型图文展示组件,具有以下核心能力:
- ✅ 三种运行模式 - 基础模式、Tab 切换模式、按钮交互模式
- ✅ 四种布局方向 - 左文右图、右文左图、上文下图、下文上图(
layout: left | right | top | bottom) - ✅ 双媒体类型支持 - 支持图片和视频,视频带懒加载和自动播放
- ✅ 三端独立配置 - 桌面端、平板端、移动端可分别配置图片/视频
- ✅ Tab 切换动画 - 平滑滑块动画 + Framer Motion 内容切换效果
- ✅ 功能列表展示 - 支持带图标的功能特性列表,图标标题带蓝色渐变效果
- ✅ 按钮功能 - 支持 primary/secondary 两种按钮样式,自动影响文本区对齐方式
- ✅ 文本对齐控制 - 支持左对齐和居中对齐(
textAlign: left | center) - ✅ 视频封面图 - 图片自动作为视频 poster,支持三端独立封面
- ✅ 响应式布局 - 移动端自动切换为上文下图布局,5 个断点完整适配
- ✅ BEM 类名体系 - 完整的 BEM 命名规范,支持深度样式定制
- ✅ 曝光追踪 - 内置 useExposure hook,自动追踪组件曝光
- ✅ 主题切换 - 支持 light/dark 主题
- ✅ TypeScript 严格模式 - 完整的类型定义和类型安全
Props 参数
ImageWithTextProps
| 属性 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
data | ImageWithTextData | 是 | - | 组件数据配置 |
className | string | 否 | - | 自定义类名 |
ImageWithTextData
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
title | string | 是 | - | 主标题 |
subtitle | string | 否 | - | 副标题/描述文本 |
desc | string | 否 | - | 大号高亮描述文本(基础模式/Tab 模式) |
descIcon | string | 否 | - | 描述文本图标(基础模式) |
theme | 'light' | 'dark' | 否 | 'dark' | 主题颜色 |
layout | 'left' | 'right' | 'top' | 'bottom' | 否 | 'left' | 布局位置,控制媒体区和文本区的相对位置 |
mediaType | 'image' | 'video' | 否 | 'image' | 媒体类型,支持图片和视频 |
textAlign | 'left' | 'center' | 否 | 'left' | 文本区域对齐方式 |
image | Media | 否 | - | 桌面端图片(视频模式下作为封面图) |
video | Media | 否 | - | 桌面端视频 URL(mediaType=‘video’ 时使用) |
padImage | Media | 否 | - | 平板端图片(fallback 到 image) |
padVideo | Media | 否 | - | 平板端视频 URL(fallback 到 video) |
mobileImage | Media | 否 | - | 移动端图片(fallback 到 image) |
mobVideo | Media | 否 | - | 移动端视频 URL(fallback 到 video) |
items | ImageWithTextItem[] | 否 | [] | 功能特性列表(基础模式) |
datalist | ImageWithTextTabItem[] | 否 | [] | Tab 数据列表(Tab 模式,优先级最高) |
button | ButtonConfig | 否 | - | 按钮配置 |
ImageWithTextItem
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
icon | Media | 是 | 功能图标 |
text | string | 是 | 功能标题(带蓝色渐变效果) |
desc | string | 是 | 功能描述 |
ImageWithTextTabItem
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
title | string | 是 | Tab 标题 |
image | Media | 是 | 桌面端图片(视频模式下作为封面图) |
imgPad | Media | 否 | 平板端图片(fallback 到 image) |
imageMob | Media | 否 | 移动端图片(fallback 到 image) |
video | Media | 否 | 桌面端视频 URL(mediaType=‘video’ 时使用) |
padVideo | Media | 否 | 平板端视频 URL(fallback 到 video) |
mobVideo | Media | 否 | 移动端视频 URL(fallback 到 video) |
ButtonConfig
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
text | string | 是 | - | 按钮文字 |
link | string | 是 | - | 跳转链接 |
variant | 'primary' | 'secondary' | 否 | 'primary' | 按钮样式类型 |
Media
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
url | string | 是 | 媒体资源 URL |
alt | string | 是 | 无障碍替代文本 |
thumbnailURL | string | 否 | 缩略图 URL |
mimeType | string | 否 | MIME 类型 |
使用示例
基础图文模式 - 左文右图
import { ImageWithText } from '@anker-in/headless-ui/biz'
export default function Example() {
return (
<ImageWithText
data={{
title: 'Precise Navigation',
subtitle: 'iPath Laser Navigation plans optimal cleaning routes.',
layout: 'left', // 默认值,可省略
image: {
url: '/precise_navigation.png',
alt: 'Precise Navigation',
},
padImage: {
url: '/precise_navigation_pad.png',
alt: 'Precise Navigation Pad',
},
mobileImage: {
url: '/precise_navigation_mobile.png',
alt: 'Precise Navigation Mobile',
},
}}
/>
)
}适用场景: 产品功能介绍页、产品详情页的基础图文展示
基础模式 + 功能列表 + 渐变图标标题
<ImageWithText
data={{
title: 'AI.See™ Obstacle Avoidance',
subtitle: 'Advanced AI and RGB visual recognition.',
layout: 'left',
image: {
url: '/ai-avoidance.png',
alt: 'AI Obstacle Avoidance',
},
mobileImage: {
url: '/ai-avoidance-mobile.png',
alt: 'AI Obstacle Avoidance Mobile',
},
items: [
{
icon: { url: '/led-icon.svg', alt: 'LED' },
text: 'LED', // 标题会自动应用蓝色渐变效果
desc: 'Equipped with LEDs for dark environments',
},
{
icon: { url: '/200-icon.svg', alt: '200+' },
text: '200+',
desc: 'Identifies and avoids 200+ objects.',
},
],
}}
/>适用场景: 需要强调产品多个技术特性的场景,items 数组的图标标题自动应用 from-[#3ad1ff] to-[#008cd6] 渐变效果
视频模式 - 三端独立视频配置
<ImageWithText
data={{
title: 'DuoSpiral™ Detangle Brushes',
subtitle: 'DuoSpiral™ brushes prevent hair from getting tangled.',
layout: 'left',
mediaType: 'video', // 启用视频模式
// 桌面端视频和封面图
video: { url: '/desktop-video.mp4', alt: 'DuoSpiral Video' },
image: { url: '/video-poster.jpg', alt: 'Video Poster' },
// 移动端独立视频和封面图
mobVideo: { url: '/mobile-video.mp4', alt: 'DuoSpiral Mobile Video' },
mobileImage: { url: '/mobile-poster.jpg', alt: 'Mobile Video Poster' },
}}
/>适用场景: 需要展示产品动态效果、使用演示的场景
关键特性:
- 视频自动循环播放、静音、行内播放
- IntersectionObserver 懒加载优化
- 三端独立视频源,移动端可使用压缩版本
- image 字段自动作为 video 的 poster
Tab 切换模式 - 多功能展示
<ImageWithText
data={{
title: 'Smart App Control',
desc: 'Effortless customization at your fingertips.',
// 当存在 datalist 时,自动启用 Tab 模式
datalist: [
{
title: 'Customizable Cleaning Modes',
image: { url: '/mode-1.png', alt: 'Cleaning Modes' },
imgPad: { url: '/mode-1-pad.png', alt: 'Cleaning Modes Pad' },
imageMob: { url: '/mode-1-mobile.png', alt: 'Cleaning Modes Mobile' },
},
{
title: 'Multi-Floor Mapping',
image: { url: '/mode-2.png', alt: 'Floor Mapping' },
imgPad: { url: '/mode-2-pad.png', alt: 'Floor Mapping Pad' },
imageMob: { url: '/mode-2-mobile.png', alt: 'Floor Mapping Mobile' },
},
],
}}
/>适用场景: 需要在同一区域展示多个相关功能、多个产品特性对比的场景
关键特性:
- Tab 切换带平滑滑块动画
- 内容切换使用 Framer Motion 淡入淡出效果
- 点击 Tab 自动居中滚动
- 支持三端独立图片配置
Tab 模式 + 视频
<ImageWithText
data={{
title: 'Smart App Control',
desc: 'Effortless customization at your fingertips.',
mediaType: 'video', // 启用视频模式
datalist: [
{
title: 'Customizable Cleaning Modes',
video: { url: '/mode-1-video.mp4', alt: 'Cleaning Modes Video' },
image: { url: '/mode-1-poster.jpg', alt: 'Cleaning Modes Poster' },
mobVideo: { url: '/mode-1-mobile.mp4', alt: 'Cleaning Modes Mobile' },
imageMob: { url: '/mode-1-mobile-poster.jpg', alt: 'Mobile Poster' },
},
{
title: 'Multi-Floor Mapping',
video: { url: '/mode-2-video.mp4', alt: 'Floor Mapping Video' },
image: { url: '/mode-2-poster.jpg', alt: 'Floor Mapping Poster' },
},
],
}}
/>适用场景: 需要通过视频动态展示多个产品功能的高级场景
关键特性:
- Tab 切换时视频重新加载并播放
- 视频播放失败静默处理,不影响用户体验
- 每个 Tab 可配置独立的三端视频
带按钮的交互式图文
<ImageWithText
data={{
title: '革新清洁体验',
subtitle: '看见科技如何重新定义家居清洁',
layout: 'top', // 上文下图布局
mediaType: 'video',
video: { url: '/product-demo.mp4', alt: '产品演示' },
image: { url: '/product-demo-poster.jpg', alt: '产品演示封面' },
items: [
{
icon: { url: 'ai-icon.png', alt: 'AI' },
text: 'AI 视觉识别',
desc: '先进 AI 算法,智能识别并避开障碍物',
},
{
icon: { url: 'quiet-icon.png', alt: 'Quiet' },
text: '静音运行',
desc: '噪音控制在 55dB 以下,静享清洁时光',
},
],
button: {
text: '查看详细参数',
link: '/specs',
variant: 'secondary', // 次要按钮样式(边框样式)
},
}}
/>适用场景: 需要引导用户点击跳转的营销页面、落地页
关键特性:
- 按钮存在时,文本区由垂直居中改为
justify-between对齐 - 支持 primary(填充样式)和 secondary(边框样式)两种按钮变体
- 按钮自动带右箭头图标
- top/bottom 布局时,laptop 以上尺寸按钮显示在标题行右侧
文本居中对齐模式
<ImageWithText
data={{
title: 'Precise Navigation',
subtitle: 'iPath Laser Navigation plans optimal cleaning routes.',
layout: 'left',
textAlign: 'center', // 文本居中对齐
image: { url: '/precise_navigation.png', alt: 'Precise Navigation' },
items: [
{
icon: { url: 'led-icon.svg', alt: 'LED' },
text: 'LED',
desc: 'Equipped with LEDs',
},
{
icon: { url: '200-icon.svg', alt: '200+' },
text: '200+',
desc: 'Identifies 200+ objects',
},
],
button: {
text: '立即购买',
link: 'https://example.com',
variant: 'primary',
},
}}
/>适用场景: 需要强调内容重要性、创造视觉聚焦的场景
关键特性:
- 标题、副标题、描述文本全部居中对齐
- 功能列表图标和标题居中对齐
- 按钮居中显示
- Tab 模式下 Tab 导航栏也会居中
响应式行为
断点定义
| 断点 | 屏幕宽度 | 布局变化 | 特性 |
|---|---|---|---|
| Mobile | < 768px | 强制上文下图 | 所有 layout 统一为上文下图,items 网格 2 列 |
| Tablet | ≥ 768px | 支持横向布局 | left/right 开始生效,items 网格 4 列 |
| Laptop | ≥ 1025px | 完整布局支持 | 所有 layout 完全生效,items 根据 layout 调整 |
| Desktop | ≥ 1440px | 间距增大 | gap 从 48px 增至 64px,字体大小增大 |
| LG Desktop | ≥ 1920px | 最大尺寸 | 超大屏优化,视频显示桌面端版本 |
布局响应式逻辑
// 基础模式
layout: 'left' → Mobile: 上文下图, Laptop+: 左文右图
layout: 'right' → Mobile: 上文下图, Laptop+: 右文左图
layout: 'top' → 所有断点: 上文下图
layout: 'bottom' → 所有断点: 下文上图功能列表网格响应式
// 当 layout 为 left 或 right 时
Mobile: 2 列 (grid-cols-2)
Tablet: 4 列 (tablet:grid-cols-4)
Laptop+: 2 列 (laptop:grid-cols-2)
// 当 layout 为 top 或 bottom 时
Mobile: 2 列 (grid-cols-2)
Tablet+: 4 列 (tablet:grid-cols-4)间距响应式
组件主间距:
Mobile: 24px (gap-[24px])
Laptop: 48px (laptop:gap-[48px])
LG Desktop: 64px (lg-desktop:gap-[64px])
功能列表 margin-top:
Mobile: 24px (mt-[24px])
Laptop: 32px (laptop:mt-[32px])
Desktop: 48px (desktop:mt-[48px])
Top/Bottom: 24px (!mt-6, 固定值)
按钮 margin-top:
Mobile: 24px (mt-[24px])
Laptop: 32px (laptop:mt-[32px])视频响应式显示
桌面端视频: LG Desktop 及以上显示 (lg-desktop:block)
平板端视频: Tablet 到 LG Desktop 之间显示 (tablet:block lg-desktop:hidden)
移动端视频: Mobile 显示 (block tablet:hidden)设计规范
标题和描述
- 标题长度: 建议 20-40 个字符,过长会影响阅读体验
- 副标题长度: 建议 50-100 个字符
- desc 文本: 大号高亮文本,建议 10-20 个字符,适合数字、简短 slogan
- descIcon 尺寸: 推荐 36x36px (移动端) / 48x48px (桌面端)
功能列表 items
- 推荐数量: 2-4 个功能项,过多会显得拥挤
- 图标尺寸: 推荐 28px (移动端) / 44-52px (桌面端)
- 图标格式: 建议使用 SVG 格式,支持响应式缩放
- 功能标题: 建议 2-8 个字符,过长会影响渐变效果
- 功能描述: 建议 20-50 个字符
Tab 数据 datalist
- 推荐数量: 2-4 个 Tab 项,过多会导致移动端显示不完整
- Tab 标题: 建议 6-15 个字符,避免换行
- Tab 滑块动画: transition-all duration-300 ease-in-out
图片和视频
- 图片格式: 推荐 WebP 或 AVIF(fallback 到 JPG/PNG)
- 桌面端图片尺寸: 推荐 800x600px 或更大,aspect-ratio 根据 layout 自动调整
- 移动端图片尺寸: 推荐 600x800px 或更大
- 视频格式: 推荐 MP4 (H.264 编码)
- 视频大小:
- 桌面端: 建议 5MB 以内
- 移动端: 建议 2MB 以内,使用压缩版本
- 视频时长: 建议 10-30 秒,循环播放
按钮
- 按钮文字: 建议 4-8 个字符,过长会影响按钮美观度
- 按钮样式:
primary: 填充样式,用于主要操作secondary: 边框样式,用于次要操作
- 按钮位置:
- left/right 布局: 按钮在文本区底部,左对齐或居中(根据 textAlign)
- top/bottom 布局: laptop+ 时按钮在标题行右侧,mobile 时在文本区底部
颜色规范
- 功能标题渐变:
from-[#3ad1ff] to-[#008cd6](固定值,不可修改) - desc 文本颜色:
#3AD1FF(固定值,亮蓝色) - Tab 滑块背景:
bg-white(固定值) - Tab 背景:
bg-[#1D1D1F](固定值,深灰色)
无障碍性
语义化 HTML
<section> {/* 使用 section 作为根元素 */}
<div className="content">
<Heading as="h2" size={4}> {/* 标题使用语义化 Heading 组件 */}
{title}
</Heading>
<Text as="p" size={3}> {/* 描述使用 Text 组件 */}
{subtitle}
</Text>
</div>
<Picture /> {/* 图片使用 Picture 组件,自动处理 alt */}
</section>ARIA 属性
- data-ui-component-id:
"ImageWithText"- 组件标识,便于测试和追踪 - video 元素:
playsInline- 移动端行内播放,不全屏muted- 静音播放,避免打扰用户poster- 视频封面图,提升加载体验loop- 循环播放
键盘导航
- Tab 切换: Tab 按钮支持键盘点击事件
- 按钮链接: 使用 Link 组件,支持键盘导航(Enter/Space 键)
- 焦点管理: Tab 切换后自动滚动到视图中心
曝光追踪
useExposure(boxRef, {
componentType: 'image',
componentName: 'image_with_text',
componentTitle: title,
componentDescription: subtitle,
})- 使用 IntersectionObserver 监听组件可见性
- 曝光数据自动上报,用于分析用户行为
- 不影响组件性能和用户体验
屏幕阅读器支持
- alt 属性: 所有 Media 对象必须提供有意义的 alt 文本
- 图片描述: 避免使用 “image”、“photo” 等通用词,描述图片实际内容
- 视频描述: alt 应描述视频内容,如 “产品使用演示视频”
- 功能列表: 图标 alt + 标题 + 描述形成完整的语义
性能优化
React 优化
// 1. 使用 React.forwardRef
const ImageWithText = React.forwardRef<HTMLDivElement, ImageWithTextProps>(
({ data, className }, ref) => {
// ...
}
)
// 2. 使用 useRef 避免重复创建
const boxRef = useRef<HTMLDivElement>(null)
const desktopVideoRef = useRef<HTMLDivElement>(null)
const tabRefs = useRef<Array<HTMLDivElement | null>>([])
// 3. 使用 useEffect 管理副作用
useEffect(() => {
// Tab 滑块位置计算
}, [activeIndex, datalist.length])
// 4. 使用 useState 管理视频懒加载状态
const [loadedDesktopVideoSrc, setLoadedDesktopVideoSrc] = useState('')视频懒加载
// 使用 useIntersectionObserverDelay 监听媒体区域可见性
useIntersectionObserverDelay(mediaWrapperRef, {
once: true, // 仅触发一次
threshold: 0.01, // 可见度阈值 1%
callback: () => {
setVideoIntersected(true) // 标记视频区域已可见
},
})
// 视频区域可见后才加载视频源
useEffect(() => {
if (!videoIntersected || !isVideo) return
// 加载视频 src
setLoadedDesktopVideoSrc(desktopSrc)
// 延迟触发播放
setTimeout(() => {
videoElement.play()
}, 200)
}, [videoIntersected, isVideo])优化效果:
- 首屏不加载视频,减少初始加载时间
- 视频可见时才加载,节省带宽
- 延迟播放确保视频已加载足够数据
图片优化
// 使用 Picture 组件,支持响应式图片
<Picture
source={`${image?.url},${padImage?.url || image?.url} 1024, ${mobileImage?.url || image?.url} 768`}
alt={image?.alt}
className="image-with-text__image"
/>响应式图片策略:
- 桌面端: 显示原始图片
- 平板端(≤1024px): 显示 padImage 或降级到 image
- 移动端(≤768px): 显示 mobileImage 或降级到 image
Framer Motion 优化
// 使用 AnimatePresence mode="wait" 确保动画不重叠
<AnimatePresence mode="wait">
<motion.div
key={activeTab.video?.url} // 使用唯一 key 强制重新渲染
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.3 }} // 短动画时长,快速响应
>
{/* 内容 */}
</motion.div>
</AnimatePresence>优化要点:
- 使用
mode="wait"确保退出动画完成后再开始进入动画 - 动画时长 300ms,快速响应不拖沓
- 使用唯一 key(video URL)强制组件重新渲染
常见问题
如何决定使用哪种运行模式?
ImageWithText 有三种运行模式,优先级如下:
// 优先级 1: Tab 模式(datalist 存在)
datalist.length > 0 → Tab 模式
// 优先级 2: 功能列表模式(items 存在)
items.length > 0 && datalist.length === 0 → 功能列表模式
// 优先级 3: 基础模式(无 datalist 和 items)
datalist.length === 0 && items.length === 0 → 基础模式选择建议:
- 基础模式: 单一产品功能、简单图文介绍
- 功能列表模式: 需要展示 2-4 个产品特性
- Tab 模式: 需要在同一区域展示多个相关功能、对比不同产品功能
视频模式下,图片和视频的关系是什么?
// 当 mediaType='video' 时:
- image/padImage/mobileImage 作为视频的 poster(封面图)
- video/padVideo/mobVideo 是实际播放的视频 URL
- 视频加载前显示 poster,加载完成后自动播放
// 优先级:
桌面端: video?.url → image?.url 作为 poster
平板端: padVideo?.url || video?.url → padImage?.url || image?.url 作为 poster
移动端: mobVideo?.url || video?.url → mobileImage?.url || image?.url 作为 poster最佳实践:
- 始终提供 poster 图片,提升用户体验
- poster 图片建议使用视频第一帧或精心设计的封面图
- 三端视频可以使用不同分辨率,移动端使用压缩版本
移动端布局总是变成上文下图吗?
是的,为了确保移动端良好的用户体验,所有 layout 配置在移动端(<768px)都会强制转换为上文下图:
// 移动端布局统一规则
layout: 'left' → Mobile: 上文下图
layout: 'right' → Mobile: 上文下图
layout: 'top' → Mobile: 上文下图(保持不变)
layout: 'bottom' → Mobile: 下文上图(保持不变)原因:
- 移动端屏幕宽度有限,横向布局会导致图片和文字过于拥挤
- 上下布局更符合移动端的滚动浏览习惯
如何自定义组件样式?
组件使用完整的 BEM 类名体系,支持深度样式定制:
/* 根元素 */
.image-with-text { }
.image-with-text.aiui-dark { } /* 深色主题 */
/* 内容区域 */
.image-with-text__content { }
.image-with-text__title { }
.image-with-text__subtitle { }
.image-with-text__description { }
/* 功能列表 */
.image-with-text__items { }
.image-with-text__item { }
.image-with-text__item-icon { }
.image-with-text__item-text { } /* 功能标题(带渐变) */
.image-with-text__item-desc { }
/* Tab 模式 */
.image-with-text__tabs-wrapper { }
.image-with-text__tabs { }
.image-with-text__tab { }
.image-with-text__tab--active { } /* 激活状态 */
.image-with-text__slider { } /* 滑块背景 */
/* 按钮 */
.image-with-text__button-wrapper { }
.image-with-text__button { }
/* 媒体区域 */
.image-with-text__media-wrapper { }
.image-with-text__image { }
.image-with-text__video { }示例:
/* 自定义功能图标样式 */
.image-with-text__item-icon {
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 12px;
}
/* 自定义 Tab 滑块颜色 */
.image-with-text__slider {
background: linear-gradient(90deg, #3ad1ff 0%, #008cd6 100%);
}按钮功能如何影响文本区布局?
按钮存在时,文本区的垂直对齐方式会自动改变:
// 无按钮:文本区垂直居中
!hasButton → justify-center
// 有按钮:文本区上下分散对齐(space-between)
hasButton → justify-between效果:
- 无按钮时,文本内容在媒体区旁边垂直居中显示
- 有按钮时,标题/副标题/功能列表在上方,按钮在下方,充分利用垂直空间
特殊情况:
- 当
layout: 'top' | 'bottom'时,laptop 及以上尺寸按钮会显示在标题行右侧 - 移动端按钮始终显示在文本区底部
padImage/padVideo 不提供时会怎样?
组件使用 fallback 机制,确保所有断点都有图片/视频显示:
// 图片 fallback 逻辑
桌面端: image
平板端: padImage || image // 未提供 padImage 时使用 image
移动端: mobileImage || image // 未提供 mobileImage 时使用 image
// 视频 fallback 逻辑
桌面端: video
平板端: padVideo || video // 未提供 padVideo 时使用 video
移动端: mobVideo || video // 未提供 mobVideo 时使用 video最佳实践:
- 至少提供
image和mobileImage,确保桌面端和移动端有不同尺寸 - 如果三端图片完全相同,只提供
image即可 - 视频同理,但建议提供
mobVideo,使用压缩版本节省移动端流量
Tab 模式支持多少个 Tab 项?
推荐数量: 2-4 个 Tab 项
原因:
- 移动端屏幕宽度有限,超过 4 个 Tab 会导致横向滚动
- Tab 数量过多会分散用户注意力,降低信息传达效率
技术限制:
- 无硬性上限,理论上可以添加任意数量的 Tab
- 移动端使用
overflow-x-scroll支持横向滚动 - Tab 切换动画依赖 refs 数组,数量过多可能影响性能
建议:
- 2-3 个 Tab: 最佳,信息清晰,用户易于理解
- 4 个 Tab: 可接受,但移动端显示会比较紧凑
- 5 个及以上: 不推荐,考虑拆分为多个组件或使用其他展示方式
技术实现
核心依赖
- React 18+ - 组件框架
- Tailwind CSS 3+ - 样式系统
- Framer Motion - 动画库,用于 Tab 切换动画
- Heading - 语义化标题组件
- Text - 文本渲染组件
- Picture - 响应式图片组件
- Link - 链接组件
关键技术点
1. 三种运行模式判断
const isTabMode = datalist && datalist.length > 0
const hasItems = items && items.length > 0
const hasButton = !!button
// 运行模式优先级:
// 1. Tab 模式(datalist 存在)
// 2. 功能列表模式(items 存在)
// 3. 基础模式2. 视频懒加载实现
const [videoIntersected, setVideoIntersected] = useState(false)
useIntersectionObserverDelay(mediaWrapperRef, {
once: true,
threshold: 0.01,
callback: () => setVideoIntersected(true),
})
useEffect(() => {
if (!videoIntersected || !isVideo) return
setLoadedDesktopVideoSrc(desktopSrc)
setTimeout(() => videoElement.play(), 200)
}, [videoIntersected, isVideo])3. Tab 滑块动画
useEffect(() => {
if (!tabRefs.current[activeIndex]) return
const activeTab = tabRefs.current[activeIndex]
const sliderLeft = activeTab.offsetLeft
const sliderWidth = activeTab.offsetWidth
setSliderStyle({
left: `${sliderLeft}px`,
width: `${sliderWidth}px`,
})
}, [activeIndex])4. Framer Motion 内容切换
<AnimatePresence mode="wait">
<motion.div
key={activeTab.video?.url}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.3 }}
>
{/* 内容 */}
</motion.div>
</AnimatePresence>5. 响应式图片源
const getImageSource = () => {
const desktop = image?.url
const tablet = padImage?.url || desktop
const mobile = mobileImage?.url || desktop
return `${desktop}, ${tablet} 1024, ${mobile} 768`
}相关资源
组件源码
- 组件实现:
/packages/ui/src/biz-components/ImageWithText/ - 类型定义:
/packages/ui/src/biz-components/ImageWithText/types.ts - Stories:
/packages/ui/src/stories/imageWithText.stories.tsx
相关组件
- ImageTextFeature - 图文分栏(带卖点),包含功能项列表和渐变文字
- FeatureCards - 特性卡片,网格布局展示多个功能
- HeroBanner - 英雄横幅,带CTA按钮的大型图文展示
- MediaPlayerBase - 媒体播放器基础组件,支持视频播放控制
外部文档
设计资源
- Figma 设计稿: IPC To-C Design System - ImageWithText
Last updated on