CSS魔法堂:改变单选框颜色就这么吹毛求疵!
前言
是否曾经被业务提出"能改改这个单选框的颜色吧!让它和主题颜色搭配一下吧!",然后苦于原生不支持换颜色,最后被迫自己手撸一个凑合使用。若抛开input[type=radio]重新开发一个,发现要模拟选中、未选中、不可用等状态很繁琐,而涉及单选框组就更烦人了,其实我们可以通过label、::before、:checked和tabindex,然后外加少量JavaScript脚本就能很好地模拟出一个样式更丰富的“原生”单选框。下面我们一起来尝试吧!
单选框了解一下
由于我们的目标是改变单选框颜色,其他外观特征和行为与原来的单选框一致,那么我们就必须先了解单选框原来的外观特征和行为主要有哪些。
1.外观特征
1.1.常态样式
margin: 3px 3px 0px 5px;
border: none 0;
padding: 0;
box-sizing: border-box;
display: inline-block;
line-height: normal;
position: static;
注意:外观上我们必须要保证布局特性和原生的一致,否则采用自定义单选框替换后很大机会会影响整体的布局,最后导致被迫调整其他元素的布局特性来达到整体的协调,从而扩大了修改范围。
1.2.获得焦点的样式
outline-offset: 0px;
outline: -webkit-focu-ring-color auto 5px;
注意:这里的获取焦点的样式仅通过键盘Tab键才生效,若通过鼠标点击虽然单选框已获得焦点,但上述样式并不会生效。
1.3.设置为disabled的样式
color: rgb(84, 84, 84);
2.行为特征
单选框的行为特征,明显就是选中与否,及选中状态的改变事件,因此我们必须保持对外提供change事件。
另外值得注意的是,当通过键盘的Tab键让单选框获得焦点后,再按下Space键则会选中该单选框。
有了上述的了解,我们可以开始着手撸代码了!
少废话,撸代码

上图中左侧就是原生单选框,右侧为我们自定义的单选框。从上到下依次为未选中、选中、获得焦点和disabled状态的样式。
CSS部分
label.radio {
/* 保证布局特性保持一致 */
margin: 3px 3px 0px 5px;
display: inline-block;
box-sizing: border-box;
width: 12px;
height: 12px;
}
.radio__appearance{
display: block; /* 设置为block则不受vertical-align影响,从而不会意外影响到.radio的linebox高度 */
position: relative;
box-shadow: 0 0 0 1px tomato; /* box-shadow不像border那样会影响盒子的框高 */
border-radius: 50%;
height: 90%;
width: 90%;
text-align: center;
}
label.radio [type=radio] + .radio__appearance::before{
content: "";
display: block;
border-radius: 50%;
width: 85%;
height: 85%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
transition: background .3s;
}
label.radio [type=radio]:checked + .radio__appearance::before{
background: tomato;
}
label.radio [type=radio][disabled] + .radio__appearance{
opacity: .5;
}
label.radio:focus{
outline-offset: 0px;
outline: #999 auto 5px;
}
/* 通过鼠标单击获得焦点时,outline效果不生效 */
label.radio.clicked{
outline: none 0;
}
/* 自定义单选框的行为主要是基于原生单选框的,因此先将原生单选框隐藏 */
label.radio input {
display: none;
}
HTML部分
<!-- 未选中状态 -->
<label class="radio" tabindex="0">
<input type="radio" name="a">
<i class="radio__appearance"></i>
</label>
<br>
<!-- 选中状态 -->
<label class="radio" tabindex="0">
<input type="radio" name="a" checked>
<i class="radio__appearance"></i>
</label>
<br>
<!-- disabled状态 -->
<label class="radio">
<input type="radio" name="a" disabled>
<i class="radio__appearance"></i>
</label>
JavaScript部分
var radios = document.querySelectorAll(".radio")
radios.forEach(radio => {
// 模拟鼠标点击后:focus样式无效
radio.addEventListener("mousedown", e => {
var tar = e.currentTarget
tar.classList.add("clicked")
var fp = setInterval(function(){
if (document.activeElement != tar){
tar.classList.remove("clicked")
clearInterval(fp)
}
}, 400)
})
// 模拟通过键盘获得焦点后,按`Space`键执行选中操作
radio.addEventListener("keydown", e => {
if (e.keyCode === 32){
e.target.click()
}
})
})
这个实现有3个注意点:
- 通过
label传递鼠标点击事件到关联的input[type=radio],因此可以安心隐藏单选框又可以利用单选框自身特性。但由于label控件自身的限制,如默认不是可获得焦点元素,因此无法传递键盘按键事件到单选框,即使添加tabindex特性也需手写JS来实现; - 当tabindex大于等于0时表示该元素可以获得焦点,为0时表示根据元素所在位置安排获得焦点的顺序,而大于0则表示越小越先获得焦点;
- 由于单选框的
display为inline-block,因此单选框将影响line box高度。当自定义单选框内元素采用inline-block时,若vertical-align设置稍有不慎就会导致内部元素所在的line box被撑高,从而导致自定义单选框所在的line box高度变大。因此这里采用将内部元素的display均设置为block的做法,直接让vertical-align失效,提高可控性。
通过opacity:0实现(2018/10/5追加)
上面我们通过label关联display:none的input[type=radio]从而利用input[type=radio]简化自定义单选框的实现,但依然要手写JS实现按Space键选中的行为特征,有没有另一种方式可以更省事呢?我们只是想让用户看不到原生单选框,那么直接设置为opacity:0不就可以了吗?!
CSS部分
.radio {
/* 保证布局特性保持一致 */
margin: 3px 3px 0px 5px;
display: inline-block;
box-sizing: border-box;
width: 13px;
height: 13px;
}
/* 自定义单选框的行为主要是基于原生单选框的,因此先将原生单选框透明,且沾满整个父元素 */
.radio input {
opacity: 0;
position: absolute;
z-index: 1; /* 必须覆盖在.radio__appearance上才能响应鼠标事件 */
width: 100%;
height: 100%;
}
.radio__container-box{
position: relative;
width: 100%;
height: 100%;
}
.radio__appearance{
display: block; /* 设置为block则不受vertical-align影响,从而不会意外影响到.radio的linebox高度 */
position: relative;
box-shadow: 0 0 0 1px tomato; /* box-shadow不像border那样会影响盒子的框高 */
border-radius: 50%;
height: 90%;
width: 90%;
text-align: center;
}
.radio [type=radio] + .radio__appearance::before{
content: "";
display: block;
border-radius: 50%;
width: 85%;
height: 85%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
transition: background .3s;
}
.radio [type=radio]:checked + .radio__appearance::before{
background: tomato;
}
.radio [type=radio][disabled] + .radio__appearance{
opacity: .5;
}
.radio:focus-within .radio__appearance{
outline-offset: 0px;
outline: #999 auto 5px;
}
/* 通过鼠标单击获得焦点时,outline效果不生效 */
.radio.clicked .radio_appearance{
outline: none 0;
}
HTML部分
<!-- 未选中状态 -->
<span class="radio">
<span class="radio__container-box">
<input type="radio" name="a">
<i class="radio__appearance"></i>
</span>
</span>
<br>
<!-- 选中状态 -->
<span class="radio">
<span class="radio__container-box">
<input type="radio" name="a" checked>
<i class="radio__appearance"></i>
</span>
</span>
<br>
<!-- disabled状态 -->
<span class="radio">
<span class="radio__container-box">
<input type="radio" name="a" disabled>
<i class="radio__appearance"></i>
</span>
</span>
JavaScript部分
var radios = document.querySelectorAll(".radio")
radios.forEach(radio => {
// 模拟鼠标点击后:focus样式无效
radio.addEventListener("mousedown", e => {
var tar = e.currentTarget
tar.classList.add("clicked")
var fp = setInterval(function(){
if (!tar.contains(document.activeElement){
tar.classList.remove("clicked")
clearInterval(fp)
}
}, 400)
})
})
总结
对于复选框我们可以稍加修改就可以了,然后通过VUE、React等框架稍微封装一下提供更简约的API,使用起来就更方便了。
尊重原创,转载请注明来自:https://www.cnblogs.com/fsjohnhuang/p/9741345.html _肥仔John
CSS魔法堂:改变单选框颜色就这么吹毛求疵!的更多相关文章
- CSS魔法堂:那个被我们忽略的outline
前言 在CSS魔法堂:改变单选框颜色就这么吹毛求疵!中我们要模拟原生单选框通过Tab键获得焦点的效果,这里涉及到一个常常被忽略的属性--outline,由于之前对其印象确实有些模糊,于是本文打算对其 ...
- CSS魔法堂:Box-Shadow没那么简单啦:)
前言 说起box-shadow那第一个想法当然就是用来实现阴影,其实它还能用于实现其他好玩的效果的,本篇就打算说说box-shadow的那些事. 二话不说看效果 3D小球 <style typ ...
- CSS魔法堂:重拾Border之——解构Border
前言 当CSS3推出border-radius属性时我们是那么欣喜若狂啊,一想到终于不用再添加额外元素来模拟圆角了,但发现border-radius还分水平半径和垂直半径,然后又发现border-t ...
- CSS魔法堂:小结一下Box Model与Positioning Scheme
前言 对于Box Model和Positioning Scheme中3种定位模式的细节,已经通过以下几篇文章记录了我对其的理解和思考. <CSS魔法堂:重新认识Box Model.IFC.B ...
- CSS魔法堂:深入理解line-height和vertical-align
前言 一直听说line-height是指两行文本的基线间的距离,然后又说行高等于行距,最近还听说有个叫行间距的家伙,@张鑫旭还说line-height和vertical-align基情四射,贵圈真乱啊 ...
- CSS魔法堂:不得不说的Containing Block
前言 <CSS魔法堂:重新认识Box Model.IFC.BFC和Collapsing margins>中提到在没有floated兄弟盒子时,line box的左右边框会与所属的cont ...
- CSS魔法堂:display:none与visibility:hidden的恩怨情仇
前言 还记得面试时被问起"请说说display:none和visibility:hidden的区别"吗?是不是回答完display:none不占用原来的位置,而visibilit ...
- CSS魔法堂:重拾Border之——更广阔的遐想
前言 当CSS3推出border-radius属性时我们是那么欣喜若狂啊,一想到终于不用再添加额外元素来模拟圆角了,但发现border-radius还分水平半径和垂直半径,然后又发现border-t ...
- CSS魔法堂:重拾Border之——不仅仅是圆角
前言 当CSS3推出border-radius属性时我们是那么欣喜若狂啊,一想到终于不用再添加额外元素来模拟圆角了,但发现border-radius还分水平半径和垂直半径,然后又发现border-t ...
随机推荐
- 【AtCoder】ARC076
ARC076 C - Reconciled? 如果\(N = M\) 答案是\(2N!M!\) 如果\(|N - M| = 1\) 答案是\(N!M!\) 否则答案是0 #include <bi ...
- mini dc与简易计算器 20165235
mini dc 任务内容 本次mini dc任务就是通过补充代码来实现整型数据的后缀表达式计算 相关知识 通过利用堆栈这一先进后出的数据结构来实现后缀表达式的计算.通过Stack<Integer ...
- Python class NameError name "xxx" is not defined
Python class NameError name "xxx" is not defined 这是因为在 class 中调用了带双下划线 "__" 的函数对 ...
- js扩展运算符(spread)是三个点(...)
作用:将一个数组转为用逗号分隔的参数序列. //该运算符主要用于函数调用.function push(array, ...items) { array.push(...items); } functi ...
- Peter's smokes -poj 2509
题意:彼得有n支雪茄,每k个烟头可以换一支新雪茄,问彼得最多可以吸多少支雪茄 ? 当时自己做时,错在了直接在while循环开始前,便将雪茄的初始数量给加上了,然而应该是先处理后再加上最终剩余的雪茄数量 ...
- Java 之 Web前端(一)
1.http a.定义:超文本传输协议 b.作用:web服务器与web浏览器之间通信 c.步骤: ①客户端与web服务器建立连接(IP地址与端口号) ②客户端发送http请求(请求资源路径) ③服务器 ...
- 解决Ubuntu中Unable to acquire the dpkg frontend lock (/var/lib/dpkg/lock-frontend), is another proce...
解决Ubuntu中Unable to acquire the dpkg frontend lock (/var/lib/dpkg/lock-frontend), is another proce... ...
- Google Hack搜索技巧
想了解更多搜索技巧,点击下面网站了解http://exploit-db.com/google-dorks Google Hack的一些整理 这里是google关键字的用法,要设置它为中文,则是 htt ...
- SpringMVC(十四) RequestMapping ModelAndView
ModelAndView返回模型数据和视图.参考以下Demo代码,了解其实现方法.关注通过视图名称创建ModelAndView的构造方法,以及通过${requestScope.attribute}的方 ...
- fluxion-wifi破解/钓鱼
转载内容,侵删 https://bbs.ichunqiu.com/thread-24085-1-5.html 0x00前言: 有人说我比那些收费的平台更可恨,因为我写教程不收费 ...