我正在为某个组件库构建重新样式以实施公司设计系统。我希望能够 渲染组件的样式,就好像它们悬停/按下/聚焦一样 以便能够显示和测试当组件处于这些状态之一时应用的样式。
AFAICS 无法实际模拟这些元素状态。类似的问题通常与模拟相应的事件有关,这与我无关,因为它无法实际模拟
:hover
/ :active
/ :focus
styling。其他人建议在样式中使用 className
而不是伪类,这在无法[完全]控制相关样式时不适用。
我使用 Emotion 进行样式设置,因此我不直接创建样式。它们自动生成为文档头中的许多
<style>
节点。
我能够通过操作生成的
<style>
节点、复制 CSS 选择器的相关部分来实现所需的结果,这样对于使用 :hover
/ :active
/ :focus
的每个选择器,我添加了它的副本改为应用类选择器。然后,当我需要显示特定的状态样式时,我应用了相同的类。
export enum StateClass {
Hover = '__demo-hover',
Focus = '__demo-focus',
Active = '__demo-active'
}
const simulateStatesInRule = (rule: CSSRule) => {
if (rule instanceof CSSMediaRule) {
Array.from(rule.cssRules).forEach(simulateStatesInRule);
return;
}
if (!(rule instanceof CSSStyleRule) || !rule.selectorText) return;
rule.selectorText = rule.selectorText.split(',').flatMap((part) => {
if (/\.(?:hover|focus|active)/.test(part)) return []; // don't keep adding selectors forever
if (/:(?:hover|focus|active)/.test(part))
return [
part,
part
.replace(/:hover/g, `.${StateClass.Hover}`)
.replace(/:focus/g, `.${StateClass.Focus}`)
.replace(/:active/g, `.${StateClass.Active}`)
];
return [part];
}).join(',');
};
const simulateStatesInStyle = (style: HTMLStyleElement) =>
Array.from(style.sheet?.cssRules ?? []).forEach(simulateStatesInRule);
export const simulateStatesUsingClasses () => {
const observer = new MutationObserver((mutations) =>
mutations.forEach(({ addedNodes }) =>
Array.from(addedNodes)
.filter((node) => node instanceof HTMLStyleElement)
.forEach(simulateStatesInStyle)
)
);
observer.observe(document.head, { childList: true });
document.head.querySelectorAll('style').forEach(simulateStatesInStyle);
return () => {
observer.disconnect();
};
}