🎨 前端多主题最佳实践:用 Less Map + generate-css 打造自动化主题系统
本文介绍了如何利用 Less 的 Map 和函数,结合 .generate-css 与 .generate-css-vars,实现全自动的主题生成体系。
在日常前端开发中,大家对 :hover
、:active
、:focus
等伪类应该都很熟悉。但 :focus-within
可能就没那么常用了,它在表单、交互容器的高亮效果上非常实用。本文将从基础对比出发,逐步深入事件监听,并分享一个 React Hook 版的 useFocusWithin
实现。
:focus
与 :focus-within
的区别:focus
input:focus {
border-color: #1a8cff;
outline: none;
}
当输入框获得焦点时,边框变蓝。
:focus-within
.form-group:focus-within {
border: 2px solid #1a8cff;
}
即使 form-group
自身不可聚焦,只要里面的 input
获得焦点,容器也会应用样式。
✅ 总结:
:focus
→ 针对当前元素。:focus-within
→ 针对容器,包含子元素聚焦的情况。CSS 可以很方便地使用 :focus
和 :focus-within
,但有时我们需要 用 JS 捕获对应的状态,比如做动画、逻辑处理。
focus
/ blur
→ 元素自身获得/失去焦点。focusin
/ focusout
→ 与 focus
类似,但会冒泡。因为
:focus-within
的核心就是“容器内是否有元素获得焦点”,所以用focusin
/focusout
来模拟最合适。
onFocusWithin
function onFocusWithin(element, onEnter, onLeave) {
if (!element) return;
element.addEventListener("focusin", () => onEnter?.(element));
element.addEventListener("focusout", () => {
// 延迟一下,避免切换子元素时误判
setTimeout(() => {
if (!element.contains(document.activeElement)) {
onLeave?.(element);
}
}, 0);
});
}
使用:
onFocusWithin(
document.querySelector(".form-group"),
() => console.log("进入 focus-within"),
() => console.log("离开 focus-within")
);
useFocusWithin
在 React 中,如果每次都手动绑定/解绑事件很麻烦,所以我们可以抽象成一个 Hook。
import { useEffect, useRef, useState } from "react";
export function useFocusWithin({ onEnter, onLeave } = {}) {
const ref = useRef(null);
const [hasFocusWithin, setHasFocusWithin] = useState(false);
useEffect(() => {
const el = ref.current;
if (!el) return;
const handleFocusIn = () => {
setHasFocusWithin(true);
onEnter?.(el);
};
const handleFocusOut = () => {
setTimeout(() => {
if (!el.contains(document.activeElement)) {
setHasFocusWithin(false);
onLeave?.(el);
}
}, 0);
};
el.addEventListener("focusin", handleFocusIn);
el.addEventListener("focusout", handleFocusOut);
return () => {
el.removeEventListener("focusin", handleFocusIn);
el.removeEventListener("focusout", handleFocusOut);
};
}, [onEnter, onLeave]);
return [ref, hasFocusWithin];
}
function MyComponent() {
const [ref, hasFocusWithin] = useFocusWithin({
onEnter: () => console.log("进入 focus-within"),
onLeave: () => console.log("离开 focus-within"),
});
return (
<div
ref={ref}
style={{
padding: "10px",
border: hasFocusWithin ? "2px solid #1a8cff" : "2px solid #ccc",
}}
>
<input placeholder="输入内容..." />
<button>按钮</button>
</div>
);
}
当 input
或 button
获得焦点时,整个容器边框变蓝;失去焦点时恢复灰色。
:focus
针对单个元素。:focus-within
可感知容器内子元素的焦点状态。focusin
/ focusout
来模拟。useFocusWithin
Hook,方便在项目中复用。这样一来,我们就能同时利用 CSS 的伪类优势 和 JS 的逻辑控制,打造更灵活的交互体验。