Ksp (KSP)
突出展示产品核心卖点的组件,支持图标、标题和描述的网格布局【✅ 已发布】
功能特性
Ksp (Key Selling Points) 是一个功能强大的关键卖点展示组件,采用复杂的响应式瓦片布局系统:
- ✅ 动态瓦片布局 - 支持 4-9 个卡片,每种数量有独特的栅格布局配置
- ✅ 10 列栅格系统 - laptop 及以上使用 10 列网格,精确控制每个卡片的位置和大小
- ✅ 双媒体类型 - 每个卡片可配置图片或视频,自动识别渲染
- ✅ 三端独立媒体 - 桌面端和移动端可使用不同的图片/视频资源
- ✅ 文字叠加 - 标题和描述叠加在媒体内容上,增强视觉冲击力
- ✅ 视频自动播放 - 视频静音、循环、行内播放,无需用户操作
- ✅ 动态宽高比 - 每个卡片根据位置和屏幕尺寸使用不同的 aspect ratio
- ✅ 响应式断点 - 5 个断点(mobile/tablet/laptop/desktop/lg-desktop)完整适配
- ✅ BEM 类名体系 - 完整的 BEM 命名,支持深度样式定制
- ✅ 主题切换 - 支持 light/dark 主题
- ✅ TypeScript 严格模式 - 完整类型定义和类型安全
Props 参数
主要参数
| 参数 | 类型 | 默认值 | 必需 | 说明 |
|---|---|---|---|---|
data | KspData | - | ✅ | 组件数据配置 |
className | string | - | ❌ | 自定义类名 |
KspData
| 字段 | 类型 | 默认值 | 必需 | 说明 |
|---|---|---|---|---|
title | string | - | ❌ | 主标题,可选 |
items | KspCardItem[] | - | ✅ | 卡片列表,推荐 7 个,支持 4-9 个 |
theme | 'light' | 'dark' | 'light' | ❌ | 主题颜色 |
KspCardItem
| 字段 | 类型 | 必需 | 说明 |
|---|---|---|---|
title | string | ❌ | 卡片标题,叠加在媒体内容上 |
desc | string | ❌ | 卡片描述,叠加在标题下方 |
image | Media | ❌ | 桌面端图片(视频模式下作为 poster) |
mobImage | Media | ❌ | 移动端图片(fallback 到 image) |
video | Media | ❌ | 桌面端视频 URL,存在时优先显示视频 |
mobVideo | Media | ❌ | 移动端视频 URL(fallback 到 video) |
重要说明:
- 如果同时提供
video和image,则image作为视频的 poster(封面图) - 如果只提供
image,则显示静态图片 mobImage和mobVideo不提供时,自动 fallback 到image和video
类型定义
完整类型定义
import { Ksp } from '@anker-in/headless-ui/biz'
// 媒体资源类型
interface Media {
url: string // 媒体资源 URL
alt: string // 无障碍替代文本
thumbnailURL?: string // 缩略图 URL(可选)
mimeType?: string // MIME 类型(可选)
}
// 卡片项接口
interface KspCardItem {
title?: string // 卡片标题
desc?: string // 卡片描述
image?: Media // 桌面端图片
mobImage?: Media // 移动端图片
video?: Media // 桌面端视频
mobVideo?: Media // 移动端视频
}
// 数据配置接口
interface KspData {
title?: string // 主标题(可选)
items: KspCardItem[] // 卡片列表(必需,4-9个)
theme?: 'light' | 'dark' // 主题(可选,默认 light)
}
// 组件 Props
interface KspProps {
data: KspData // 数据配置(必需)
className?: string // 自定义类名(可选)
}使用示例
1. 标准 7 卡片图片布局
7 卡片是最常用且视觉效果最丰富的配置:
import { Ksp } from '@anker-in/headless-ui/biz'
export default function StandardSevenCardsExample() {
return (
<Ksp
data={{
title: 'Why Choose Our Product',
theme: 'light',
items: [
{
title: 'Improved Cleaning Efficiency',
desc: 'HydroJet™ Floor Washing System',
image: {
url: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=1200&h=800',
alt: 'Improved Cleaning Efficiency',
},
mobImage: {
url: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=600&h=800',
alt: 'Improved Cleaning Efficiency Mobile',
},
},
{
title: '100% Cleaning Coverage',
desc: 'Edge-Cleaning CornerRover™ Arm',
image: {
url: 'https://images.unsplash.com/photo-1563213126-a4273aed2016?w=1200&h=800',
alt: '100% Cleaning Coverage',
},
mobImage: {
url: 'https://images.unsplash.com/photo-1563213126-a4273aed2016?w=600&h=800',
alt: '100% Cleaning Coverage Mobile',
},
},
{
title: 'All You Need, All-in-One',
desc: 'Complete Cleaning Solution',
image: {
url: 'https://images.unsplash.com/photo-1556911220-bff31c812dba?w=1200&h=800',
alt: 'All You Need',
},
mobImage: {
url: 'https://images.unsplash.com/photo-1556911220-bff31c812dba?w=600&h=800',
alt: 'All You Need Mobile',
},
},
{
title: 'Less Maintenance',
desc: 'DuoSpiral™ Detangle Brushes',
image: {
url: 'https://images.unsplash.com/photo-1550745165-9bc0b252726f?w=1200&h=800',
alt: 'Less Maintenance',
},
mobImage: {
url: 'https://images.unsplash.com/photo-1550745165-9bc0b252726f?w=600&h=800',
alt: 'Less Maintenance Mobile',
},
},
{
title: 'Cleaning Power Upgrade',
desc: '20,000 Pa Turbo Suction',
image: {
url: 'https://images.unsplash.com/photo-1558002038-1055907df827?w=1200&h=800',
alt: 'Cleaning Power',
},
mobImage: {
url: 'https://images.unsplash.com/photo-1558002038-1055907df827?w=600&h=800',
alt: 'Cleaning Power Mobile',
},
},
{
title: 'Intelligent Cleaning',
desc: 'Smart App Control & Scheduling',
image: {
url: 'https://images.unsplash.com/photo-1519389950473-47ba0277781c?w=1200&h=800',
alt: 'Intelligent Cleaning',
},
mobImage: {
url: 'https://images.unsplash.com/photo-1519389950473-47ba0277781c?w=600&h=800',
alt: 'Intelligent Cleaning Mobile',
},
},
{
title: 'AI.See™ Obstacle Avoidance',
desc: 'Advanced Object Recognition',
image: {
url: 'https://images.unsplash.com/photo-1535378917042-10a22c95931a?w=1200&h=800',
alt: 'AI Obstacle Avoidance',
},
mobImage: {
url: 'https://images.unsplash.com/photo-1535378917042-10a22c95931a?w=600&h=800',
alt: 'AI Obstacle Avoidance Mobile',
},
},
],
}}
/>
)
}适用场景: 产品详情页的关键卖点展示,7 卡片是最常用的配置
布局特点(laptop+ 尺寸):
- 行 1: 大卡片(59.53%) + 小卡片(39.30%)
- 行 2: 小卡片(29.19%) + 中卡片(39.30%) + 小卡片(29.19%)
- 行 3: 小卡片(39.30%) + 大卡片(59.53%)
2. 混合图片和视频
前几个卡片使用视频吸引注意力,其余使用图片:
<Ksp
data={{
title: 'Experience the Difference',
items: [
// 卡片 1: 视频
{
title: 'Powerful Performance',
desc: 'See it in action',
video: {
url: 'https://cdn.shopify.com/videos/performance.mp4',
alt: 'Performance Video',
},
mobVideo: {
url: 'https://cdn.shopify.com/videos/performance-mobile.mp4',
alt: 'Performance Mobile Video',
},
image: {
url: 'https://cdn.shopify.com/posters/performance-desktop.jpg',
alt: 'Performance Poster',
},
mobImage: {
url: 'https://cdn.shopify.com/posters/performance-mobile.jpg',
alt: 'Performance Poster Mobile',
},
},
// 卡片 2: 视频
{
title: 'Smart Navigation',
desc: 'Advanced mapping technology',
video: {
url: 'https://cdn.shopify.com/videos/navigation.mp4',
alt: 'Navigation Video',
},
mobVideo: {
url: 'https://cdn.shopify.com/videos/navigation-mobile.mp4',
alt: 'Navigation Mobile Video',
},
image: {
url: 'https://cdn.shopify.com/posters/navigation-desktop.jpg',
alt: 'Navigation Poster',
},
mobImage: {
url: 'https://cdn.shopify.com/posters/navigation-mobile.jpg',
alt: 'Navigation Poster Mobile',
},
},
// 卡片 3-7: 图片(使用示例1的配置)
],
}}
/>适用场景: 需要通过视频动态展示产品功能
关键特性:
- 视频自动播放、循环、静音
- image 字段作为 video 的 poster(封面图)
- 桌面端和移动端可使用不同的视频资源(节省移动端流量)
3. 深色主题
使用深色主题适配深色背景页面:
<Ksp
data={{
title: '为什么选择我们的产品',
theme: 'dark', // 启用深色主题
items: [
{
title: '清洁效率大幅提升',
desc: 'HydroJet™ 地板清洁系统',
image: {
url: 'https://cdn.example.com/efficiency.jpg',
alt: '清洁效率',
},
mobImage: {
url: 'https://cdn.example.com/efficiency-mobile.jpg',
alt: '清洁效率移动端',
},
},
// ... 其他 6 个 items
],
}}
/>适用场景: 深色背景页面,需要组件融入整体设计
4. 4 卡片简洁布局
强调 4 个核心功能的简洁布局:
<Ksp
data={{
title: 'Core Features',
items: [
{
title: 'Feature 1',
desc: 'Description 1',
image: {
url: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=1200&h=800',
alt: 'Feature 1',
},
},
{
title: 'Feature 2',
desc: 'Description 2',
image: {
url: 'https://images.unsplash.com/photo-1563213126-a4273aed2016?w=1200&h=800',
alt: 'Feature 2',
},
},
{
title: 'Feature 3',
desc: 'Description 3',
image: {
url: 'https://images.unsplash.com/photo-1556911220-bff31c812dba?w=1200&h=800',
alt: 'Feature 3',
},
},
{
title: 'Feature 4',
desc: 'Description 4',
image: {
url: 'https://images.unsplash.com/photo-1550745165-9bc0b252726f?w=1200&h=800',
alt: 'Feature 4',
},
},
],
}}
/>布局特点(laptop+ 尺寸):
- 行 1: 大卡片(59.53%) + 小卡片(39.30%)
- 行 2: 小卡片(39.30%) + 大卡片(59.53%)
适用场景: 强调 4 个核心功能,简洁布局
5. 5 卡片不对称布局
5 个差异化卖点,不对称布局创造视觉层次:
<Ksp
data={{
title: 'Five Reasons to Choose',
items: [
// 5 个 items 配置
],
}}
/>布局特点(laptop+ 尺寸):
- 行 1: 大卡片(59.53%) + 小卡片(39.30%)
- 行 2: 小卡片(29.19%) + 中卡片(39.30%) + 小卡片(29.19%)
适用场景: 5 个差异化卖点,不对称布局创造视觉层次
6. 无主标题布局
不显示主标题,卡片直接从顶部开始:
<Ksp
data={{
items: [
// 7 个 items 配置
],
// 不提供 title 字段
}}
/>效果: 主标题区域不渲染,卡片布局直接从顶部开始
适用场景:
- 页面其他位置已有标题
- 卡片本身已足够说明功能,无需额外标题
7. 仅提供桌面端媒体
移动端自动 fallback 到桌面端图片:
<Ksp
data={{
title: 'Product Features',
items: [
{
title: 'Feature 1',
desc: 'Description 1',
image: {
url: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=1200&h=800',
alt: 'Feature 1',
},
// 不提供 mobImage,自动使用 image
},
// ... 其他 items 同样不提供 mobImage
],
}}
/>适用场景: 图片尺寸和比例适合所有设备,无需单独提供移动端资源
8. 8 卡片密集布局
展示 8 个完整功能点的密集布局:
<Ksp
data={{
title: 'Complete Feature Set',
items: [
// 8 个 items 配置
],
}}
/>布局特点(laptop+ 尺寸): 3 行对称复杂布局
适用场景: 完整功能展示,信息量较大
9. 自定义样式
使用 BEM 类名体系深度定制样式:
<Ksp
data={{
title: 'Premium Features',
items: [
// 7 个 items
],
}}
className="custom-ksp"
/>对应 CSS:
/* 主标题 - 渐变色文字 */
.custom-ksp .ksp-title {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
/* 卡片 hover 效果 */
.custom-ksp .ksp-card {
transition: transform 0.3s ease;
}
.custom-ksp .ksp-card:hover {
transform: scale(1.02);
}
/* 卡片标题 - 金色 */
.custom-ksp .ksp-card-title {
color: #fbbf24;
}
/* 卡片描述 - 浅灰色 */
.custom-ksp .ksp-card-desc {
color: #d1d5db;
}适用场景: 需要品牌定制化样式的高端产品页
10. 9 卡片最大信息量
展示 9 个功能点的最密集布局:
<Ksp
data={{
title: 'All Features',
items: [
// 9 个 items 配置
],
}}
/>布局特点(laptop+ 尺寸): 3 行最密集布局
适用场景: 信息量最大化,展示所有功能点
注意: 超过 9 个卡片布局配置不存在,组件可能显示异常
响应式行为
Ksp 组件在不同屏幕尺寸下的表现:
断点定义
| 断点 | 屏幕宽度 | 栅格列数 | 间距 | 特性 |
|---|---|---|---|---|
| Mobile | < 768px | 2 列 | 12px (gap-3) | 简化布局,半宽或全宽卡片 |
| Tablet | ≥ 768px | 3 列 | 16px (gap-4) | 过渡布局,部分卡片合并 |
| Laptop | ≥ 1025px | 10 列 | 16px (gap-4) | 完整布局,精确栅格控制 |
| Desktop | ≥ 1440px | 10 列 | 16px (gap-4) | 增大卡片尺寸,优化间距 |
| LG Desktop | ≥ 1920px | 10 列 | 16px (gap-4) | 最大尺寸,最佳视觉效果 |
7 卡片布局配置表
| 卡片索引 | Mobile | Tablet | Laptop+ | 说明 |
|---|---|---|---|---|
| 0 (第1张) | col-span-2 | - | col-span-6 | 大卡片,占行首 59.53% |
| 1 (第2张) | col-span-1 | - | col-span-4 | 小卡片,占行尾 39.30% |
| 2 (第3张) | col-span-1 | - | col-span-3 | 小卡片,占第2行首 29.19% |
| 3 (第4张) | col-span-2 | col-span-1 | col-span-4 | 中卡片,占第2行中 39.30% |
| 4 (第5张) | col-span-1 | - | col-span-3 | 小卡片,占第2行尾 29.19% |
| 5 (第6张) | col-span-1 | - | col-span-4 | 小卡片,占第3行首 39.30% |
| 6 (第7张) | col-span-2 | - | col-span-6 | 大卡片,占第3行尾 59.53% |
文字大小响应式
// 主标题 (Heading size)
Tablet+: size={3} // 较大字号
Mobile: size={2} // 较小字号
// 卡片标题 (Heading size)
Tablet+: size={3}
Mobile: size={2}
// 卡片描述 (Text size)
Mobile: 14px (text-[14px])
Tablet: 14px (tablet:text-[14px])
Laptop: 14px (laptop:text-[14px])
Desktop: 16px (desktop:text-[16px])
LG Desktop: 18px (lg-desktop:text-[18px])内容间距响应式
// 卡片内边距
Mobile: p-4 (16px)
Desktop: desktop:p-6 (24px)
LG Desktop: lg-desktop:p-8 (32px)
// 标题 margin-top
desc 和 title 之间: mt-[4px] (固定 4px)视频显示响应式
// 桌面端视频
laptop:inline-block hidden // Laptop+ 显示
// 移动端视频
laptop:hidden inline-block // Mobile 和 Tablet 显示
translate-y-[-80px] // 向上偏移 80px(设计需求)不同卡片数量的布局差异
| 卡片数量 | 布局特点 | 行数 | 适用场景 |
|---|---|---|---|
| 4 | 2 行,对称布局 | 2 | 核心功能简洁展示 |
| 5 | 2 行,不对称布局 | 2 | 5 个差异化卖点 |
| 6 | 2 行,对称复杂布局 | 2 | 平衡的功能展示 |
| 7 | 3 行,不对称布局(推荐) | 3 | 最常用,视觉层次丰富 |
| 8 | 3 行,对称复杂布局 | 3 | 完整功能展示 |
| 9 | 3 行,最密集布局 | 3 | 信息量最大化 |
核心算法解析
1. 布局配置算法
根据卡片数量和索引动态计算栅格布局类:
/**
* 布局配置对象
* key: 卡片总数量
* value: 每个索引对应的布局类名字符串
*/
const LAYOUT_CONFIG: Record<number, Record<number, string>> = {
7: {
0: 'col-span-2 laptop:col-span-6 aspect-w-[358] aspect-h-[240] tablet:aspect-w-[465] tablet:aspect-h-[240] laptop:aspect-w-[440] laptop:aspect-h-[240] desktop:aspect-w-[648] desktop:aspect-h-[256] lg-desktop:aspect-w-[824] lg-desktop:aspect-h-[320]',
1: 'col-span-1 laptop:col-span-4 aspect-w-[173] aspect-h-[240] tablet:aspect-w-[355] tablet:aspect-h-[240] laptop:aspect-w-[288] laptop:aspect-h-[240] desktop:aspect-w-[424] desktop:aspect-h-[256] lg-desktop:aspect-w-[540] lg-desktop:aspect-h-[320]',
2: 'col-span-1 laptop:col-span-3 aspect-w-[173] aspect-h-[240] tablet:aspect-w-[355] tablet:aspect-h-[240] laptop:aspect-w-[209] laptop:aspect-h-[240] desktop:aspect-w-[308] desktop:aspect-h-[256] lg-desktop:aspect-w-[391] lg-desktop:aspect-h-[320]',
3: 'col-span-2 tablet:col-span-1 laptop:col-span-4 aspect-w-[358] aspect-h-[240] tablet:aspect-w-[232] tablet:aspect-h-[240] laptop:aspect-w-[288] laptop:aspect-h-[240] desktop:aspect-w-[424] desktop:aspect-h-[256] lg-desktop:aspect-w-[540] lg-desktop:aspect-h-[320]',
4: 'col-span-1 laptop:col-span-3 aspect-w-[173] aspect-h-[240] tablet:aspect-w-[355] tablet:aspect-h-[240] laptop:aspect-w-[209] laptop:aspect-h-[240] desktop:aspect-w-[308] desktop:aspect-h-[256] lg-desktop:aspect-w-[391] lg-desktop:aspect-h-[320]',
5: 'col-span-1 laptop:col-span-4 aspect-w-[173] aspect-h-[240] tablet:aspect-w-[355] tablet:aspect-h-[240] laptop:aspect-w-[288] laptop:aspect-h-[240] desktop:aspect-w-[424] desktop:aspect-h-[256] lg-desktop:aspect-w-[540] lg-desktop:aspect-h-[320]',
6: 'col-span-2 laptop:col-span-6 aspect-w-[358] aspect-h-[240] tablet:aspect-w-[465] tablet:aspect-h-[240] laptop:aspect-w-[440] laptop:aspect-h-[240] desktop:aspect-w-[648] desktop:aspect-h-[256] lg-desktop:aspect-w-[824] lg-desktop:aspect-h-[320]',
},
// ... 配置 4-9 的其他布局
}
/**
* 获取卡片布局类名
* @param index 卡片索引(0-based)
* @param totalCount 卡片总数量
* @returns 布局类名字符串
*/
function getCardLayoutClass(index: number, totalCount: number): string {
const layoutConfig = LAYOUT_CONFIG[totalCount]
if (!layoutConfig) {
console.warn(`No layout config for ${totalCount} cards`)
return ''
}
return layoutConfig[index] || ''
}
// 使用示例
{items.map((item, index) => (
<div
key={index}
className={cn(
'ksp-card',
getCardLayoutClass(index, items.length)
)}
>
{/* 卡片内容 */}
</div>
))}
// 时间复杂度: O(1) - 哈希表查找
// 空间复杂度: O(1) - 常量对象存储2. 动态宽高比系统
根据卡片位置和断点动态计算 aspect ratio:
/**
* 宽高比计算逻辑
* 以卡片 0 (7卡片布局) 为例
*/
const aspectRatioConfig = {
mobile: { w: 358, h: 240 }, // 358/240 ≈ 1.49:1
tablet: { w: 465, h: 240 }, // 465/240 ≈ 1.94:1
laptop: { w: 440, h: 240 }, // 440/240 ≈ 1.83:1
desktop: { w: 648, h: 256 }, // 648/256 ≈ 2.53:1
lgDesktop: { w: 824, h: 320 }, // 824/320 ≈ 2.58:1
}
/**
* 计算宽高比类名
* @param cardIndex 卡片索引
* @param totalCards 卡片总数
* @param breakpoint 断点
* @returns aspect ratio 类名
*/
function getAspectRatioClass(
cardIndex: number,
totalCards: number,
breakpoint: string
): string {
const config = aspectRatioConfig[breakpoint]
return `aspect-w-[${config.w}] aspect-h-[${config.h}]`
}
/**
* 为什么需要动态宽高比?
*
* 1. 栅格列数不同:
* - Mobile: 2列 (每列约50%)
* - Tablet: 3列 (每列约33%)
* - Laptop+: 10列 (col-span-3=30%, col-span-4=40%, col-span-6=60%)
*
* 2. 保持视觉协调:
* - 大卡片(col-span-6)需要更宽的宽高比(2.5:1)
* - 中卡片(col-span-4)使用中等宽高比(1.9:1)
* - 小卡片(col-span-3)使用较窄宽高比(1.5:1)
*
* 3. 屏幕尺寸适配:
* - Desktop+ 增大 height 值,避免卡片过扁
* - Mobile 减小 width 值,适配小屏幕
*/
// 时间复杂度: O(1)
// 空间复杂度: O(1)3. 媒体资源 Fallback 逻辑
智能 fallback 确保所有设备都有媒体显示:
/**
* 获取桌面端图片 URL
* @param item 卡片项
* @returns 桌面端图片 URL
*/
function getDesktopImageUrl(item: KspCardItem): string {
return item.image?.url || item.mobImage?.url || ''
}
/**
* 获取移动端图片 URL
* @param item 卡片项
* @returns 移动端图片 URL
*/
function getMobileImageUrl(item: KspCardItem): string {
return item.mobImage?.url || item.image?.url || ''
}
/**
* 获取桌面端视频 URL
* @param item 卡片项
* @returns 桌面端视频 URL
*/
function getDesktopVideoUrl(item: KspCardItem): string {
return item.video?.url || item.mobVideo?.url || ''
}
/**
* 获取移动端视频 URL
* @param item 卡片项
* @returns 移动端视频 URL
*/
function getMobileVideoUrl(item: KspCardItem): string {
return item.mobVideo?.url || item.video?.url || ''
}
/**
* 判断是否为视频卡片
* @param item 卡片项
* @returns 是否为视频卡片
*/
function isVideoCard(item: KspCardItem): boolean {
return !!(item.video?.url || item.mobVideo?.url)
}
/**
* Fallback 优先级:
*
* 桌面端:
* 1. image (优先)
* 2. mobImage (fallback)
*
* 移动端:
* 1. mobImage (优先)
* 2. image (fallback)
*
* 视频同理
*/
// 使用示例
<Picture
source={getDesktopImageUrl(item)}
alt={item.image?.alt || item.mobImage?.alt || ''}
className="laptop:inline-block hidden" // 仅桌面端显示
/>
<Picture
source={getMobileImageUrl(item)}
alt={item.mobImage?.alt || item.image?.alt || ''}
className="laptop:hidden inline-block" // 仅移动端显示
/>
// 时间复杂度: O(1)
// 空间复杂度: O(1)4. BEM 类名生成算法
完整的 BEM 命名体系,支持深度定制:
/**
* BEM 类名常量
*/
const BEM_CLASSES = {
root: 'ksp-container',
darkTheme: 'aiui-dark',
title: 'ksp-title',
layout: 'ksp-layout',
card: 'ksp-card',
cardImage: 'ksp-card-image',
cardVideo: 'ksp-card-video',
cardVideoDesktop: 'ksp-card-video-desktop',
cardVideoMobile: 'ksp-card-video-mobile',
cardContent: 'ksp-card-content',
cardTitle: 'ksp-card-title',
cardDesc: 'ksp-card-desc',
}
/**
* 生成根容器类名
* @param theme 主题
* @param customClassName 自定义类名
* @returns 完整类名字符串
*/
function getRootClassName(
theme: 'light' | 'dark',
customClassName?: string
): string {
return cn(
BEM_CLASSES.root,
theme === 'dark' && BEM_CLASSES.darkTheme,
customClassName
)
}
/**
* 生成卡片类名
* @param index 卡片索引
* @param totalCount 卡片总数
* @returns 卡片类名字符串
*/
function getCardClassName(index: number, totalCount: number): string {
return cn(
BEM_CLASSES.card,
'relative',
'overflow-hidden',
'rounded-lg',
getCardLayoutClass(index, totalCount)
)
}
/**
* BEM 命名规范:
*
* Block: ksp
* Element: title, layout, card, card-image, card-video, card-content, card-title, card-desc
* Modifier: dark (通过 aiui-dark 类实现)
*
* 示例:
* - ksp-container (Block)
* - ksp-title (Block__Element)
* - ksp-card (Block__Element)
* - ksp-card-title (Block__Element__SubElement)
* - ksp-container.aiui-dark (Block--Modifier)
*/
// 时间复杂度: O(n), n为类名数量(通常很小)
// 空间复杂度: O(1)5. 响应式栅格计算
10 列栅格系统的精确计算:
/**
* 栅格系统配置
*/
const GRID_CONFIG = {
mobile: {
columns: 2,
gap: 12, // gap-3
},
tablet: {
columns: 3,
gap: 16, // gap-4
},
laptop: {
columns: 10,
gap: 16, // gap-4
},
desktop: {
columns: 10,
gap: 16, // gap-4
},
lgDesktop: {
columns: 10,
gap: 16, // gap-4
},
}
/**
* 计算列宽度百分比
* @param colSpan 列跨度
* @param totalColumns 总列数
* @param gap 间距(px)
* @returns 宽度百分比
*/
function calculateColumnWidth(
colSpan: number,
totalColumns: number,
gap: number
): number {
// 总间距 = (列数 - 1) × 间距
const totalGap = (totalColumns - 1) * gap
// 每列宽度 = (100% - 总间距) / 列数
const columnWidth = (100 - totalGap) / totalColumns
// 跨列宽度 = 列宽度 × 跨度 + 间距 × (跨度 - 1)
const width = columnWidth * colSpan + gap * (colSpan - 1)
return width
}
// 示例计算(Laptop, 10列)
// col-span-6 (大卡片):
calculateColumnWidth(6, 10, 16)
// = (100 - 9*16) / 10 * 6 + 16 * 5
// = (100 - 144) / 10 * 6 + 80
// = -4.4 * 6 + 80
// ≈ 59.53%
// col-span-4 (中卡片):
calculateColumnWidth(4, 10, 16)
// ≈ 39.30%
// col-span-3 (小卡片):
calculateColumnWidth(3, 10, 16)
// ≈ 29.19%
// 时间复杂度: O(1)
// 空间复杂度: O(1)6. 文字叠加渲染算法
文字内容叠加在媒体上的渲染逻辑:
/**
* 渲染卡片内容(文字叠加)
*/
function renderCardContent(item: KspCardItem) {
return (
<div
className={cn(
BEM_CLASSES.cardContent,
'absolute',
'bottom-0',
'left-0',
'right-0',
'p-4',
'desktop:p-6',
'lg-desktop:p-8',
'bg-gradient-to-t',
'from-black/60', // 半透明黑色渐变
'to-transparent'
)}
>
{item.title && (
<Heading
as="h3"
size={3}
className={cn(
BEM_CLASSES.cardTitle,
'text-white', // 白色文字确保可读性
'mb-[4px]'
)}
>
{item.title}
</Heading>
)}
{item.desc && (
<Text
as="p"
size={2}
className={cn(
BEM_CLASSES.cardDesc,
'text-white/90', // 稍透明的白色
'text-[14px]',
'desktop:text-[16px]',
'lg-desktop:text-[18px]'
)}
>
{item.desc}
</Text>
)}
</div>
)
}
/**
* 文字叠加关键技术:
*
* 1. 绝对定位: absolute bottom-0 left-0 right-0
* - 覆盖在图片/视频上方
* - 固定在卡片底部
*
* 2. 渐变背景: bg-gradient-to-t from-black/60 to-transparent
* - 从底部向上渐变
* - 确保文字区域有足够对比度
*
* 3. 白色文字: text-white
* - 在深色背景上清晰可读
* - 符合 WCAG 对比度标准(至少 4.5:1)
*
* 4. 响应式内边距: p-4 desktop:p-6 lg-desktop:p-8
* - 小屏幕 16px,桌面端 24px,超大屏 32px
* - 确保文字不贴边
*/
// 时间复杂度: O(1)
// 空间复杂度: O(1)7. 视频处理算法
视频自动播放和响应式显示:
/**
* 渲染视频元素
*/
function renderVideo(
videoUrl: string,
posterUrl: string,
alt: string,
isMobile: boolean
) {
return (
<video
playsInline // 移动端行内播放,不全屏
autoPlay // 自动播放
muted // 静音(确保自动播放不被浏览器阻止)
loop // 循环播放
src={videoUrl}
poster={posterUrl} // 封面图
aria-label={alt}
className={cn(
isMobile ? BEM_CLASSES.cardVideoMobile : BEM_CLASSES.cardVideoDesktop,
'absolute',
'inset-0',
'w-full',
'h-full',
'object-cover',
isMobile ? 'laptop:hidden inline-block' : 'laptop:inline-block hidden',
isMobile && 'translate-y-[-80px]' // 移动端向上偏移
)}
/>
)
}
/**
* 视频处理关键点:
*
* 1. 自动播放要求:
* - muted: 必须静音才能自动播放(浏览器策略)
* - playsInline: 移动端不全屏播放
*
* 2. 封面图 (poster):
* - 视频加载前显示
* - 提升首屏体验
* - 使用 image 字段作为 poster
*
* 3. 响应式显示:
* - 桌面端: laptop:inline-block hidden
* - 移动端: laptop:hidden inline-block
* - 避免两个视频同时加载
*
* 4. 移动端偏移:
* - translate-y-[-80px]
* - 将重要内容移至卡片中心
* - 避免被文字叠加遮挡
*/
// 时间复杂度: O(1)
// 空间复杂度: O(1)设计规范
卡片数量规范
- 推荐数量: 7 个卡片(最常用,布局最丰富)
- 支持范围: 4-9 个卡片
- 最小数量: 4 个(低于 4 个布局配置不存在)
- 最大数量: 9 个(超过 9 个布局会过于拥挤)
为什么推荐 7 个?
7 个卡片布局特点:
- 视觉层次丰富: 大中小三种尺寸交替,创造动态视觉效果
- 不对称美学: 不对称布局更具现代感和吸引力
- 信息密度适中: 不会过于拥挤或过于空旷
- 适合产品页: 7 个卖点刚好涵盖产品核心特性,不会信息过载
标题和描述规范
- 主标题长度: 建议 10-30 个字符,过长影响阅读
- 卡片标题长度: 建议 5-20 个字符,需要在图片上清晰可读
- 卡片描述长度: 建议 10-40 个字符,简洁有力
图片规格
- 桌面端图片尺寸: 推荐 1200x800px 或更大
- 移动端图片尺寸: 推荐 600x800px 或更大
- 图片格式: 推荐 WebP 或 AVIF(fallback 到 JPG/PNG)
- 图片质量: 80-90% 质量,平衡视觉效果和加载速度
- 宽高比: 根据卡片位置自动适配,无需固定比例
视频规格
- 视频格式: 推荐 MP4 (H.264 编码)
- 桌面端视频大小: 建议 5MB 以内
- 移动端视频大小: 建议 2MB 以内(使用压缩版本)
- 视频时长: 建议 5-15 秒,循环播放
- 视频分辨率:
- 桌面端: 1920x1080 或 1280x720
- 移动端: 720x480 或更低
- 封面图 (poster): 必须提供,使用 image/mobImage 字段
文字叠加规范
- 文字背景: 卡片内容区域自动添加半透明渐变背景,确保文字可读性
- 文字颜色: 使用白色文字,根据主题自动调整
- 文字对齐: 左对齐,确保在所有卡片尺寸下保持一致
- 内边距: 移动端 16px,桌面端 24px,超大屏 32px
颜色规范
/* Light 主题 */
:root {
--ksp-text-primary: #111827; /* 主标题 */
--ksp-text-overlay: #ffffff; /* 叠加文字 */
--ksp-overlay-bg: rgba(0, 0, 0, 0.6); /* 叠加背景 */
}
/* Dark 主题 */
:root[data-theme='dark'] {
--ksp-text-primary: #f9fafb;
--ksp-text-overlay: #ffffff;
--ksp-overlay-bg: rgba(0, 0, 0, 0.7);
}无障碍性
Ksp 组件遵循 WCAG 2.1 AA 标准:
语义化 HTML
<section data-ui-component-id="ksp"> {/* 使用 <section> 作为根元素 */}
<Heading as="h1" size={4}> {/* 主标题使用 h1 */}
{title}
</Heading>
<div className="ksp-layout">
{items.map((item, index) => (
<div className="ksp-card"> {/* 卡片容器 */}
<Picture /> {/* 图片使用 Picture 组件 */}
<video /> {/* 视频使用原生 video 标签 */}
<div className="ksp-card-content">
<Heading as="h3" size={3}> {/* 卡片标题使用 h3 */}
{item.title}
</Heading>
<Text as="p" size={2}> {/* 描述使用 p 标签 */}
{item.desc}
</Text>
</div>
</div>
))}
</div>
</section>ARIA 属性
- data-ui-component-id:
"ksp"- 组件标识,便于测试和追踪 - video 元素:
playsInline- 移动端行内播放,不全屏muted- 静音播放,避免打扰用户poster- 视频封面图,提升加载体验loop- 循环播放autoPlay- 自动播放(静音状态下)aria-label- 视频描述
图片无障碍
- alt 属性: 所有 Media 对象必须提供有意义的 alt 文本
- 视频 alt: 描述视频内容,如 “产品清洁演示视频”
- 图片 alt: 描述图片实际内容,避免通用词如 “image”、“photo”
示例:
// ❌ 错误: 通用 alt 文本
image: {
url: '...',
alt: 'Image',
}
// ✅ 正确: 描述性 alt 文本
image: {
url: '...',
alt: 'HydroJet Floor Washing System in action',
}颜色对比度
所有文本与背景的对比度符合WCAG AA标准(最低4.5:1):
// 文字叠加对比度检查
const contrastRatios = {
'白色文字/渐变黑色背景': '12:1', // ✅ #ffffff / rgba(0,0,0,0.6)
'主标题/浅色背景': '7:1', // ✅ #111827 / #ffffff
'卡片描述/叠加背景': '11:1', // ✅ rgba(255,255,255,0.9) / rgba(0,0,0,0.6)
}键盘导航
- 组件为静态展示,无需键盘交互
- 视频自动播放,无需用户操作
- 如需添加交互(如卡片点击跳转),需使用
<a>标签或<button>标签
性能优化
1. 布局配置常量化
布局配置对象常量化,避免重复计算:
// ✅ 正确: 常量化布局配置
const LAYOUT_CONFIG: Record<number, Record<number, string>> = {
7: {
0: 'col-span-2 laptop:col-span-6 ...',
1: 'col-span-1 laptop:col-span-4 ...',
// ...
},
// ... 其他配置
}
// ❌ 错误: 每次渲染都计算
function getCardLayoutClass(index: number, totalCount: number): string {
// 复杂的计算逻辑...
}2. 响应式图片策略
使用 Picture 组件自动处理响应式图片:
// 桌面端显示 image
<Picture
source={image?.url || mobImage?.url}
alt={image?.alt || mobImage?.alt || ''}
className="laptop:inline-block hidden"
/>
// 移动端显示 mobImage
<Picture
source={mobImage?.url || image?.url}
alt={mobImage?.alt || image?.alt || ''}
className="laptop:hidden inline-block"
/>优化要点:
- 使用 CSS 控制显示/隐藏,避免两张图片同时加载
object-cover确保图片填满容器object-bottom定位到底部,避免裁剪重要内容
3. 视频优化
// 桌面端视频
<video
playsInline
autoPlay
muted
loop
src={video?.url}
poster={image?.url} // 使用 image 作为封面图
className="laptop:inline-block hidden" // 仅在 laptop+ 显示
/>
// 移动端视频
<video
playsInline
autoPlay
muted
loop
src={mobVideo?.url}
poster={mobImage?.url} // 使用 mobImage 作为封面图
className="laptop:hidden inline-block" // 仅在 mobile/tablet 显示
/>优化要点:
- 使用 CSS 控制桌面端和移动端视频显示,避免两个视频同时加载
poster属性提供封面图,优化首屏体验playsInline避免移动端全屏播放muted确保自动播放不被浏览器阻止
4. CSS 优化
/* 使用 Tailwind aspect ratio 类 */
aspect-w-[358] aspect-h-[240] /* 动态宽高比 */
/* 使用 Tailwind 断点类 */
tablet:aspect-w-[465] laptop:aspect-w-[440]
/* 避免使用复杂的 CSS 动画 */
/* 卡片布局纯静态,无动画开销 */优化效果:
- Tailwind 类编译为原子 CSS,体积小
- aspect ratio 使用 CSS aspect-ratio 属性(现代浏览器原生支持)
- 无 JS 计算,纯 CSS 布局,性能最优
5. React 优化
// 1. 使用 index 作为 key(卡片顺序固定,不会动态增删)
{items.map((item, index) => (
<Card key={index} {...item} />
))}
// 2. 卡片组件独立抽离
const Card = ({ title, desc, image, mobImage, video, mobVideo }) => {
// 卡片渲染逻辑
}
// 3. 布局计算函数
const getCardLayoutClass = (index: number, totalCount: number): string => {
return LAYOUT_CONFIG[totalCount]?.[index] || ''
}常见问题 FAQ
1. 为什么推荐 7 个卡片?
7 个卡片是经过设计优化的最佳配置:
7 卡片布局特点:
- 行 1: 大卡片 + 小卡片 (2 个)
- 行 2: 小卡片 + 中卡片 + 小卡片 (3 个)
- 行 3: 小卡片 + 大卡片 (2 个)
优势:
- 视觉层次丰富: 大中小三种尺寸交替,创造动态视觉效果
- 不对称美学: 不对称布局更具现代感和吸引力
- 信息密度适中: 不会过于拥挤或过于空旷
- 适合产品页: 7 个卖点刚好涵盖产品核心特性,不会信息过载
其他数量:
- 4-6 个: 适合简化场景,信息量较少
- 8-9 个: 适合信息密集场景,但布局会较拥挤
2. 卡片数量必须是 7 个吗?
不是,组件支持 4-9 个卡片:
// 支持的卡片数量和对应布局
4 个: 2 行布局
5 个: 2 行布局
6 个: 2 行布局
7 个: 3 行布局(推荐)
8 个: 3 行布局
9 个: 3 行布局注意:
- 少于 4 个: 布局配置不存在,组件可能显示异常
- 超过 9 个: 布局配置不存在,超出的卡片可能显示异常
- 建议根据实际需求选择 4-9 个之间的数量
3. 图片和视频可以混合使用吗?
可以,每个卡片可以独立配置:
// 示例: 前 2 个卡片使用视频,其余使用图片
items: [
{
title: 'Video Card 1',
video: { url: 'video1.mp4', alt: 'Video 1' },
mobVideo: { url: 'video1-mobile.mp4', alt: 'Video 1 Mobile' },
image: { url: 'poster1.jpg', alt: 'Poster 1' }, // 作为 poster
mobImage: { url: 'poster1-mobile.jpg', alt: 'Poster 1 Mobile' },
},
{
title: 'Video Card 2',
video: { url: 'video2.mp4', alt: 'Video 2' },
mobVideo: { url: 'video2-mobile.mp4', alt: 'Video 2 Mobile' },
image: { url: 'poster2.jpg', alt: 'Poster 2' },
mobImage: { url: 'poster2-mobile.jpg', alt: 'Poster 2 Mobile' },
},
{
title: 'Image Card 3',
image: { url: 'image3.jpg', alt: 'Image 3' },
mobImage: { url: 'image3-mobile.jpg', alt: 'Image 3 Mobile' },
},
// ... 其他图片卡片
]最佳实践:
- 视频卡片放在前面,吸引用户注意力
- 不要全部使用视频,避免页面加载过慢
- 建议 1-3 个视频卡片,其余使用图片
4. mobImage/mobVideo 不提供会怎样?
组件使用 fallback 机制:
// 图片 fallback
桌面端: image
移动端: mobImage || image // 未提供 mobImage 时使用 image
// 视频 fallback
桌面端: video
移动端: mobVideo || video // 未提供 mobVideo 时使用 video建议:
- 至少提供
image,确保桌面端和移动端都有图片显示 - 如果图片尺寸和比例合适,可以不提供
mobImage - 视频建议提供
mobVideo,使用压缩版本节省移动端流量
5. 如何自定义卡片样式?
组件使用完整的 BEM 类名体系:
/* 根元素 */
.ksp-container { }
.ksp-container.aiui-dark { } /* 深色主题 */
/* 主标题 */
.ksp-title { }
/* 布局容器 */
.ksp-layout { }
/* 卡片 */
.ksp-card { }
.ksp-card-image { }
.ksp-card-video { }
.ksp-card-video-desktop { }
.ksp-card-video-mobile { }
.ksp-card-content { }
.ksp-card-title { }
.ksp-card-desc { }示例:
/* 卡片 hover 效果 */
.ksp-card {
transition: transform 0.3s ease;
}
.ksp-card:hover {
transform: scale(1.02);
}
/* 主标题渐变色 */
.ksp-title {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
/* 卡片标题金色 */
.ksp-card-title {
color: #fbbf24;
}6. 为什么移动端视频有 translate-y-[-80px]?
这是设计需求,为了视觉效果:
className="translate-y-[-80px] object-contain object-center"原因:
- 移动端卡片高度固定(aspect ratio 控制)
- 视频内容可能在顶部或中部
- 向上偏移 80px 可以将视频重要内容移至卡片中心,避免被文字叠加遮挡
如需调整:
- 修改
translate-y值 - 或移除该类,使用默认定位
7. 可以不显示主标题吗?
可以,title 字段是可选的:
<Ksp
data={{
items: [
// 7 个 items 配置
],
// 不提供 title 字段
}}
/>效果: 主标题区域不渲染,卡片布局直接从顶部开始
适用场景:
- 页面其他位置已有标题
- 卡片本身已足够说明功能,无需额外标题
8. 布局配置可以自定义吗?
布局配置在组件内部硬编码:
const LAYOUT_CONFIG: Record<number, Record<number, string>> = {
7: {
0: 'col-span-2 laptop:col-span-6 aspect-w-[358]...',
1: 'col-span-1 laptop:col-span-4 aspect-w-[173]...',
// ...
}
}如需自定义:
- 方案 1: 通过
classNameprop 传入自定义类覆盖 - 方案 2: 复制组件代码,修改
LAYOUT_CONFIG - 方案 3: 使用 CSS 覆盖对应的 BEM 类
不建议修改原因:
- 布局经过精心设计,确保各尺寸协调
- 修改可能导致某些断点显示异常
- 宽高比计算复杂,需要同时修改多个断点
9. 如何确保视频自动播放?
视频自动播放需要满足浏览器策略:
<video
playsInline // ✅ 必需: 移动端行内播放
autoPlay // ✅ 必需: 自动播放
muted // ✅ 必需: 静音(浏览器只允许静音视频自动播放)
loop // ✅ 推荐: 循环播放
src={videoUrl}
poster={posterUrl} // ✅ 推荐: 封面图
/>关键点:
muted是必需的,浏览器只允许静音视频自动播放playsInline避免移动端全屏播放poster提供封面图,优化首屏体验
10. 如何优化移动端流量?
提供移动端专用的压缩资源:
items: [
{
title: 'Feature',
desc: 'Description',
// 桌面端: 高清图片/视频
image: {
url: 'https://cdn.example.com/desktop-1920x1080.jpg', // 1920x1080, ~500KB
alt: 'Feature',
},
video: {
url: 'https://cdn.example.com/desktop-video.mp4', // 1080p, ~5MB
alt: 'Feature Video',
},
// 移动端: 压缩图片/视频
mobImage: {
url: 'https://cdn.example.com/mobile-720x480.jpg', // 720x480, ~150KB
alt: 'Feature Mobile',
},
mobVideo: {
url: 'https://cdn.example.com/mobile-video.mp4', // 480p, ~1.5MB
alt: 'Feature Mobile Video',
},
},
]优化策略:
- 移动端图片: 宽度 600-800px,质量 75-80%
- 移动端视频: 分辨率 720x480 或更低,码率 1-2Mbps
- 使用 WebP/AVIF 图片格式(fallback 到 JPG)
- 使用 H.264 视频编码,兼容性最好
相关资源
- Storybook 文档:
/storybook/ksp- 查看所有变体和交互示例 - 组件源码:
/packages/ui/src/biz-components/Ksp- 查看完整实现代码 - 类型定义:
/packages/ui/src/biz-components/Ksp/types.ts- 完整TypeScript类型 - Figma设计稿: Ksp Design
- 单元测试:
/packages/ui/tests/Ksp.test.tsx- 测试用例和覆盖率 - 布局配置文档:
/docs/ksp-layout-system.md- 详细的布局系统说明 - 响应式断点指南:
/docs/responsive-breakpoints.md- 断点使用最佳实践 - 视频优化指南:
/docs/video-optimization.md- 视频压缩和格式建议 - 无障碍性测试报告:
/docs/ksp-accessibility-audit.md- WCAG合规性报告