4

突发奇想,同步单复选框checked态岂不点击通杀?

 3 years ago
source link: https://www.zhangxinxu.com/wordpress/2020/11/label-sync-radio-checkbox-checked/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=9683
本文欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可以联系授权。

思考的狐狸,突发奇想的张鑫旭

标题完整内容应该是“就在星期天的下午,我在书房突发奇想,要是可以让元素(如<label>元素)无论在页面什么位置都能响应单选框或复选框元素的状态变化,那么几乎页面所有的点击交互岂不是都可以通杀了,那就牛逼大啦!”

到底牛不牛逼呢,大家可以跟过来一起看看评一评。

一、起步、背景和目标效果

几乎所有常见的点击交互的本质就是单选或者复选。

例如选项卡是典型的单选,展开收起或者下拉就是典型的复选(只有1个选项的复选),树结构是多选等。

因此radio/checkbox配合:checked伪类可以纯CSS实现大量的点击交互效果,这个技术我早在8年前就介绍过了:“CSS radio/checkbox单复选框元素显隐技术”。

但是这个技术有个限制,由于CSS选择器中的+或者~选择符只能选择后面的兄弟元素,因此,交互事件的主体需要和单复选框元素是兄弟关系,这就导致DOM结构有了明显的限制。

有一种方法可以一定程度上绕开这种限制,就是使用<label>元素,通过for属性与单选复选元素进行关联,这样,单选框或者复选框就可以在页面的任意的位置。

但是这种方法虽然功能OK,但是DOM的结构却很奇怪,语义不符,结构混乱,例如实现选项卡效果的时候,选项卡按钮和选项卡面板元素需要公用一个祖先元素,如下截图红框框所示,这太奇怪了,完全不能用在实际项目中。

奇怪的单复选框实现效果

突发奇想的背景

我的上一篇文章就是和单选框技术相关的,实现下图这个单选变色效果:

列表点击效果

因为HTML代码有限制,如下所示:

<ul>
    <li><input type="radio" name="item" checked>选项1</li> 
    <li><input type="radio" name="item">选项2</li> 
    <li><input type="radio" name="item">选项3</li> 
    <li><input type="radio" name="item">选项4</li> 
    <li><input type="radio" name="item">选项5</li> 
</ul>

所以用了“高级的”mix-blend-mode属性实现的。

当时,我就琢磨着,要是父元素<li>可以实时响应里面单选框元素的checked状态,什么屁事都没了,关键就是不支持,毕竟CSS中没有父选择器。

心有不甘,一直盘在心里,然后周日晚上9点多的时候,看着鱼缸里的自由自在的小鱼,就突然来了灵感:嘿!以我目前的技术储备,想要让任意元素和单复选框的checked状态关联似乎是可行的呀,脑子里盘了盘,有了个雏形,然后就开始开搞。

实现的目标效果如下:

  1. 引入一段JS代码;
  2. 任意for/id属性关联元素状态实时联动;
<ul>
    <li for="$1"><input type="radio" id="$1" name="item" checked>选项1</li> 
    <li for="$2"><input type="radio" id="$2" name="item">选项2</li> 
    <li for="$3"><input type="radio" id="$3" name="item">选项3</li> 
    <li for="$4"><input type="radio" id="$4" name="item">选项4</li> 
    <li for="$5"><input type="radio" id="$5" name="item">选项5</li> 
</ul>

无论通过何种方式改变了单选框元素的选中态,for属性值等于这个单选框元素id值的任意元素都会有对应的状态列表,例如toggle一个类名.active

这样,就可以使用li.active选择器轻松改变文字的颜色了。

二、代码出场、基本效果

按照心中的雏形,盘啊盘,测啊测,代码撸出来了。

该JS地址为:smart-for.js

直接在页面中引入下面的JS,然后万能点击效果就有了。

<script src="smart-for.js"></script>

眼见为实,上面那个列表选择效果,您可以狠狠地点击这里:父元素响应单选框checked状态demo

打开控制台,就可以看到,单选框在点击的时候,父元素的类名.active会自动添加删除。

类名自动同步示意

状态关联的机制很简单,就是需要状态同步的元素的for属性值就是单选框或复选框元素的id属性值就可以了,类似于<label>元素和单复选框元素的关联。

有了smart-for.js,几乎所有的点击交互效果就不需要额外的JS代码去实现了,通杀。

popup或侧边栏效果

例如移动端常见的底部popup浮层效果,或者aside侧边栏效果,就不需要额外的JS来实现了。

眼见为实,您可以狠狠地点击这里:无业务JS实现的Popup和Aside浮层交互demo

可以看到如下GIF所示的效果(点击播放-183K):

浮层出现隐藏效果示意

相关代码如下,主要是HTML部分,侧边栏效果示意,无关紧要代码删除了:

<label class="ui-button"><input type="checkbox" id="zxxAside" hidden>点击我显示侧边栏</label>
<aside class="aside" for="zxxAside">
    <label for="zxxAside" class="aside-overlay"></label>
    <div class="aside-content">点击黑色蒙层可以收起</div>
</aside>

显隐控制的CSS代码主要就是:

.aside {
    visibility: hidden;
}
.aside.active {
    visibility: visible;
}

浏览器有个特性,点击<label>元素,里面的单选框或者复选框元素就会选中,此时就会触发任意for="zxxAside"的元素添加类名.active,于是浮层显示。

黑色蒙层使用的是指向复选框的<label>元素,因此点击时候就会取消复选框的选中,此时<aside>元素的.active类名自动删除,浮层隐藏。

如果希望点击其他元素或者按钮让隐藏,但是又不想使用<label>元素。

则需要使用JS改变复选框元素的选中态,例如:

someEle.addEventListener('click', function () {
    zxxAside.checked = false;
});

元素状态会自动同步,无需开发者去触发。

展开更多效果

这个demo页面是老的:checked伪类实现的效果,不过需要固定的层级,有了本文的JS代码,可以无视层级,更自由了,实现自然不在话下。

例子就不举了,因为这种交互更推荐使用“<details>、<summary>元素实现”,纯CSS,语义更好,不支持的浏览器简单几行JS Polyfill下就可以了。

更多展开收起gif效果

下拉列表效果

这个效果绝对是CSS :focus-within伪类实现最佳,纯CSS,现代浏览器兼容性还是可以的,我已经在实际项目中使用了,Safari浏览器注意使用<a>元素+tabindex="0"

<details>/<summary>元素也可以实现下拉列表效果,本文的checked状态同步也可以实现,但是,点击空白区域隐藏这个处理,在复杂页面场景下可能会有层级混乱的问题。

算了,想了想,还是整个demo吧,万一可以帮到需要的人呢,毕竟兼容性要比:focus-within伪类好很多,IE11+都支持。

您可以狠狠地点击这里:checked状态同步与下拉列表交互demo

下面的GIF录屏就是demo页面实现的交互效果:

checked状态实现的下拉列表效果GIF

优点除了兼容性好之外,还有就是下拉列表浮层元素的位置是可以任意的。其他几个CSS方法都有DOM位置和层级的限制。

大家有兴趣可以研究下demo页面中的源代码。

选项卡切换效果

有了本文的smart-for.js,选项卡效果再也不需要复杂而又奇怪的DOM层级结构了。

您可以狠狠地点击这里:checked状态同步选项卡效果demo (内有完整的源代码)

此时的HTML结构就是正常的了,符合我们的理解和认知:

正常选项卡结构示意

实现效果如下GIF所示:

选项卡切换效果GIF录屏

更多细节参见demo页面,这里不展开。


总之,只要引入一小段JS代码,Min + Gzip后 < 1K,借助隐藏的单选框或者复选框元素,各类点击交互效果就无需额外的JS代码了,层级无限制,位置无限制,元素几乎无限制,非常灵活。

反正这年头语义化就是个梦,大家应该会用得很开心的。

三、实现的原理、难点在哪

要想让元素和单复选框元素的:checked状态关联还是有一些挑战的。

因为单复选框:checked状态变化的场景太多了:

  1. 点击行为选中(无任何属性变化)
  2. JS设置:radio.checked = true // 或false
  3. HTML属性设置:radio.setAttribute(‘checked’, ”)
  4. 单选框组中其他元素check导致自身uncheck
  5. 页面新增一个单复选框
  6. 删除一个单复选框
  7. 页面新增一个for关联元素

必须观察上面所有的场景,而且要立即识别,无需用户主动触发。

对于checked状态变化,我一开始的思路是:

:checked {
    padding: 0.1px;
}

然后使用ResizeObserver观察元素的尺寸是否变化,这样就知道状态可能发生了变化。但是这样做,需要元素非display:none隐藏,而且,最重要的是ResizeObverse iOS 13才开始支持,兼容性不佳,于是放弃了这个想法,改为采用下面的策略:

  1. dom.checked引起的状态变化通过重置单复选框元素的checked属性实现的,代码如下:
    var propsChecked = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'checked');
    var propsCheckedNew = {};
    
    for (key in propsChecked) {
        propsCheckedNew[key] = propsChecked[key];
    }
    
    propsCheckedNew.set = function (value) {
        propsChecked.set.call(this, value);
        // 同步对应label元素的状态
        funCheckedSync(this);
    };

    补充于翌日

    感谢XboxYan的反馈,观察checked状态变化还可以使用animationend回调,兼容性会好很多,IE10+,不足就是display:none隐藏无法感知。

  2. setAttribute/removeAttribute导致的属性变化、DOM元素的增删,全部使用MutationObserver方法实现,这个在之前“聊聊JS DOM变化的监听检测与应用”一文中有介绍,IE11+支持。

    具体代码这里就不展示了。

  3. 点击行为导致的状态变化采用委托的方式进行监听,代码如下:
    document.addEventListener('click', function (event) {
        var eleTarget = event && event.target;
        if (eleTarget && eleTarget.matches('[type="radio"], [type="checkbox"]')) {
            funCheckedSync(eleTarget);
        }
    });

更多细节大家仔细参阅源码。


算了算了,直接浏览器打开的代码一坨糊,很不利于阅读。

我找了个地方开源了下:https://gitee.com/zhangxinxu/smart-for

gitee速度杠杠的,比github快多了,欢迎关注我 (https://gitee.com/zhangxinxu)。

关注我的gitee

以后非国际化的项目我都会放在gitee上,国际形势不定,放在github上的代码说不定哪天都不是自己的。

JS源码参阅直接戳这里:https://gitee.com/zhangxinxu/smart-for/blob/master/smart-for.js

四、测试、降维打击、结语

为了验证品质,我还专门写了个测试页面

点击测试按钮,一排绿的感觉真好,然后有改动,测一下,也放心一点。

理论上,IE9+浏览器也是可以对DOM和属性进行观察的,但是那几个window事件性能太差,早就要淘汰了,我就没支持。

因此,本JS兼容到IE11+,也就是MutationObserver方法支持的版本。

例如下图是IE11浏览器下的测试结果:

IE11下测试结果示意

本文的这个JS覆盖点击交互的方法,感觉就是一个完全的不同层面的思路,有种降维打击的感觉。

无需针对每一种交互效果去写具体的代码,只要抽象出一个规则,然后利用浏览器原生的特性,配合已知的一些浏览器API能力,就可以实现全覆盖的交互增强支持。

根据这么多年的时间,单选复选的浏览器原生特性是非常稳健的,因此,代码出坑的可能性并不大。

以后要是有个运营活动之类的项目、或者内部项目,我会尝试用用看,感受下到底香不香。

OK,以上就是本文的全部内容,周末突然的一点奇思妙想,居然扯这么多内容。

研究、学习、成长不止!

感谢您的阅读,如果你觉得本文内容还不错,欢迎转发。

2764.svg

(本篇完)1f44d.svg 是不是学到了很多?可以分享到微信
1f44a.svg 有话要说?点击这里


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK