如何在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
相关产品推荐
相关产品推荐

