You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Storybook中根据所选视口展示不同故事标记并合并配置?

合并Storybook桌面/移动端配置,实现视口切换加载对应资源

核心解决思路

不用维护两套独立配置,而是通过Storybook的全局变量结合视口插件的状态监听,在切换视口时动态加载对应平台的CSS和标记文件,实现同一配置下的跨视口预览。

具体实现步骤

1. 统一主配置文件

把原两套配置的main.js内容合并,确保所有组件故事能被统一扫描,保留必要的插件:

// .storybook/main.js
module.exports = {
  stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
  addons: [
    '@storybook/addon-viewport',
    // 其他你用到的addons都放这
  ],
};

2. 在preview.js中实现动态资源加载

用Storybook的useGlobals钩子监听视口变化,根据当前视口加载对应的CSS和标记:

// .storybook/preview.js
import { useEffect, useGlobals } from '@storybook/preview-api';
import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport';

// 先定义好视口和对应资源的映射关系
const viewportResourceMap = {
  // 对应桌面端视口(用你实际的视口key)
  desktop: {
    css: '../src/styles/desktop.css',
    markup: '../src/markup/desktop-base.html'
  },
  // 对应移动端视口
  mobile1: {
    css: '../src/styles/mobile.css',
    markup: '../src/markup/mobile-base.html'
  }
};

export const decorators = [
  (Story) => {
    const [{ viewport }] = useGlobals();
    // 默认用桌面端视口
    const currentViewport = viewport || 'desktop';
    const { css, markup } = viewportResourceMap[currentViewport];

    useEffect(() => {
      // 先移除之前加载的平台专属CSS
      document.querySelectorAll('.platform-css').forEach(el => el.remove());
      // 加载当前视口对应的CSS
      const cssLink = document.createElement('link');
      cssLink.rel = 'stylesheet';
      cssLink.href = css;
      cssLink.className = 'platform-css';
      document.head.appendChild(cssLink);

      // 替换平台专属标记(假设页面里有个容器放这些标记)
      const markupContainer = document.getElementById('platform-markup');
      if (markupContainer) {
        fetch(markup)
          .then(res => res.text())
          .then(html => markupContainer.innerHTML = html);
      }

      // 组件卸载时清理CSS
      return () => cssLink.remove();
    }, [currentViewport]);

    return (
      <>
        {/* 专门放平台标记的容器 */}
        <div id="platform-markup"></div>
        <Story />
      </>
    );
  }
];

// 配置视口选项,用默认的或者自定义你的视口
export const parameters = {
  viewport: {
    viewports: INITIAL_VIEWPORTS,
    defaultViewport: 'desktop'
  }
};

3. 可选:预加载资源解决延迟问题

如果切换视口时资源加载慢,可以提前预加载所有平台的资源,切换时直接用缓存好的内容:

// 在preview.js开头加这段预加载逻辑
const preloaded = {};
// 提前拉取所有资源并存起来
Object.values(viewportResourceMap).forEach(async (res) => {
  if (!preloaded[res.css]) {
    const cssRes = await fetch(res.css);
    preloaded[res.css] = await cssRes.text();
  }
  if (!preloaded[res.markup]) {
    const markupRes = await fetch(res.markup);
    preloaded[res.markup] = await markupRes.text();
  }
});

// 然后把useEffect里的加载逻辑改成用预加载内容
useEffect(() => {
  document.querySelectorAll('.platform-css').forEach(el => el.remove());
  // 用style标签插入预加载的CSS文本
  const cssStyle = document.createElement('style');
  cssStyle.className = 'platform-css';
  cssStyle.textContent = preloaded[css];
  document.head.appendChild(cssStyle);

  const markupContainer = document.getElementById('platform-markup');
  if (markupContainer) {
    markupContainer.innerHTML = preloaded[markup];
  }

  return () => cssStyle.remove();
}, [currentViewport]);

4. 组件故事的适配(按需)

如果个别组件需要针对不同视口调整,直接在故事里通过全局变量判断即可:

// Button.stories.jsx
export const Primary = {
  render: (args, { globals }) => {
    const isMobile = globals.viewport === 'mobile1';
    return <Button {...args} variant={isMobile ? 'mobile-primary' : 'desktop-primary'} />;
  }
};

内容的提问来源于stack exchange,提问作者Sergiu Statache

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 12:04:51