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

如何在Storybook中测试CSS的@media媒体查询?

在Storybook中为不同@media查询创建独立故事

下面给你几种直接可用的实现方式,无需复杂插件就能搞定:

方案1:自定义MediaWrapper组件(最直观)

直接写一个React组件,通过修改DOM样式表模拟@media print这类特殊媒体查询的渲染环境,核心逻辑是把目标媒体规则替换为屏幕媒体规则,让浏览器将对应样式应用到当前页面:

import { useEffect } from 'react';

export const MediaWrapper = ({ media, children }) => {
  useEffect(() => {
    const styleSheets = Array.from(document.styleSheets);
    const originalRules = [];

    styleSheets.forEach((sheet) => {
      try {
        const rules = Array.from(sheet.cssRules);
        rules.forEach((rule, index) => {
          // 定位目标@media规则
          if (rule.type === CSSRule.MEDIA_RULE && rule.media.mediaText.includes(media)) {
            // 保存原始规则,用于组件卸载时恢复
            originalRules.push({ sheet, index, text: rule.cssText });
            // 替换媒体类型为screen,让当前屏幕应用该样式
            const newRuleText = rule.cssText.replace(`@media ${media}`, '@media screen');
            sheet.deleteRule(index);
            sheet.insertRule(newRuleText, index);
          }
        });
      } catch (e) {
        // 跨域样式表无法访问,直接忽略即可
      }
    });

    // 组件卸载时还原原始样式规则
    return () => {
      originalRules.forEach(({ sheet, index, text }) => {
        sheet.deleteRule(index);
        sheet.insertRule(text, index);
      });
    };
  }, [media]);

  return <>{children}</>;
};

之后就可以像你期望的那样使用:

export const PrintFoo: Story = {
  render: (args) => (
    <MediaWrapper media="print">
      <Foo {...args} />
    </MediaWrapper>
  ),
};

方案2:用Storybook装饰器+渲染钩子

如果不想单独封装组件,也可以把逻辑做成Storybook装饰器,支持全局或局部故事使用:

import { useEffect } from 'react';

export const withMediaQuery = (media) => (Story) => {
  useEffect(() => {
    const styleSheets = Array.from(document.styleSheets);
    const originalRules = [];

    styleSheets.forEach((sheet) => {
      try {
        const rules = Array.from(sheet.cssRules);
        rules.forEach((rule, index) => {
          if (rule.type === CSSRule.MEDIA_RULE && rule.media.mediaText.includes(media)) {
            originalRules.push({ sheet, index, text: rule.cssText });
            const newRuleText = rule.cssText.replace(`@media ${media}`, '@media screen');
            sheet.deleteRule(index);
            sheet.insertRule(newRuleText, index);
          }
        });
      } catch (e) {}
    });

    return () => {
      originalRules.forEach(({ sheet, index, text }) => {
        sheet.deleteRule(index);
        sheet.insertRule(text, index);
      });
    };
  }, [media]);

  return <Story />;
};

在故事中引入装饰器即可:

export const PrintFoo: Story = {
  decorators: [withMediaQuery('print')],
  render: (args) => <Foo {...args} />,
};

注意事项

  • 跨域加载的外部样式表(比如CDN资源)无法修改规则,这种情况建议将目标媒体样式内联到项目代码,或打包为本地样式文件。
  • 如果需要测试响应式尺寸类的媒体查询(比如@media (max-width: 768px)),可以调整替换逻辑,比如直接给根元素添加模拟类名,配合样式覆盖实现效果。

内容的提问来源于stack exchange,提问作者Aaron Digulla

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 13:24:50