首页
/ Storybook 在 preview 中配置视口参数:INITIAL_VIEWPORTS 与 initialGlobals 实践指南

Storybook 在 preview 中配置视口参数:INITIAL_VIEWPORTS 与 initialGlobals 实践指南

2026-09-06 19:16:40作者:齐添朝

导读

在 Storybook 中渲染 Story 的 iframe 尺寸由 viewport 特性控制,这是开发响应式 UI 的核心手段。默认情况下 Storybook 只内置一套精简视口(MINIMAL_VIEWPORTS),而当你想把设备预设替换为涵盖 iPhone、iPad、Galaxy、Pixel、Nexus 等数十款主流设备的完整设备清单,并把某款设备设为组件初始渲染尺寸时,就需要在 .storybook/preview.* 中同时使用 parameters.viewport.optionsinitialGlobals。本文以仓库中的 addon-viewport-options-in-preview.md 代码片段为骨架,结合 viewport.mdx 官方文档与 code/core/src/viewport 目录下的源码实现,完整演示配置写法、两种预设视口清单、底层类型结构与参数解析原理,帮助你一步到位完成全局视口定制。

为什么要覆盖默认视口配置

viewport 特性允许你调整渲染 Story 的 iframe 尺寸。开箱即用时,Storybook 使用一套"最小可用"的预设视口集合(即 MINIMAL_VIEWPORTS),它只包含覆盖常见响应式断点的 4 种尺寸:mobile1mobile2tabletdesktop。例如在源码 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 文件中,你可以通过两条声明完成定制:

  1. parameters.viewport.options —— 决定"可用视口有哪些";
  2. 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-vitenextjsvue3-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、namestylestype 做强类型校验;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.heightstyles.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_VIEWPORTSINITIAL_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.disableboolean)可用于关闭该模块行为,典型场景是项目级关闭、组件级或 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 尺寸会按 optionsipadstyleswidth: '768px'height: '1024px')生效。此外,PARAM_KEY 常量、ViewportGlobals/ViewportTypes 类型与 responsiveViewport 等实现都统一由 index.tsconstantstypesdefaultsresponsiveViewport 四个模块再导出,这也正是你能从 storybook/viewport 单点引入 INITIAL_VIEWPORTSMINIMAL_VIEWPORTS 的原因。

API 速查

模块导出(Exports)

import { INITIAL_VIEWPORTS, MINIMAL_VIEWPORTS } from 'storybook/viewport';
  • INITIAL_VIEWPORTSobject):完整的预设设备清单(上表所列),不会在项目中被默认使用,需要手动配到 options
  • MINIMAL_VIEWPORTSobject):mobile1/mobile2/tablet/desktop 四档精简清单,Storybook 默认使用这一套

参数(Parameters,均位于 viewport 命名空间下)

  • disableboolean):关闭该模块行为,可在更具体层级用 false 重新启用。
  • options:指定可用视口集合,styles 的宽高必须带单位。

全局(Globals,位于 viewport 命名空间下)

  • valuestring):设置后该视口将被锁定、无法用工具栏切换,取值须匹配 options 中某个视口的 key。
  • isRotatedboolean):为 true 时将当前视口旋转 90°(例如从竖屏变横屏)。

键盘快捷键(可在快捷键设置页修改)

  • 下一个视口:alt + v
  • 上一个视口:alt + shift + v
  • 重置视口:alt + control + v

延伸阅读

登录后查看全文
热门项目推荐
相关项目推荐