Storybook 在 preview 中配置视口参数:INITIAL_VIEWPORTS 与 initialGlobals 实践指南
导读
在 Storybook 中渲染 Story 的 iframe 尺寸由 viewport 特性控制,这是开发响应式 UI 的核心手段。默认情况下 Storybook 只内置一套精简视口(MINIMAL_VIEWPORTS),而当你想把设备预设替换为涵盖 iPhone、iPad、Galaxy、Pixel、Nexus 等数十款主流设备的完整设备清单,并把某款设备设为组件初始渲染尺寸时,就需要在 .storybook/preview.* 中同时使用 parameters.viewport.options 与 initialGlobals。本文以仓库中的 addon-viewport-options-in-preview.md 代码片段为骨架,结合 viewport.mdx 官方文档与 code/core/src/viewport 目录下的源码实现,完整演示配置写法、两种预设视口清单、底层类型结构与参数解析原理,帮助你一步到位完成全局视口定制。
为什么要覆盖默认视口配置
viewport 特性允许你调整渲染 Story 的 iframe 尺寸。开箱即用时,Storybook 使用一套"最小可用"的预设视口集合(即 MINIMAL_VIEWPORTS),它只包含覆盖常见响应式断点的 4 种尺寸:mobile1、mobile2、tablet、desktop。例如在源码 defaults.ts 中,它们的定义是:
| Key | 描述 | 尺寸(w×h, px) |
|---|---|---|
mobile1 |
Small mobile | 320 × 568 |
mobile2 |
Large mobile | 414 × 896 |
tablet |
Tablet | 834 × 1112 |
desktop |
Desktop | 1024 × 1280 |
从源码可以看到每个视口条目都是一个对象,包含可读的 name、带单位的 styles.height/styles.width 以及用于工具栏分组的 type。这套精简集合适合快速冒烟测试,但若要逐个还原真实移动端/平板场景,就需要切换到更详尽的内置设备清单——INITIAL_VIEWPORTS。
viewport 参数的入口在项目级配置文件 .storybook/preview.*(对应官方文档的 Configure story rendering)。在 preview 文件中,你可以通过两条声明完成定制:
parameters.viewport.options—— 决定"可用视口有哪些";initialGlobals.viewport—— 决定"Story 首次渲染时用哪个视口"。
在 preview 中启用 INITIAL_VIEWPORTS 并设定初始设备
下面这段配置即为本文核心片段:把完整的 INITIAL_VIEWPORTS 导出注入 options,同时用 initialGlobals 将初始视口固定为 iPad(ipad),且不旋转。
JavaScript(CSF 3 写法)
import { INITIAL_VIEWPORTS } from 'storybook/viewport';
export default {
parameters: {
viewport: {
options: INITIAL_VIEWPORTS,
},
},
initialGlobals: {
viewport: { value: 'ipad', isRotated: false },
},
};
TypeScript(CSF 3 写法)
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc.
import type { Preview } from '@storybook/your-framework';
import { INITIAL_VIEWPORTS } from 'storybook/viewport';
const preview: Preview = {
parameters: {
viewport: {
options: INITIAL_VIEWPORTS,
},
},
initialGlobals: {
viewport: { value: 'ipad', isRotated: false },
},
};
export default preview;
CSF Next(使用 definePreview)写法
不同框架接入的是各自包导出的 definePreview:
// Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite)
import { definePreview } from '@storybook/your-framework';
import { INITIAL_VIEWPORTS } from 'storybook/viewport';
export default definePreview({
parameters: {
viewport: {
options: INITIAL_VIEWPORTS,
},
},
initialGlobals: {
viewport: { value: 'ipad', isRotated: false },
},
});
// Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite)
import { definePreview } from '@storybook/your-framework';
import { INITIAL_VIEWPORTS } from 'storybook/viewport';
export default definePreview({
parameters: {
viewport: {
options: INITIAL_VIEWPORTS,
},
},
initialGlobals: {
viewport: { value: 'ipad', isRotated: false },
},
});
import { definePreview } from '@storybook/vue3-vite';
import { INITIAL_VIEWPORTS } from 'storybook/viewport';
export default definePreview({
parameters: {
viewport: {
options: INITIAL_VIEWPORTS,
},
},
initialGlobals: {
viewport: { value: 'ipad', isRotated: false },
},
});
import { definePreview } from '@storybook/vue3-vite';
import { INITIAL_VIEWPORTS } from 'storybook/viewport';
export default definePreview({
parameters: {
viewport: {
options: INITIAL_VIEWPORTS,
},
},
initialGlobals: {
viewport: { value: 'ipad', isRotated: false },
},
});
import { definePreview } from '@storybook/angular';
import { INITIAL_VIEWPORTS } from 'storybook/viewport';
export default definePreview({
parameters: {
viewport: {
options: INITIAL_VIEWPORTS,
},
},
initialGlobals: {
viewport: { value: 'ipad', isRotated: false },
},
});
import { definePreview } from '@storybook/web-components-vite';
import { INITIAL_VIEWPORTS } from 'storybook/viewport';
export default definePreview({
parameters: {
viewport: {
options: INITIAL_VIEWPORTS,
},
},
initialGlobals: {
viewport: { value: 'ipad', isRotated: false },
},
});
import { definePreview } from '@storybook/web-components-vite';
import { INITIAL_VIEWPORTS } from 'storybook/viewport';
export default definePreview({
parameters: {
viewport: {
options: INITIAL_VIEWPORTS,
},
},
initialGlobals: {
viewport: { value: 'ipad', isRotated: false },
},
});
需要说明的是:CSF Next 的 definePreview 从具体框架包导入(如 @storybook/react-vite、@storybook/vue3-vite、@storybook/angular、@storybook/web-components-vite),而 TS 写法中类型来源应替换为你实际使用的框架(例如 react-vite、nextjs、vue3-vite 等)。
initialGlobals 的三个关键约定
片段中的 initialGlobals.viewport 是一个对象,接受两个字段:
value:初始视口的 key,必须与options中某个视口的 key 严格对应。上例中取'ipad',即打开 Storybook 时每个 Story 默认以 768 × 1024 的 iPad 竖屏尺寸渲染。isRotated:boolean。为true时将当前视口旋转 90°,实现横屏(landscape)与竖屏(portrait)的等价切换。'ipad'旋转后就变成 1024 × 768 的横屏形态。viewport就是该模块贡献的 global 命名空间(在 preview.ts 中通过PARAM_KEY注册,其默认初始状态为{ value: undefined, isRotated: false })。
值得注意的行为差异(官方文档明确说明):
通过
initialGlobals(或在 story/meta 中通过globals)为某个 Story 或某组 Story 指定视口后,该视口会被锁定,无法再通过工具栏切换。这一机制适合"这条 Story 必须始终在指定设备上呈现"的固定场景。
若要保留工具栏切换能力,则只配置 options,不要为对应层级设置全局值。
INITIAL_VIEWPORTS:完整的预设设备清单
INITIAL_VIEWPORTS 是 Viewport 模块随 storybook/viewport 导出的完整预设集合,覆盖从 iPhone 5 到 iPhone 14 Pro Max、iPad 全系、Galaxy、Nexus、Pixel 等主流设备。下表为官方文档列出的完整设备清单(同名实现可在源码 defaults.ts 中逐一核对):
| Key | 描述 | 尺寸(w×h, px) |
|---|---|---|
iphone5 |
iPhone 5 | 320 × 568 |
iphone6 |
iPhone 6 | 375 × 667 |
iphone6p |
iPhone 6 Plus | 414 × 736 |
iphone8p |
iPhone 8 Plus | 414 × 736 |
iphonex |
iPhone X | 375 × 812 |
iphonexr |
iPhone XR | 414 × 896 |
iphonexsmax |
iPhone XS Max | 414 × 896 |
iphonese2 |
iPhone SE(第 2 代) | 375 × 667 |
iphone12mini |
iPhone 12 mini | 375 × 812 |
iphone12 |
iPhone 12 | 390 × 844 |
iphone12promax |
iPhone 12 Pro Max | 428 × 926 |
iphoneSE3 |
iPhone SE(第 3 代) | 375 × 667 |
iphone13 |
iPhone 13 | 390 × 844 |
iphone13pro |
iPhone 13 Pro | 390 × 844 |
iphone13promax |
iPhone 13 Pro Max | 428 × 926 |
iphone14 |
iPhone 14 | 390 × 844 |
iphone14pro |
iPhone 14 Pro | 393 × 852 |
iphone14promax |
iPhone 14 Pro Max | 430 × 932 |
galaxys5 |
Galaxy S5 | 360 × 640 |
galaxys9 |
Galaxy S9 | 360 × 740 |
nexus5x |
Nexus 5X | 412 × 668 |
nexus6p |
Nexus 6P | 412 × 732 |
pixel |
Pixel | 540 × 960 |
pixelxl |
Pixel XL | 720 × 1280 |
ipad |
iPad | 768 × 1024 |
ipad10p |
iPad Pro 10.5-in | 834 × 1112 |
ipad11p |
iPad Pro 11-in | 834 × 1194 |
ipad12p |
iPad Pro 12.9-in | 1024 × 1366 |
在源码中,INITIAL_VIEWPORTS 被定义为 as const satisfies ViewportMap(见 defaults.ts),类型 ViewportMap 来自 types.ts。这意味着当你用 options: INITIAL_VIEWPORTS 时,TypeScript 能在编译期对 key、name、styles、type 做强类型校验;keyof typeof INITIAL_VIEWPORTS 也会自动展开为全部 28 个设备 key。若需要这些 key 的类型,可以引用从同一模块导出的 InitialViewportKeys。
自建设备与 options 的数据结构
options 参数的类型定义(官方 API 文档)为:
{
[key: string]: {
name: string;
styles: { height: string, width: string };
type: 'desktop' | 'mobile' | 'tablet' | 'other';
};
}
三条关键约束:
name为工具栏下拉中展示的显示名;styles.height与styles.width必须携带单位,例如'320px',不能只写数字;type决定工具栏中对设备的归类标签,取值范围为'desktop' | 'mobile' | 'tablet' | 'other'。
当内置清单不满足需求时,可以新增自定义设备。官方示例是把两款 Kindle 追加进默认的精简视口集合(完整代码见 addon-viewport-add-viewport-in-preview.md),核心写法是先在 preview 里展开 MINIMAL_VIEWPORTS 再合并你自己的条目,例如:
import { MINIMAL_VIEWPORTS } from 'storybook/viewport';
const customViewports = {
kindleFire2: {
name: 'Kindle Fire 2',
styles: { width: '600px', height: '963px' },
},
kindleFireHD: {
name: 'Kindle Fire HD',
styles: { width: '533px', height: '801px' },
},
};
export default {
parameters: {
viewport: {
options: { ...MINIMAL_VIEWPORTS, ...customViewports },
},
},
};
这样既保留了默认的 4 个响应式断点,又扩展了 Kindle 设备。由于 MINIMAL_VIEWPORTS 与 INITIAL_VIEWPORTS 都满足 ViewportMap,使用展开运算符合并自定义对象在类型上也是安全的。
按组件或 Story 层级覆盖 options
parameters 天然支持项目级、组件(meta)级和 Story 级三级作用域,因此 "全项目用哪些设备" 与 "某个组件用哪些设备" 可以分开配置。若你只想让某个组件的全部 Story 使用特定视口集,请在组件的 meta 中配置 parameters.viewport.options,而非在 preview 中全局声明;对应的完整写法见 addon-viewport-configuration-in-meta.md。更细粒度地,还能用 globals.viewport 单独为某条 Story 锁定初始视口,示例参见 addon-viewport-define-globals.md。
同理,parameters.viewport.disable(boolean)可用于关闭该模块行为,典型场景是项目级关闭、组件级或 Story 级用 false 重新开启。
从源码看参数的默认行为
查看 Viewport 模块入口 preview.ts 会发现其核心逻辑非常轻:
export const initialGlobals: Record<string, GlobalState> = {
[PARAM_KEY]: { value: undefined, isRotated: false },
};
export default () =>
definePreviewAddon<ViewportTypes>({
initialGlobals,
});
这里揭示了本片段背后的默认值来源:模块把全局状态注册为 viewport: { value: undefined, isRotated: false },即默认不锁定任何设备且不旋转。当你在 preview 中写入 initialGlobals.viewport = { value: 'ipad', isRotated: false } 时,实际是覆盖了这份注册时的初始值——iframe 尺寸会按 options 中 ipad 的 styles(width: '768px'、height: '1024px')生效。此外,PARAM_KEY 常量、ViewportGlobals/ViewportTypes 类型与 responsiveViewport 等实现都统一由 index.ts 从 constants、types、defaults、responsiveViewport 四个模块再导出,这也正是你能从 storybook/viewport 单点引入 INITIAL_VIEWPORTS、MINIMAL_VIEWPORTS 的原因。
API 速查
模块导出(Exports)
import { INITIAL_VIEWPORTS, MINIMAL_VIEWPORTS } from 'storybook/viewport';
INITIAL_VIEWPORTS(object):完整的预设设备清单(上表所列),不会在项目中被默认使用,需要手动配到options。MINIMAL_VIEWPORTS(object):mobile1/mobile2/tablet/desktop四档精简清单,Storybook 默认使用这一套。
参数(Parameters,均位于 viewport 命名空间下)
disable(boolean):关闭该模块行为,可在更具体层级用false重新启用。options:指定可用视口集合,styles的宽高必须带单位。
全局(Globals,位于 viewport 命名空间下)
value(string):设置后该视口将被锁定、无法用工具栏切换,取值须匹配options中某个视口的 key。isRotated(boolean):为true时将当前视口旋转 90°(例如从竖屏变横屏)。
键盘快捷键(可在快捷键设置页修改)
- 下一个视口:alt + v
- 上一个视口:alt + shift + v
- 重置视口:alt + control + v
延伸阅读
- 关联文档目录中的其他视口片段:addon-viewport-add-viewport-in-preview.md、addon-viewport-configuration-in-meta.md、addon-viewport-define-globals.md
- 完整官方文档:Viewport 特性说明
- 参数机制:Parameters 官方文档
- 源码实现:viewport 模块目录,核心文件包括 defaults.ts(预设设备清单与默认值)、preview.ts(global 注册)、types.ts(
ViewportMap类型)与 index.ts(统一导出入口)
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00