你知道组件库的checkbox如何实现的吗?
最近参加了一场笔试,题目要求使用 React 和 TypeScript 封装一个多选框组件。题目本身不难,但如果处理得太简单,就容易"出事" ------ 毕竟既然考你封装组件,那用现成组件库自然是不允许的,只能从零开始,用"原生方式"搞定。
不过,原生的 checkbox 又太单调 ,交互和样式都很局限。于是我去看了一下 Element Plus 的设计,想找找灵感。
没想到一看就被惊到了:
"哦,原来 checkbox 还可以这么写!"
从结构上看,Element Plus 并没有直接使用原生 checkbox 来展示勾选框,而是:
-
将原生
<input type="checkbox">
隐藏 -
自定义了一个具有视觉样式的
span.checkbox__inner
-
再通过
label
的点击联动原生input
,实现状态同步
看似复杂,其实本质是:
原生 input 被"屏蔽"了视觉,但保留了交互响应能力。
那么问题来了:
如何隐藏一个元素,但仍然保留交互?
我查阅了一些资料,并做了几个测试,发现了下面几种常见的"隐藏方式"及其副作用对比如下:
display: none
无文档流 无法交互opacity: 0
有文档流 可以交互height: 0 width: 0
无文档流 无法交互visibility:hidden
无文档流 无法交互 但占据空间clip-path:polygon(..)
裁剪为不可见 有文档流 但不可见 无法绑定transsform.scale(0)
将元素缩放到不可见 有文档流 可以交互
所以最终选择哪种方式?
其实 opacity: 0 和 transform: scale(0) 都可以满足"视觉隐藏但保留交互"的需求。
但我最后还是选择了 opacity ------ 更直观,语义也更清晰,不容易引起奇怪的副作用或命中问题。
额外一点建议:关于 label 和 input 的绑定
在 Element Plus 的源码中,原生 是放在 标签内部的,这样可以自动形成绑定,不需要再写 for / id。确实没问题,也很简洁。
不过我个人的建议是:
出于习惯,如果结构允许,最好还是给 input 写上 id,label 使用 for 显式绑定。
- 测试人员可以更方便地选中目标元素(特别是自动化测试)
- 语义更清晰,可维护性更强
- 即使结构发生变化,也不影响交互逻辑
那么还有一个问题,✅没了,如何实现选中效果呢?
选中时让原生input出现么?你有没有发现,勾勾的角好像是直角,我猜你肯定被问过如何用css画三角形吧?那么画个勾,问题也不大吧。
想的都是问题,做的才是答案,开始吧
先打好框架
html
<label class="checkbox" for="checkbox">
<span class="checkbox__input">
<input class="checkbox__original" type="checkbox" id="checkbox">
<span class="checkbox__inner"></span>
</span>
<span class="checkbox__label">点击选中</span>
</label>
完成常规的布局,第一件事儿,就是给原生input
打上一套组合拳
css
.checkbox__original {
opacity: 0;
width: 0;
height: 0;
z-index: -1;
position: absolute;
}
似乎忘了勾选框,嗯,能不能勾勾用伪元素来做呢?span
元素画正方形,然后伪元素绝对定位,微调一下,就可以了。
给span元素画边框

如果你希望一个 span 元素能设置宽高,一般做法是给它加上 display: inline-block
或 block
,或者让它处于一个特殊布局(如 flex
/grid
)下。
*
给当前元素加上伪元素,并画出选中标记✅
为什么伪元素默认是 inline ?
因为它本质上是"插入"到内容流中的一段内容,默认就是像文字一样在行内显示。但 inline
是不支持设置宽高的,所以我们一般都需要手动设置,如图,我们要做的,就是将它旋转一下,并取消左和上的边框,这里旋转中心,就是伪元素的中心。

移动选中标记
使用left
和top
,所以它的position
不能是默认的static
,如果你想改为absolute
,那就是相对于离它层级最近的一个不是static
的容器,所以父容器也需要修改position
,然后微调就可以了。

最后,再给它加上一些效果。
css
.checkbox__inner{
/* ...*/
transition: background-color 0.3s ease,border-color 0.3s ease; /* 添加过渡动画 */
}
.checkbox__inner:hover{
border-color: #409eff;
}
.checkbox__original:checked + .checkbox__inner{
border-color: #409eff;
background-color: #409eff;
}
.checkbox__original:checked + .checkbox__inner::after {
transform: rotate(45deg) scale(1); /* 选中时显示 */
}

写在最后
我发现,亲手去写一些小样式、小交互,真的能极大地提升对前端的理解和掌控感。 知识的广度 ,决定了你解决问题时能想到多少种方案; 知识的深度 ,则决定了你的代码是否优雅而高效。 所以,如果你看到这里,不妨动动手,试试实现几种不同的点击动效?