这篇文章发布于 2020年11月26日,星期四,01:51,归类于 JS实例。 阅读 18093 次, 今日 4 次 12 条评论
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状态关联似乎是可行的呀,脑子里盘了盘,有了个雏形,然后就开始开搞。
目标效果
实现的目标效果如下:
- 引入一段JS代码;
- 任意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下就可以了。
//zxx: 如果你看到这段文字,说明你现在访问是体验糟糕的垃圾盗版网站,你可以访问原文获得很好的体验:https://www.zhangxinxu.com/wordpress/?p=9683(作者张鑫旭)
下拉列表效果
这个效果绝对是CSS :focus-within伪类实现最佳,纯CSS,现代浏览器兼容性还是可以的,我已经在实际项目中使用了,Safari浏览器注意使用<a>
元素+tabindex="0"
。
<details>
/<summary>
元素也可以实现下拉列表效果,本文的checked状态同步也可以实现,但是,点击空白区域隐藏这个处理,在复杂页面场景下可能会有层级混乱的问题。
算了,想了想,还是整个demo吧,万一可以帮到需要的人呢,毕竟兼容性要比:focus-within
伪类好很多,IE11+都支持。
您可以狠狠地点击这里:checked状态同步与下拉列表交互demo
下面的GIF录屏就是demo页面实现的交互效果:
优点除了兼容性好之外,还有就是下拉列表浮层元素的位置是可以任意的。其他几个CSS方法都有DOM位置和层级的限制。
大家有兴趣可以研究下demo页面中的源代码。
选项卡切换效果
有了本文的smart-for.js,选项卡效果再也不需要复杂而又奇怪的DOM层级结构了。
您可以狠狠地点击这里:checked状态同步选项卡效果demo (内有完整的源代码)
此时的HTML结构就是正常的了,符合我们的理解和认知:
实现效果如下GIF所示:
更多细节参见demo页面,这里不展开。
总之,只要引入一小段JS代码,Min + Gzip后 < 1K,借助隐藏的单选框或者复选框元素,各类点击交互效果就无需额外的JS代码了,层级无限制,位置无限制,元素几乎无限制,非常灵活。
反正这年头语义化就是个梦,大家应该会用得很开心的。
三、实现的原理、难点在哪
要想让元素和单复选框元素的:checked状态关联还是有一些挑战的。
因为单复选框:checked状态变化的场景太多了:
- 点击行为选中(无任何属性变化)
- JS设置:radio.checked = true // 或false
- HTML属性设置:radio.setAttribute(‘checked’, ”)
- 单选框组中其他元素check导致自身uncheck
- 页面新增一个单复选框
- 删除一个单复选框
- 页面新增一个for关联元素
必须观察上面所有的场景,而且要立即识别,无需用户主动触发。
对于checked状态变化,我一开始的思路是:
:checked { padding: 0.1px; }
然后使用ResizeObserver观察元素的尺寸是否变化,这样就知道状态可能发生了变化。但是这样做,需要元素非display:none
隐藏,而且,最重要的是ResizeObverse iOS 13才开始支持,兼容性不佳,于是放弃了这个想法,改为采用下面的策略:
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
隐藏无法感知。setAttribute
/removeAttribute
导致的属性变化、DOM元素的增删,全部使用MutationObserver方法实现,这个在之前“聊聊JS DOM变化的监听检测与应用”一文中有介绍,IE11+支持。具体代码这里就不展示了。
- 点击行为导致的状态变化采用委托的方式进行监听,代码如下:
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上,国际形势不定,放在github上的代码说不定哪天都不是自己的。
JS源码参阅直接戳这里:https://gitee.com/zhangxinxu/smart-for/blob/master/smart-for.js
吼吼~
四、测试、降维打击、结语
为了验证品质,我还专门写了个测试页面。
点击测试按钮,一排绿的感觉真好,然后有改动,测一下,也放心一点。
理论上,IE9+浏览器也是可以对DOM和属性进行观察的,但是那几个window事件性能太差,早就要淘汰了,我就没支持。
因此,本JS兼容到IE11+,也就是MutationObserver方法支持的版本。
例如下图是IE11浏览器下的测试结果:
降维打击
本文的这个JS覆盖点击交互的方法,感觉就是一个完全的不同层面的思路,有种降维打击的感觉。
无需针对每一种交互效果去写具体的代码,只要抽象出一个规则,然后利用浏览器原生的特性,配合已知的一些浏览器API能力,就可以实现全覆盖的交互增强支持。
根据这么多年的时间,单选复选的浏览器原生特性是非常稳健的,因此,代码出坑的可能性并不大。
以后要是有个运营活动之类的项目、或者内部项目,我会尝试用用看,感受下到底香不香。
OK,以上就是本文的全部内容,周末突然的一点奇思妙想,居然扯这么多内容。
研究、学习、成长不止!
感谢您的阅读,如果你觉得本文内容还不错,欢迎转发。
本文为原创文章,欢迎分享,勿全文转载,如果实在喜欢,可收藏,永不过期,且会及时更新知识点及修正错误,阅读体验也更好。
本文地址:https://www.zhangxinxu.com/wordpress/?p=9683
(本篇完)
- 借助HTML5 details,summary无JS实现各种交互效果 (0.542)
- 检测DOM尺寸变化JS API ResizeObserver简介 (0.247)
- 尝试使用JS IntersectionObserver让标题和导航联动 (0.247)
- 狠狠地研究了下 PerformanceObserver API (0.238)
- CSS :focus-visible伪类让我感动哭了 (0.217)
- 小tip:CSS计数器+伪类实现数值动态计算与呈现 (0.211)
- 折腾:2颗星星+纯CSS实现星星评分交互效果 (0.130)
- IE7浏览器下CSS属性选择器二三事 (0.130)
- 聊聊JS DOM变化的监听检测与应用 (0.130)
- HTML中无标签文本的CSS变色技巧 (0.130)
- 翻译-你必须知道的28个HTML5特征、窍门和技术 (RANDOM - 0.009)
旭哥, 用着怎么样, 爽不爽?
和之前的 attr-polyfill 差不多呢
代码在严格模式会报错,56行key未声明
Get it,晚上我处理下。
666,先mark一记
确实是完全不一样的思路。
将 UI 逻辑和 业务逻辑完美的独立出来,
拓展性刚刚的。
请问 这里面的测试Demo会一保存吗,说不定我三四年后想起来回来看一下。
如果会的话我就不写在自己笔记里了。
会的,而且会更新
for (key in propsCheckedNew) { // propsChecked?
propsCheckedNew[key] = propsChecked[key];
}
感谢反馈。
真心羡慕大佬可以研究的这么细致
沙发,太棒了,又多了一个思路