Animal Island Vue 设计风格指南
三文档分工(生成代码 / 调样式时按需查阅,避免互相翻查):
AI_USAGE.md— API 手册:每个组件的 props、类型、默认值、合法取值、禁用用法。写代码优先查这里。
skill/SKILL.md(本文档)— 像素级样式:设计 token、每组件精确 CSS(hex/px/keyframe)、Demo 布局、新组件开发模板。要自己实现/扩展样式时查这里。
DESIGN_PROMPT.md— 给外部工具(v0 / Figma AI / Midjourney / DALL-E)的提示词包,含 clip-path、色板速查、禁用清单。只在喂别的 AI 时用。
概述
animal-island-vue 是一套自然可爱小岛风格的 Vue 3 + TypeScript UI 组件库。 设计语言核心:温暖大地色系 + 大圆角 pill 形 + 游戏按键立体感 + 柔和动效 + 几何 / 有机形状并存(几何代表:Title 飘带的 swallowtail clip-path;有机代表:Modal 的 SVG blob)。
源码:
src/components/<ComponentName>/(每组件包含*.vue+index.ts+ 可选types.ts)Demo 站:
demo/pages/<ComponentName>Demo.vue构建:Vite (library mode) +
vite.config.ts(库)/vite.config.docs.ts(Demo)样式系统:scoped
<style lang="less" scoped>+ BEM +src/styles/variables.less设计 token(不使用 CSS Modules)
全量导出清单(40 个 named exports — 含子组件、命令式 API 与伴生导出)
从 src/index.ts 导出:
| 组件 | 职责 | 交互 | 装饰 / 纯展示 |
|---|---|---|---|
BackTop |
返回顶部按钮,钱袋图标浮窗,支持自定义滚动容器、动画时长和可见高度 | ✓ | |
Button |
按钮,5 种类型 × 3 种尺寸 | ✓ | |
Input |
输入框,3 种尺寸 + clear/prefix/suffix | ✓ | |
Switch |
开关,默认/小号 | ✓ | |
Modal |
SVG blob 裁切弹窗 | ✓ | |
Card |
容器,default/dashed,13 种调色板实色 + 13 种 pattern 波点墙纸(CSS radial-gradient,非图片) |
✓ | |
Title |
章节标题,飘带横幅(swallowtail clip-path 燕尾 + 折角阴影 + 微透视正面),13 种配色(替代旧 Card type="title") |
✓ | |
Collapse |
手风琴(动画用 CSS Grid 0fr↔1fr 实现,无 JS 动画) | ✓ | |
Select |
下拉选择器(受控) | ✓ | |
Skeleton |
加载占位骨架屏(SkeletonButton、SkeletonInput、SkeletonAvatar) | ✓ | |
Checkbox |
多选框组,水平/垂直,3 种尺寸 | ✓ | |
Radio |
单选框组,3 种尺寸,键盘 roving tabindex | ✓ | |
Tooltip |
12 种 placement,hover/focus/click 触发,default/island 形态 |
✓ | |
Time |
HUD 实时时钟 | ✓ | |
Footer |
底部装饰(14 个 🎄 居中一行,seamless 铺满整行) |
✓ | |
Divider |
装饰分割线,5 种风格 | ✓ | |
Cursor |
自定义光标包裹器,default 手指箭头 / raindrop 蓝色雨滴(内联 SVG,零图片资源) |
✓ | |
Background |
装饰背景壁纸,dots 波点 / sprinkles 圆柱形彩色针糖(纯 CSS + 内联 SVG,零图片资源) |
✓ | |
Typewriter |
打字机效果,保留 VNode 结构 | ✓ | |
Tabs |
标签页切换,叶子摆动动画可选 | ✓ | |
CodeBlock |
JSX/TS 语法高亮代码块 | ✓ | |
Loading |
全屏遮罩 + SVG spinner(mint #19c8b9,stroke-dasharray 动画) |
✓ | |
Table |
数据表格,固定列、空状态、loading | ✓ | |
Pagination |
分页器,条数切换 / 快速跳转 / 总数展示,可内置于 Table | ✓ | |
Carousel |
轮播图,自动播放 / 箭头 / 圆点 / 键盘导航 | ✓ | |
Countdown |
倒计时,里程表式单向滚动数字 | ✓ | |
Form |
表单系统(Form、FormItem、FormProvider、useForm) | ✓ | |
Image |
相框图片,14 种底色、懒加载、点击预览 | ✓ | |
DatePicker |
日期选择器,日历网格 / 范围选择 | ✓ | |
TimePicker |
时间选择器,时/分/秒滚动列 | ✓ | |
Notification |
命令式通知(含 NotificationContainer) | ✓ | |
Tag |
标签,3 变体 × 13 色 | ✓ | |
Progress |
进度条,斜纹动画填充 | ✓ | |
Drawer |
抽屉,四方向弹出 + 焦点陷阱 | ✓ |
类型导出:BackTopProps、ButtonProps/ButtonType/ButtonSize/ButtonHTMLType、InputProps/InputSize、SwitchProps/SwitchSize、ModalProps、CardProps/CardType/CardColor、TitleProps/TitleSize/TitleColor、CollapseProps、CursorProps/CursorType、BackgroundProps/BackgroundType、DividerProps/DividerType、TypewriterProps、SelectProps/SelectOption、SkeletonProps/SkeletonVariant、TabsProps/TabItem、CheckboxProps/CheckboxOption/CheckboxSize/CheckboxValue、RadioProps/RadioOption/RadioSize/RadioValue、TooltipProps/TooltipPlacement/TooltipTrigger/TooltipVariant、CodeBlockProps、TableProps/TableColumn/TableRecord、PaginationProps/PaginationVariant、CarouselProps、CountdownProps/CountdownSize/CountdownVariant、FormProps 系列、ImageProps/ImageColor、DatePickerProps/DatePickerSize/DatePickerStatus/DatePickerValue、TimePickerProps/TimePickerSize/TimePickerStatus/TimePart、NotificationConfig 系列、TagProps/TagSize/TagVariant/TagColor、ProgressProps/ProgressSize/ProgressInfoPosition、DrawerProps/DrawerPlacement。
Vue 端约定:
受控值统一通过
v-model/v-model:open/v-model:expanded(即modelValue+update:modelValue等事件)React 中的
ReactNodeprops 在 Vue 端改为命名插槽(#icon、#prefix、#suffix、#footer、#checked、#unchecked、#question、#empty、Table 的#cell-{dataIndex}/#header-{dataIndex}、Tabs 按item.key命名的动态插槽等);其余可结构化的内容统一通过默认插槽承载(如 Card、Collapse 答案区、Modal 主体、Typewriter 等)
1. Design Tokens
色彩系统
// 主色(薄荷青绿)
@primary-color: #19c8b9;
@primary-color-hover: #3dd4c6;
@primary-color-active: #11a89b;
@primary-color-bg: #e6f9f6;
// 文字(温暖棕色系)
@text-color: #794f27; // 主文字(header/sidebar)
@text-color-body: #725d42; // 正文(组件内文字)
@text-color-secondary: #9f927d; // 次级文字
@text-color-muted: #8a7b66; // 浅棕(modal body)
@text-color-disabled: #c4b89e; // 禁用
// 边框
@border-color: #9f927d;
@border-color-light: #c4b89e; // 输入框边框
@border-color-hover: #a89878; // 输入框 hover
// 背景(奶油米白)
@bg-color: #f8f8f0; // 主背景
@bg-color-content: rgb(247, 243, 223); // 内容区(Modal、Card)
@bg-color-secondary: #f0e8d8;
@bg-color-disabled: #f0ece2;
@bg-color-input: rgb(247, 243, 223); // 输入框背景
@bg-color-input-dis: #ece8dc; // 输入框禁用
// 状态色
@success-color: #6fba2c;
@success-color-active: #5a9e1e;
@warning-color: #f5c31c;
@warning-color-active: #dba90e;
@error-color: #e05a5a;
@error-color-active: #c94444;
// 游戏特殊色
@focus-yellow: #ffcc00; // 焦点高亮(非蓝色)
@focus-yellow-dark: #e0b800; // 焦点阴影
@sidebar-active-bg: #b7c6e5; // 侧边栏选中背景
@sidebar-hover-bg: #d6dff0; // 侧边栏 hover 背景
// 3D 阴影色
@shadow-btn: #bdaea0; // 按钮 3D 阴影
@shadow-input: #d4c9b4; // 输入框 3D 阴影
@shadow-switch-on: #5a9e1e; // Switch 开启 3D 阴影
应用调色板(Card color prop 可选值):
| color 值 | 背景色 | 文字色 |
|---|---|---|
| default | rgb(247, 243, 223) |
#725d42 |
| app-pink | #f8a6b2 |
#fff |
| purple | #b77dee |
#fff |
| app-blue | #889df0 |
#fff |
| app-yellow | #f7cd67 |
#725d42 |
| app-orange | #e59266 |
#fff |
| app-teal | #82d5bb |
#fff |
| app-green | #8ac68a |
#fff |
| app-red | #fc736d |
#fff |
| lime-green | #d1da49 |
#3d5a1a |
| yellow-green | #ecdf52 |
#725d42 |
| brown | #9a835a |
#fff |
| warm-peach-pink | #e18c6f |
#fff |
字体
项目使用两款 Google Fonts 圆体字,必须按以下方式引入,本地未安装时通过在线地址加载:
<!-- 在 index.html <head> 中引入 -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;500;600;700;800;900&family=Noto+Sans+SC:wght@400;500;700&display=swap"
rel="stylesheet"
/>
或在 CSS / Less 入口文件顶部:
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@400;500;600;700;800;900&family=Noto+Sans+SC:wght@400;500;700&display=swap');
font-family:
Nunito,
'Noto Sans SC',
-apple-system,
'PingFang SC',
'Hiragino Sans GB',
'Microsoft YaHei',
sans-serif;
| 字体 | 用途 | Google Fonts key |
|---|---|---|
| Nunito | 主字体,拉丁字符 | family=Nunito |
| Noto Sans SC | 中文字体,简体覆盖 | family=Noto+Sans+SC |
Vue 版本同时通过
@fontsource/*在src/index.ts直接 import 字体子集(nunito/noto-sans-sc/zen-maru-gothic),库消费者无需手动<link>;如果你脱离组件库自实现,按上文<link>引入即可。如需扩展日文字符,自行@importZen Maru Gothic或类似字体并追加到font-family末尾。
字重分级:
正文内容:500
按钮文字、标题、菜单项:600–700
数字强调(时间数字、时钟):900
placeholder / 说明文字:400
字间距:letter-spacing: 0.01em(正文)/ 0.02em(按钮/标题)/ 1.5px(星期大写)
禁止使用细体(weight < 400)或等宽字体。
间距 / 圆角 / 边框
间距:xs=4px sm=8px md=12px lg=16px xl=24px
圆角:sm=12px base=18px lg=24px pill=50px(按钮/输入框)
边框:默认 2px solid,输入框 2.5px,大尺寸输入框 3px
阴影
/* 卡片/容器阴影(暖色调,非冷黑)*/
box-shadow: 0 3px 10px 0 rgba(61, 52, 40, 0.1); /* 基础 */
box-shadow: 0 8px 24px 0 rgba(61, 52, 40, 0.14); /* 较大 */
/* Card 默认无 box-shadow(依赖 border / pattern 营造层次,不靠悬浮阴影)*/
/* 默认/虚线/文字/链接按钮阴影(柔和 elevation —— 非 3D 厚阴影)*/
box-shadow: 0 2px 4px 0 rgba(61, 52, 40, 0.06); /* btn-default 静止:--animal-shadow-sm */
box-shadow: 0 3px 10px 0 rgba(61, 52, 40, 0.1); /* btn-default hover:--animal-shadow-base */
/* active 回落到 --animal-shadow-sm,translateY(0) */
/* 游戏按键 3D 立体阴影(仅 primary / danger-primary 按钮;Input 仅 shadow={true} 时启用;Switch 仅 track inset 阴影,handle 无 box-shadow)*/
box-shadow: 0 5px 0 0 #bdaea0; /* primary 按钮默认 */
box-shadow: 0 6px 0 0 #bdaea0; /* primary 按钮 hover */
box-shadow: 0 1px 0 0 #bdaea0; /* primary 按钮 active */
box-shadow: 0 5px 0 0 #c94444; /* danger-primary 按钮默认(hover 6 / active 1) */
box-shadow: 0 3px 0 0 #d4c9b4; /* 输入框 shadow={true} 中号 */
box-shadow: 0 2px 0 0 #d4c9b4; /* 输入框 shadow={true} 小号 */
box-shadow: 0 4px 0 0 #d4c9b4; /* 输入框 shadow={true} 大号 */
/* Switch 仅 track 有 inset 阴影:inset 0 2px 4px rgba(114,93,66,0.15) (OFF) / inset 0 2px 4px rgba(90,158,30,0.20) (ON);handle 无 outer box-shadow */
重要:只有 primary 风格按钮(含 danger primary)才使用
0 5px 0 0这种像素级 3D 厚阴影;default/dashed/text/link用上面的柔和 elevation 阴影。把 3D 阴影套到所有按钮上会让界面变得过重过游戏化。
动效
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); /* 通用 */
transition: all 0.15s; /* 快速(clear 按钮等)*/
transition: all 0.3s ease; /* 卡片 */
transition: grid-template-rows 0.3s cubic-bezier(0.4, 0, 0.2, 1); /* 手风琴 */
/* Hover:上浮 */
transform: translateY(-1px); /* 按钮 / 输入框 */
transform: translateY(-2px); /* 卡片 */
/* Switch handle: 始终 translateY(-50%) 垂直居中,无 hover 上浮 */
/* Active:下压(游戏按键反馈)*/
transform: translateY(2px); /* 按钮 active */
/* 出现动画 */
@keyframes animal-zoom-in {
from {
opacity: 0;
transform: scale(0.92);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes animal-fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes ac-fade-up {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
2. 组件精确样式规范
BackTop
返回顶部按钮,钱袋图标浮窗,固定在右下角(position: fixed),滚动超过 visibilityHeight px 后出现。
| 属性 | 说明 | 默认值 |
|---|---|---|
visibilityHeight |
滚动多少 px 后显示 | 400 |
duration |
滚动动画时长(ms) | 300 |
target |
滚动容器函数 | () => window |
onClick |
点击回调 | - |
className |
自定义类名 | - |
style |
自定义样式 | - |
<BackTop :visibilityHeight="400" />
<BackTop :duration="800" />
<BackTop :target="() => containerRef" :visibilityHeight="200" />
Button
| 属性 | small | middle | large |
|---|---|---|---|
| height | 32px | 45px | 48px |
| padding | 0 16px |
0 20px |
0 32px |
| font-size | 12px | 14px | 16px |
| border-radius | 12px | 50px | 24px |
| border-width | 2px | 2px | 2px |
primary 按钮精确值(仅 primary / danger-primary 用 3D 厚阴影):
color: #794f27;
background: #f8f8f0;
border-color: #f8f8f0;
font-weight: 600;
letter-spacing: 0.02em;
line-height: 1;
box-shadow: 0 5px 0 0 #bdaea0;
/* hover */
transform: translateY(-1px);
box-shadow: 0 6px 0 0 #bdaea0;
/* active */
transform: translateY(2px);
box-shadow: 0 1px 0 0 #bdaea0;
/* focus-visible */
outline: 2px solid #19c8b9;
outline-offset: 2px;
/* disabled */
opacity: 0.5;
default / dashed / text / link 按钮(柔和 elevation):
/* 静止 */
box-shadow: var(--animal-shadow-sm); /* 0 2px 4px 0 rgba(61,52,40,0.06) */
/* hover */
color: #19c8b9;
border-color: #19c8b9;
box-shadow: var(--animal-shadow-base); /* 0 3px 10px 0 rgba(61,52,40,0.10) */
transform: translateY(-1px);
/* active */
color: #11a89b;
border-color: #11a89b;
transform: translateY(0);
box-shadow: var(--animal-shadow-sm); /* 回落到静止态 */
不要把 primary 那套
0 5px / 6px / 1px #bdaea0套到 default / dashed 上 —— 整体会显得过重过 cartoon。
loading 斜纹动画(精确值):
background: #0ec4b6;
border: 4px solid #4de2da;
color: #fff;
background-image: repeating-linear-gradient(-45deg, #0ec4b6, #0ec4b6 10px, #01b0a7 10px, #01b0a7 20px);
background-size: 28.28px 28.28px;
animation: animal-btn-loading 1s linear infinite;
@keyframes animal-btn-loading {
0% {
background-position: 0 0;
}
100% {
background-position: -28.28px 0;
}
}
danger primary 按钮:
color: #fff;
box-shadow: 0 5px 0 0 #c94444; /* error-active */
Input
⚠️
shadowprop 默认false:默认无阴影,下表的box-shadow仅在<Input shadow />显式开启时生效。status (error/warning) 阴影与 focus 黄色光晕不受此 prop 控制。
| 属性 | small | middle | large |
|---|---|---|---|
| height | 32px | 40px | 48px |
| padding | 0 14px |
0 18px |
0 22px |
| font-size | 12px | 14px | 16px |
| border-radius | 40px | 50px | 50px |
| border-width | 2.5px | 2.5px | 3px |
box-shadow(仅 shadow={true}) |
0 2px 0 0 #d4c9b4 |
0 3px 0 0 #d4c9b4 |
0 4px 0 0 #d4c9b4 |
精确颜色值:
background: rgb(247, 243, 223);
border: 2.5px solid #c4b89e;
/* 默认无 box-shadow;shadow={true} 时按上表中号取 0 3px 0 0 #d4c9b4 */
/* 文字 */
color: #725d42;
font-weight: 500;
letter-spacing: 0.01em;
/* placeholder */
color: #c4b89e;
font-weight: 400;
/* prefix/suffix */
color: #a0936e;
/* prefix margin-right */
margin-right: 6px;
/* suffix margin-left */
margin-left: 6px;
/* hover */
border-color: #a89878;
box-shadow: 0 3px 0 0 #c4b89e;
/* focus */
border-color: #ffcc00;
box-shadow:
0 3px 0 0 #e0b800,
0 0 0 3px rgba(255, 204, 0, 0.15);
/* disabled */
background: #ece8dc;
border-color: #d4c9b4;
box-shadow: none;
opacity: 0.6;
color: #c4b89e;
/* error */
box-shadow: 0 3px 0 0 #c94444;
/* warning */
box-shadow: 0 3px 0 0 #dba90e;
clear 按钮:
width: 20px;
height: 20px;
margin-left: 4px;
color: #c4b89e;
font-size: 13px;
font-weight: 700;
border-radius: 50%;
transition: all 0.15s;
/* hover */
color: #725d42;
background: rgba(114, 93, 66, 0.1);
Switch
默认尺寸:
min-width: 52px;
height: 28px;
border: 2.5px solid #c4b89e;
border-radius: 50px;
background: #d4c9b4;
box-shadow: inset 0 2px 4px rgba(114, 93, 66, 0.15);
/* handle */
width: 21px;
height: 21px;
top: 50%;
left: 2px;
transform: translateY(-50%); /* 垂直居中 */
background: rgb(247, 243, 223);
border: 2.5px solid #bdaea0;
border-radius: 50%;
/* handle 无 outer box-shadow,仅靠 border 与 track inset 阴影分层 */
/* 开启态 */
background: #86d67a;
border-color: #6fba2c;
box-shadow: inset 0 2px 4px rgba(90, 158, 30, 0.2);
/* handle 开启后 left */
left: calc(100% - 24px);
border-color: #5a9e1e;
/* focus-visible */
outline: 2px solid #ffcc00;
outline-offset: 2px;
/* disabled */
opacity: 0.5;
small 尺寸:
min-width: 38px;
height: 20px;
border-width: 2px;
/* handle */
width: 14px;
height: 14px;
top: 1px;
left: 1px;
box-shadow: 0 2px 0 0 #bdaea0;
/* 开启 handle left */
left: calc(100% - 16px);
box-shadow: 0 2px 0 0 #5a9e1e;
inner 文字(#checked / #unchecked 插槽):
font-size: 11px;
font-weight: 700;
color: #fff;
line-height: 1;
letter-spacing: 0.02em;
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
padding: 0 8px 0 28px; /* 未开启 */
padding: 0 28px 0 8px; /* 开启 */
/* small 版 */
padding: 0 6px 0 20px;
font-size: 9px;
loading spinner:
width: 11px;
height: 11px;
border: 2px solid #6fba2c;
border-right-color: transparent;
border-radius: 50%;
animation: animal-spin 0.6s linear infinite;
/* 关闭态 */
border-color: #a89878;
@keyframes animal-spin {
to {
transform: rotate(360deg);
}
}
Card
/* 默认 */
border-radius: 20px;
background: rgb(247, 243, 223);
padding: 16px 24px;
color: #725d42;
font-weight: 500;
/* 默认 NO box-shadow(依赖 border / pattern 分层,不靠悬浮阴影)*/
transition: all 0.3s ease;
/* hover */
transform: translateY(-2px);
/* dashed 类型 */
border: 2px dashed #e8dcc8;
background: rgb(250, 248, 242);
box-shadow: none;
/* pattern 叠加(pattern !== 'none' 时,纯 CSS 实现,**无 png/svg**) */
/* 双层 radial-gradient 点阵 + 同色调 1.5px solid 边框 + pastel 浅底,
13 种命名(default / app-pink / purple / app-blue / app-yellow / app-orange /
app-teal / app-green / app-red / lime-green / yellow-green / brown / warm-peach-pink)
与 Card.color 同名,但呈现为浅底波点"墙纸"而非实色块。 */
/* 例:pattern="app-pink" */
background:
radial-gradient(circle, rgba(248, 166, 178, 0.18) 1.5px, transparent 1.5px) 0 0/28px 28px,
radial-gradient(circle, rgba(255, 200, 210, 0.12) 1px, transparent 1px) 7px 7px/14px 14px,
#fde4e8;
border: 1.5px solid #f8a6b2;
color: #a85565;
/* 当 color 与 pattern 同时设置时,pattern 视觉上覆盖 color */
旧版
Card type="title"在 v0.9.x 移除,章节标题请使用独立的<Title>组件(见下文)。
Title(飘带 Ribbon 章节标题)
替代旧 Card type="title",渲染游戏风飘带横幅:燕尾两端 + 折角阴影 + 微透视正面主体。
源码:src/components/Title/Title.vue(scoped Less,BEM 类名 animal-title__*)。
/* 默认(绿色配色,可被 .animal-title--color-* 覆盖) */
--rf: #27d039; /* front 正面 */
--rb: #20992a; /* back 燕尾 */
--rk: #115017; /* fold 折角阴影 */
--rt: #fff; /* text 文字色 */
font-family: Nunito, 'Noto Sans SC', sans-serif;
font-weight: 800; /* 外层 wrapper */
/* .animal-title__text 内层文字 font-weight 900;padding-top 0.11em CJK 光学居中 */
/* 飘带主体 */
display: inline-flex;
height: 2em;
padding: 0 1.6em;
letter-spacing: 0.04em;
filter: drop-shadow(0 0.08em 0.12em rgba(0, 0, 0, 0.05));
/* 燕尾(左/右)—— clip-path 鱼尾形 */
.animal-title__back--left {
clip-path: polygon(100% 0%, 100% 100%, 0% 100%, 30% 50%, 0% 0%);
}
.animal-title__back--right {
clip-path: polygon(0% 0%, 100% 0%, 70% 50%, 100% 100%, 0% 100%);
}
width: 1.7em;
height: 1.7em;
bottom: -0.4em;
/* 折角阴影 —— CSS border 三角 */
.animal-title__fold--left {
border-width: 0 0.95em 0.45em 0;
border-color: transparent var(--rk) transparent transparent;
}
.animal-title__fold--right {
border-width: 0 0 0.45em 0.95em;
border-color: transparent transparent transparent var(--rk);
}
/* 正面主体 */
.animal-title__front {
inset: 0 0.1em;
border-radius: 0.2em;
transform: perspective(11.5em) rotateX(3deg);
}
尺寸(SIZE_MAP 通过 inline font-size 注入;所有内部 em 自动缩放):
| size | font-size |
|---|---|
| small | 14px |
| middle | 20px |
| large | 28px |
13 种颜色覆盖:在 wrapper 上叠加 .animal-title--color-app-pink / .animal-title--color-purple / .animal-title--color-app-blue / .animal-title--color-app-yellow / .animal-title--color-app-orange / .animal-title--color-app-teal / .animal-title--color-app-green / .animal-title--color-app-red / .animal-title--color-lime-green / .animal-title--color-yellow-green / .animal-title--color-brown / .animal-title--color-warm-peach-pink 之一;每个类同时覆盖 --rf / --rb / --rk / --rt 四个变量。详见 Title.vue <style scoped> 末尾的 13 行 .animal-title--color-* 定义。
例:
.animal-title--color-app-yellow {
--rf: #f7cd67;
--rb: #d4a030;
--rk: #8a6010;
--rt: #725d42;
}
.animal-title--color-purple {
--rf: #b77dee;
--rb: #9050d0;
--rk: #5a1a9a;
--rt: #fff;
}
Collapse
/* 外层卡片 */
border-radius: 18px;
border: 2px solid #9f927d;
margin-bottom: 12px;
/* disabled */ opacity: 0.6;
/* 问题栏 */
padding: 16px 24px;
gap: 12px;
/* 图标圆圈 */
width: 28px; height: 28px;
background: #19c8b9;
color: #fff;
border-radius: 50%;
font-size: 18px; font-weight: 700;
box-shadow: 0 2px 4px rgba(25, 200, 185, 0.3);
/* 展开时 */ transform: rotate(180deg);
/* 叶子装饰 */
opacity: 0.5;
/* 展开时 */ opacity: 1; transform: rotate(45deg);
/* 问题文字 */
font-size: 16px; font-weight: 600; line-height: 1.4;
/* 答案展开(CSS Grid trick,无 JS)*/
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 0.3s cubic-bezier(0.4, 0, 0.2, 1);
/* 展开 */ grid-template-rows: 1fr;
/* 内层 */ overflow: hidden;
/* 答案文字 */
padding: 0 24px;
font-size: 14px; line-height: 1.7;
/* 展开后 padding-bottom */ 24px;
Tabs
scoped Less + BEM;类名根 .animal-tabs,内部使用 __list / __item / __icon / __leaf / __content 等子元素。
/* 外层容器 */
.animal-tabs {
background: rgb(247, 243, 223);
border-radius: 20px;
border: 2px solid #9f927d;
overflow: hidden;
}
/* 标签列表 */
.animal-tabs__list {
display: flex;
gap: 4px;
padding: 12px;
background: rgba(255, 255, 255, 0.6);
border-bottom: 2px solid #c4b89e;
}
/* 标签项 */
.animal-tabs__item {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 14px;
background: transparent;
border: none;
border-radius: 50px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
color: #8a7b66;
transition: all 0.2s ease;
}
/* hover */
.animal-tabs__item:hover {
background: rgba(25, 200, 185, 0.1);
color: #725d42;
}
/* 激活状态 — 实心 teal 胶囊 + 奶油色字 */
.animal-tabs__item.animal-tabs__item--active {
background: #0cc0b5;
color: #fff9e3;
font-weight: 600;
}
.animal-tabs__item--active.animal-tabs__item--shadow {
box-shadow: 0 3px 0 0 #d4c9b4; /* 仅 shadow opt-in 时启用 */
}
/* 标签图标 */
.animal-tabs__icon {
font-size: 10px;
}
/* 激活时图标放大 */
.animal-tabs__item--active .animal-tabs__icon {
transform: scale(1.2);
}
/* 叶子装饰动画 */
.animal-tabs__leaf {
position: absolute;
right: -6px;
top: -3px;
font-size: 12px;
animation: leafWiggle 2s ease-in-out infinite;
}
/* leafAnimation={false} 时追加 .animal-tabs__leaf--static 修饰符去除 animation */
@keyframes leafWiggle {
0%,
100% {
transform: rotate(0deg);
}
25% {
transform: rotate(-10deg);
}
75% {
transform: rotate(10deg);
}
}
/* 内容区 */
.animal-tabs__content {
padding: 24px;
animation: fadeIn 0.25s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
Modal
SVG clip-path 完整 path d 值(精确还原 blob 轮廓):
<template>
<svg style="position: absolute; width: 0; height: 0" aria-hidden="true">
<defs>
<clipPath id="animal-modal-clip" clipPathUnits="objectBoundingBox">
<path
d="M0.501,0.005 L0.501,0.005 L0.523,0.005 L0.549,0.006
C0.704,0.01,0.796,0.017,0.825,0.027
L0.827,0.028
C0.872,0.045,0.939,0.044,0.978,0.17
C1,0.254,1,0.365,0.99,0.505
L0.988,0.513
C0.979,0.558,0.971,0.598,0.965,0.633
C0.956,0.689,0.979,0.77,0.964,0.865
C0.953,0.928,0.921,0.966,0.869,0.979
C0.821,0.986,0.773,0.992,0.726,0.995
L0.712,0.996 L0.694,0.997
C0.648,1,0.586,1,0.507,1
L0.501,1 L0.464,1
C0.385,1,0.325,0.998,0.283,0.995
C0.234,0.992,0.184,0.987,0.133,0.979
C0.081,0.966,0.05,0.928,0.039,0.865
C0.023,0.77,0.047,0.689,0.037,0.633
C0.031,0.595,0.023,0.552,0.013,0.505
C-0.006,0.365,-0.002,0.254,0.024,0.17
C0.064,0.045,0.13,0.045,0.174,0.028
L0.175,0.028
C0.204,0.017,0.303,0.009,0.474,0.005
L0.501,0.005"
/>
</clipPath>
</defs>
</svg>
</template>
Modal 精确样式:
/* 遮罩 */
background: rgba(0, 0, 0, 0.35);
animation: animal-fade-in 0.25s ease;
z-index: 1000;
/* 弹窗容器 */
max-width: calc(100vw - 32px);
max-height: calc(100vh - 64px);
animation: animal-zoom-in 0.3s ease;
/* 裁切内容区 */
clip-path: url(#animal-modal-clip);
background: rgb(247, 243, 223);
color: rgb(128, 115, 89);
padding: 48px 48px 32px 48px;
/* 标题 */
font-size: 28px;
font-weight: 700;
color: rgba(114, 93, 66, 1);
padding-bottom: 15px;
/* 关闭按钮 */
width: 32px;
height: 32px;
font-size: 22px;
color: rgba(114, 93, 66, 0.6);
border-radius: 50%;
transition: all 0.2s;
/* hover */
background: rgba(114, 93, 66, 0.1);
color: rgba(114, 93, 66, 1);
/* body */
font-size: 20px;
font-weight: 600;
line-height: 1.6;
color: #8a7b66;
padding-bottom: 20px;
/* footer */
gap: 12px;
/* 普通按钮 */
height: 40px;
padding: 0 24px;
font-size: 18px;
border: 2px solid rgba(114, 93, 66, 0.3);
border-radius: 39.81px;
transition: all 0.2s;
line-height: 1;
/* hover */
border-color: rgba(114, 93, 66, 0.6);
background: rgba(114, 93, 66, 0.08);
/* 主按钮(确认)*/
color: rgba(114, 93, 66, 1);
background: rgba(255, 204, 0, 1); /* 游戏黄色!*/
border-color: rgba(255, 204, 0, 1);
/* hover */
background: rgba(255, 204, 0, 0.85);
border-color: rgba(255, 204, 0, 0.85);
Footer
<template>
<Footer />
<!-- 14 个 🎄 居中一行(高 80px) -->
<Footer seamless />
<!-- 🎄 铺满整行(space-between) -->
</template>
.animal-footer {
// 14 个 🎄 emoji 居中一行(flex + letter-spacing),非图片
width: 100%;
height: 80px;
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
letter-spacing: 12px;
}
.animal-footer--seamless {
justify-content: space-between;
padding: 0 8px;
letter-spacing: 0;
}
- 无
typeprop(原sea海浪类型与森林像素图均已移除),仅保留seamlessprop。
Divider
<template>
<Divider type="line-brown" />
<!-- 默认 -->
<Divider type="line-teal" />
<Divider type="line-white" />
<Divider type="line-yellow" />
<Divider type="wave-yellow" />
</template>
.animal-divider {
width: 100%;
height: 12px;
background: url('./img/divider-line-brown.svg') center/contain no-repeat;
}
.animal-divider--line-teal {
background-image: url('./img/divider-line-teal.svg');
}
.animal-divider--line-white {
background-image: url('./img/divider-line-white.svg');
}
.animal-divider--line-yellow {
background-image: url('./img/divider-line-yellow.svg');
}
.animal-divider--wave-yellow {
background-image: url('./img/wave-yellow.svg');
}
默认 SVG 色值参考:#D8D0C3(米褐),viewBox="0 0 297 14"。
Cursor
<template>
<Cursor>
<App />
<!-- 此范围内所有元素变为光标 -->
</Cursor>
<!-- type="raindrop":蓝色雨滴光标 -->
<Cursor type="raindrop">
<MapCanvas />
</Cursor>
<!-- forceAll=false:保留交互语义(a/button 仍是 pointer,input 仍是 text) -->
<Cursor :force-all="false">
<FormPage />
</Cursor>
</template>
Props:type('default' | 'raindrop',默认 'default')、forceAll(boolean,默认 true)。
样式文件为 普通全局 CSS(非 scoped;类名固定为 animal-cursor,挂在根 <div> 上):
/* 内联 SVG data-URI(无外部图片资源):28×28 几何箭头,hotspot 6 4 */
.animal-cursor--force,
.animal-cursor--force * {
cursor: url("data:image/svg+xml,...") 6 4, default !important;
}
/* type="raindrop":32×32 蓝色水滴,hotspot 16 6 */
.animal-cursor--force.animal-cursor--raindrop,
.animal-cursor--force.animal-cursor--raindrop * {
cursor: url("data:image/svg+xml,...") 16 6, default !important;
}
/* forceAll=false(scoped 模式):容器自身用自定义光标,后代恢复 auto,
交互元素(a/button/select 等)恢复 pointer、文本输入恢复 text、禁用态 not-allowed */
.animal-cursor.animal-cursor--scoped { cursor: url("data:image/svg+xml,...") 6 4, default !important; }
.animal-cursor--scoped * { cursor: auto !important; }
使用
!important覆盖默认光标⚠️ 此组件不能用
scoped:scoped 选择器无法穿透 slot 内容;必须以全局 CSS 形式注册(<style>不带scoped,或全局样式入口引入)
Background
装饰背景壁纸容器(纯 CSS + 内联 SVG,零图片资源),子内容渲染在图案之上。
<template>
<!-- dots 波点壁纸(默认):两层错位圆点(28px 大点 + 14px 小点,绿色系 #bfe3bf 底) -->
<Background style="height: 200px" />
<!-- sprinkles 圆柱形彩色针糖壁纸:三层互质 tile(190×170 / 230×195 / 255×215),
内联 SVG 胶囊(圆角矩形 + 竖向高光渐变模拟圆柱受光),重复周期约 220000×280000px,
视觉上随机散落;#fdf3e3 奶油底 -->
<Background type="sprinkles" :style="{ minHeight: '200px', padding: '24px' }">
<p>内容渲染在图案背景之上</p>
</Background>
</template>
Props:type('dots' | 'sprinkles',默认 'dots')。容器为 position: relative; width: 100%; min-height: 100%,需通过 style 给显式高度。类名 animal-background(基础)+ animal-background--sprinkles(修饰类在根元素上,scoped 样式中必须放顶层)。
Typewriter
<template>
<Typewriter :speed="90" :trigger="openCount" auto-play @done="handleDone">
<p>第一行 <strong>加粗</strong></p>
<p>第二行</p>
</Typewriter>
</template>
Props:
| name | type | default | 说明 |
|---|---|---|---|
| 默认插槽 | Slot |
— | 要逐字打出的内容,保留原有元素结构 / 换行 / 样式 |
speed |
number (ms) |
90 |
每字间隔 |
trigger |
unknown |
— | 值变化即重新播放(通常传递弹窗 open 次数或递增 key) |
autoPlay |
boolean |
true |
false 直接全量显示 |
@done |
() => void |
— | 播放完成事件 |
实现要点:
countText(vnode):递归统计 VNode 树(含 children /el?.textContent/ 字符串子节点)的纯文本长度renderTruncated(vnode, state):按剩余字符数递归裁剪,使用cloneVNode保留原节点与样式watch([() => total.value, () => props.speed, () => props.trigger, () => props.autoPlay]),内部setInterval按步递增count无样式文件,不包裹任何额外 DOM(默认插槽直接 render),对布局零影响
Checkbox
Props:
| name | type | default | 说明 |
|---|---|---|---|
options |
CheckboxOption[] |
— | 必填;每项 { label, value, disabled? } |
modelValue |
Array<string | number> |
— | 受控选中值(配合 v-model) |
defaultValue |
Array<string | number> |
[] |
非受控默认值 |
size |
'small' | 'middle' | 'large' |
'middle' |
尺寸 |
disabled |
boolean |
false |
禁用全部项 |
direction |
'horizontal' | 'vertical' |
'horizontal' |
排列方向 |
@update:modelValue |
(values) => void |
— | 选中值变化(v-model) |
@change |
(values) => void |
— | 同上,业务回调 |
尺寸表(box 方框):
| 属性 | small | middle | large |
|---|---|---|---|
| 宽高 | 18×18px | 22×22px | 28×28px |
| border-width | 2px | 2.5px | 3px |
| 标签 font-size | 12px | 14px | 16px |
| 对勾 font-size | 11px | 13px | 16px |
精确样式:
/* group */
display: flex; flex-wrap: wrap;
gap: 12px; /* horizontal */
/* vertical */ flex-direction: column; gap: 8px;
/* item */
display: inline-flex; align-items: center;
gap: 8px;
cursor: pointer;
transition: all 0.25s cubic-bezier(0.4,0,0.2,1);
/* box(未选)*/
background: rgb(247, 243, 223);
border: 2.5px solid #c4b89e;
border-radius: 8px;
display: inline-flex; align-items: center; justify-content: center;
/* box hover */
border-color: #19c8b9;
transform: translateY(-1px);
/* box focus-visible */
outline: 2px solid #ffcc00; outline-offset: 2px;
/* 选中 */
background: #19c8b9;
border-color: #11a89b;
/* 选中 hover */ background: #3dd4c6; border-color: #19c8b9;
/* 对勾 ✓ */
color: #fff; font-weight: 700; line-height: 1;
animation: animal-checkbox-pop 0.15s cubic-bezier(0.4,0,0.2,1);
@keyframes animal-checkbox-pop {
0% { transform: scale(0.4); opacity: 0; }
60% { transform: scale(1.2); }
100% { transform: scale(1); opacity: 1; }
}
/* label */
color: #725d42; font-weight: 500;
letter-spacing: 0.01em;
/* item hover */ label color: #794f27;
/* 禁用(单项或整组)*/
cursor: not-allowed;
opacity: 0.55;
/* box */ background: #f0ece2; border-color: #d4c9b4; transform: none !important;
/* label */ color: #c4b89e;
CodeBlock
Props:
| name | type | default | 说明 |
|---|---|---|---|
code |
string |
— | 必填;原始源码字符串,内部自动按 JSX/TS 分词高亮 |
copyable |
boolean |
true |
是否显示右上角复制按钮 |
Emits:copy(code: string) — 复制成功后触发。
class 与非布局类 :style 键落在 <pre> 上;width / min-width / max-width / margin* 键落在外层 wrapper(inheritAttrs: false 手动分派)。显示复制按钮且未自定义 padding / padding-right 时,<pre> 自动加 padding-right: 96px 给按钮留位。
结构(BEM,scoped Less):
<div class="animal-code-block"> <!-- wrapper: relative, min-width 0, margin 1em 0 -->
<pre class="animal-code-block__code"><!-- 高亮内容 -->
<button class="animal-code-block__copy-btn">复制</button>
</div>
复制逻辑与 React 版一致:优先 navigator.clipboard.writeText,不可用时降级临时 textarea + document.execCommand('copy')(finally 中清理临时节点);按钮三态文案 复制 / 已复制 / 复制失败,2s 后自动复位(卸载时清定时器);成功时 emit copy。
/* 复制按钮 */
.animal-code-block__copy-btn {
position: absolute;
top: 12px;
right: 12px;
min-width: 62px;
height: 32px;
padding: 0 12px;
border: 1px solid rgba(232, 213, 188, 0.3);
border-radius: 50px;
background: rgba(61, 48, 40, 0.94);
color: #e8d5bc;
font-size: 12px;
font-weight: 700;
}
.animal-code-block__copy-btn:hover {
background: #4b3b31;
transform: translateY(-1px);
}
.animal-code-block__copy-btn:focus-visible {
outline: 2px solid var(--animal-primary-color, #19c8b9);
outline-offset: 2px;
}
pre 默认主题:
box-sizing: border-box;
width: 100%;
margin: 0;
padding: 20px 24px;
background: #2b2118;
border: 1px solid #3d3028;
border-radius: 20px;
font-size: 14px;
line-height: 1.7;
font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', Consolas, monospace;
font-weight: 600;
color: #e8d5bc;
white-space: pre;
overflow: auto;
tab-size: 4;
Token 调色板(COLORS 常量):
| token | 颜色 | 覆盖 | |
|---|---|---|---|
| comment | #6b5e50 |
/* */、// |
|
| string | #a8d4a0 |
反引号 / 单双引号、数字 | |
| keyword | #d4a0e0 |
import/export/const/return/async/...、true/false/null/undefined |
|
| react | #e06c75 |
Vue/ref/computed/onMounted/defineProps/defineEmits/PropType/...(保留 React 名字以兼容跨框架代码片段) |
|
| component | #80c0e0 |
大写驼峰标识符(组件名、类型名) | |
| func | #61afef |
小写标识符后跟 ( |
|
| prop | #e8c87a |
标识符后跟 =(template props / 赋值) |
|
| jsx | #f0a870 |
<Tag、</Tag、/> |
|
| operator | #d4b896 |
{}[]();, 和 `+-*/=<>& |
^~?:` 等 |
| default | #e8d5bc |
其余文本 |
不支持
languageprop;非 JS/TS 代码(Python/Shell/SQL)会按通用规则着色,显示可能不准确。不带行号或折行;复制按钮默认显示(copyable: false关闭)。
Radio
源码:src/components/Radio/Radio.vue(scoped Less + BEM 类名 animal-radio__*)。
| 属性 | small | middle | large |
|---|---|---|---|
| 外盒尺寸 | 18×18px | 22×22px | 28×28px |
…(truncated)