乐趣区

利用HTML5,无JS实现各种交互效果

本文利用的是 HTML5 details, summary
首先
一、了解 HTML5 details, summary 默认交互行为
<details> 标签在 Chrome,Firefox 等浏览器下默认是有展开收起行为的,例如下面 HTML:
<details>

<summary> 这是摘要 1 </summary>

<p> 这里具体描述,标签相对随意,例如这里使用的 &lt;p&gt; 标签。</p>

</details>

结果 UI 表现为:

具体描述为:

只显示了 <summary> 标签内容,而 <p> 默认隐藏了;

<summary> 标签前面出现了一个小三角;

小三角图形的隐喻是:我是可点击的,点击我可能会出现宝箱。
OK,我们不妨就点击一下,结果如下图:

具体描述为:

原本隐藏的 <p> 标签显示出来了;

<summary> 标签前面的小三角方向朝下了;

此时我们再一次点击,<p> 标签内容又会隐藏收起,箭头方向还原,如下图:

活脱脱一个天然的展开收起效果。
展开与收起是通过 open 属性控制的
通过在 <details> 标签上添加布尔类型的 open 属性,可以让我们的详情信息默认就是展开状态,如下 HTML 示意:
<details open>
<summary> 这是摘要 2 </summary>
<content> 这里 &lt;details&gt; 标签设置了 HTML 布尔属性 open,因此,默认是展开状态。</content>
</details>

结果如下截图:

如果我们使用 JS 脚本手动移除这个 open 属性,即使没有点击行为的发生,我们内容也会收起。
<summary> 如果缺省
<summary> 标签如果缺省,则 <details> 元素会在内部自动创建一个 <summary> 内容,默认的文案是“详细信息”。如下 HTML 代码:
<details open>
<p> 如果 &lt;summary&gt; 缺省,则会自动补上,文案是“详细信息”。</p>
</details>

结果如下截图所示:

二、details 浏览器内置 UI 可以自定义
<details> 标签默认的小三角样式有些简陋,在实际应用的时候,往往不是我们希望的样子,不要担心,我们是可以对其进行自定义的。在 Chrome 等浏览器下使用::-webkit-details-marker,在 Firefox 浏览器下使用::-moz-list-bullet 可以对小三角进行 UI 控制,例如改变颜色,改变大小,使用自定义的图形代替,或者直接隐藏等,我们来看几个简单的案例。
案例 1:小三角右侧显示同时颜色变淡
HTML 代码如下:
<details class=”details-1″ open>
<summary> 这是示例 1 </summary>
<content> 本案例展示对小三角 UI 重定义:包括显示在右侧,颜色减淡等。</content>
</details>

CSS 如下:
.details-1 summary {
width: -moz-fit-content;
width: fit-content;
direction: rtl;
}
.details-1 ::-webkit-details-marker {
direction: ltr;
color: gray;
margin-left: .5ch;
}
.details-1 ::-moz-list-bullet {
direction: ltr;
color: gray;
margin-left: .5ch;
}

结果如下图所示:

当我们点击摘要标题升起的时候,表现为下图(截自 Firefox):

而实际上实际开发的时候,对小三角 UI 更便捷的定制方法是:隐藏浏览器原生的小三角,然后借助::before 或::after 伪元素重新生成我们想要的 UI 效果,下面这个案例就将展示相关的处理。
案例 2:隐藏浏览器原生的小三角并使用自定义三角替换
HTML 结构还是类似的:
<details class=”details-2″ open>
<summary> 这是示例 2 </summary>
<content> 本案例隐藏原生小三角,使用自定义小三角。</content>
</details>

CSS 主要分为 2 部分,一部分是隐藏浏览器原生的小三角,另外一部分是使用伪元素生成自定义的三角效果。
首先看一下隐藏 <details> 标签默认的小三角的 CSS:
/* 隐藏默认三角 */
.details-2 ::-webkit-details-marker {
display: none;
}
.details-2 ::-moz-list-bullet {
font-size: 0;
}

可以看到 Chrome 浏览器和 Firefox 浏览器的小三角隐藏采用的是不同的策略。在 Chrome 浏览器下,我们可以直接设置 display:none 进行隐藏,但是这一招在 Firefox 浏览器下确实没有效果的,即使设置 display:none!important 也是如此,根据我的测试,只有 font-size:0 能够比较完美的隐藏。类似 position:absolute;visibility:hidden 这种常见的隐藏也是不行的,因为 position:absolute 无法生效。
然后是自定义小三角显示的 CSS,这里采用的是::after 伪元素模拟的:
/* 自定义的三角 */
.details-2 summary::after {
content: ”;
position: absolute;
width: 1em; height: 1em;
margin: .2em 0 0 .5ch;
background: url(./arrow-on.svg) no-repeat;
background-size: 100% 100%;
transition: transform .2s;
}
.details-2:not([open]) summary::after {
margin-top: .25em;
transform: rotate(90deg);
}

最终效果如下图所示:

收起时候:

*
最后有一点需要注意一下,就是如果 <details> 标签内并没有 <summary> 元素,则我们的对三角的自定义代码都是无效的,可以使用一个空的 <summary> 元素占位,类似这样:
<details>
<summary></summary>
<content> 内容。</content>
</details>
三、Chrome 浏览器下点击时候 outline 轮廓等体验处理
UI 可以定制了,但是还有个不容忽视的体验问题,那就是在 Chrome 浏览器下点击时候会出现 outline 轮廓,如下图所示:

在实际项目开发的时候,产品和设计一定会让你把这个效果去掉的。以及,当我们 <summary> 元素点击较快的时候,文本会被选中,也不是我们想看到的。
阻止文本选中,我们可以:
summary {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}

对于 outline 轮廓,比较直接的做法是:
summary {
outline: 0;
}
但是这样处理对无障碍访问而是非常不友好的,那有没有什么办法兼顾视觉体验和无障碍访问体验呢?
我的做法是这样子的:
利用 <a> 标签的 outline 交互体验
浏览器对 <a> 标签元素的 outline 轮廓进行了专门的体验优化处理,鼠标点击的时候不显示轮廓,键盘访问时候显示轮廓。于是我们可采用李代桃僵策略,让 <summary> 元素的 outline 交给 <a> 元素,方法就是在 <summary> 中再内嵌一个 <a>,同时通过 tabindex 属性 remove 掉 <summary> 原本的可访问性。HTML 代码示意如下:

<details open>
<summary tabindex=”-1″><a href=”javascript:”> 这是示例 </a></summary>
<content> 点击无外框,键盘 focus 有。</content>
</details>

CSS 如下:
summary {
user-select: none;
outline: 0;
}
summary a {
color: inherit;
}

此时,在 Chrome 浏览器下,我们点击摘要信息,没有任何 outline 轮廓出现;但是当我们使用 Tab 键索引时候,可以看到下图所示的轮廓效果:

轮廓区域比原生的 <summary> 要小,但这无伤大雅,而且实际项目开发的时候,我们会去掉小箭头,此时只要设置 <a> 标签 display:block,则轮廓就可以和 <summary> 保持一致了。
接下来,我们按下 Space 空格键,就会发现 <details> 元素内的内容信息不断的展开与收起:

然后上面实现并不完美,相比原生的 <summary> 元素,Enter 回车键展开收起效果丢失了。这是因为 HTML 元素中如果多个 focusable 同时带 click 浏览器行为元素嵌套的时候,点击里面的元素,外部元素的浏览器行为是不会触发的。类似的有 <label> 内嵌 <a> 标签。
对于 <a> 标签,其浏览器行为只能通过回车键触发,空格键是无效的;但是对于 <summary>,回车键和空格键都能触发展开收起行为,这就是为什么上面代码空格键有效,回车键无效的原因。
如果想要同时支持回车键展开与收起,可以对 HTML 如下处理:

<details open>
<summary tabindex=”-1″><a href=”javascript:” onClick=”this.parentNode.click();”> 这是示例 </a></summary>
<content> 点击无外框,键盘 focus 有。</content>
</details>

需要注意的是上面处理在 <summary> 自己额外绑定 click 事件时候可能会有 double 触发的问题,此时,阻止 <a> 元素的冒泡即可。
JS 捕获键盘行为手动设置 outline
这个方法不需要对 HTML 进行任何的改动,是通过 CSS 和 JS 配合对全局的 <summary> 元素进行 outline 优化。
CSS 如下:
summary {
user-select: none;
outline: 0;
}
summary[focus] {
outline: 1px dotted;
outline: 5px auto -webkit-focus-ring-color;
}

JS 如下:
window.addEventListener(‘keydown’, function () {
window.isKeyEvent = true;
setTimeout(function () {
window.isKeyEvent = false;
}, 100);
});

document.addEventListener(‘focusin’, function (event) {
var target = event.target;
if (target && target.tagName.toLowerCase() == ‘summary’ && window.isKeyEvent == true) {
target.setAttribute(‘focus’, ”);
}
});
document.addEventListener(‘focusout’, function (event) {
var eleFocusAll = document.querySelectorAll(‘summary[focus]’);
[].slice.call(eleFocusAll).forEach(function (summary) {
summary.removeAttribute(‘focus’);
});
});

只要把上面的 CSS 和 JS 复制到页面中,视觉体验和交互体验完美支持的 <summary> 元素 outline 效果就有了。
表现为,点击 <summary> 没有任何 outline,键盘 focus 时候出现,且和浏览器原生 outline 效果一模一样,Space 键和 Enter 键展开与收起访问完全保留。
例如下图就是键盘 Tab 键 focus 后回车后的效果:

每每看到如此极致的用户体验处理,心情都大好。
原理:关键是全局监听 keydown 事件,如果有发生,则认为此 100 毫秒内的页面 focus 行为均是键盘产生,从而有效区分是点击触发的 focus 行为还是键盘触发的 focus 行为,如果是键盘触发,给 <summary> 元素手动增加 outline 效果。
四、基于 details 元素行为的各种交互效果案例
了解了 <details> 元素的点击交互行为;解决了 UI 定制难题;解决了 outline 的体验问题,下面我们就可以付诸实践,不借助任何 JS 来实现各种我们平常见到的交互效果。
案例 1:“更多”展开与收起效果
实现最终效果如下 gif:

因为“更多”元素是在底部,因此效果实现的要点的所有的内容信息都放在 <summary> 元素内部,然后通过 <details> 元素的 open 属性控制 UI 的变化。
HTML 和 CSS 代码如下,其中,最核心部分已经红色高亮:
<details>
<summary>
<p> 据台媒报道,大 … 青睐。</p>
<div class=”more”>
<p> 其他几首歌曲 …</p>
</div>
<a> 更多 </a>
</summary>
</details>
::-webkit-details-marker {
display: none;
}
::-moz-list-bullet {
font-size: 0;
float: left;
}
.more {
display: none;
}
[open] .more {
display: block;
}
[open] summary a {
font-size: 0;
}
[open] summary a::before {
content: ‘ 收起 ’;
font-size: 14px;
}

把“更多”对应的信息放在.more 元素内,然后通过 [open] 属性选择器控制器显示,效果即达成。
案例 2:无 JS 实现点击显示悬浮菜单,自定义下拉框等效果
效果如下 gif:

没有任何 JS 参与。HTML 结构如下:

<details>
<summary> 我的消息 </summary>
<div class=”box”>
<a href> 我的回答 <sup>12</sup></a>
<a href> 我的私信 </a>
<a href> 未评价订单 <sup>2</sup></a>
<a href> 我的关注 </a>
</div>
</details>

然后 CSS 让.box 元素绝对定位即可,显示和隐藏 <details> 元素内置行为就搞定了。
案例 3:accordion 多项折叠效果
此效果常见于条目比较多的垂直导航栏,新闻条目等。
例如下面实现的效果:

这个更加简单了,就是一堆 <details> 元素并排放置就可以了,如下 HTML:
<details open>
<summary><dt> 订单中心 </dt></summary>
<dd><a href> 我的订单 </a></dd>
<dd><a href> 我的活动 </a></dd>
<dd><a href> 评价晒单 </a></dd>
<dd><a href> 购物助手 </a></dd>
</details>
<details open>
<summary><dt> 关注中心 </dt></summary>
<dd><a href> 关注的商品 </a></dd>

</details>
<details open>

</details>

计算 CSS 没有任何设置,效果也天然达成。
案例 3 中的展开项显示的时候是非常生硬的突然显示,实际上我们可以借助一些选择器技巧以及 CSS3 transition 属性让菜单展开收起的时候是有动画效果的,效果如下 gif 截图:

此效果实现原理核心是 [open] 属性选择器,和加号 + 相邻兄弟选择器。
首先看下 HTML,展开列表结构发生了变化,不是作为 <details> 的子元素,而是作为其相邻兄弟元素存在,HTML 示意:
<details open><summary> 订单中心 </summary></details>
<dl>
<dd><a href> 我的订单 </a></dd>
<dd><a href> 我的活动 </a></dd>
<dd><a href> 评价晒单 </a></dd>
<dd><a href> 购物助手 </a></dd>
</dl>

上面 <dl> 定义列表就是展开收起的内容,其作为兄弟元素和 <details> 元素平起平坐,于是,我们就可以利用点击 <summary> 元素 <details> 元素的 open 属性会变化的特性实现我们想要的动画效果,CSS 如下:

details + dl {
max-height: 0;
transition: max-height .25s;
overflow: hidden;
}
[open] + dl {
max-height: 100px;
}

借助相邻兄弟选择器以及 max-height 任意元素 slideUp/slideDown 技术就可以效果达成。
案例 5:多级嵌套的树形菜单交互效果
这里的树形菜单效果实现也很简单,多个 <details> 元素相互嵌套就可以,效果 Gif 如下:

HTML 结构大致如下:
<details>
<summary> 我的视频 </summary>
<details>
<summary> 爆肝工程师的异世界狂想曲 </summary>
<div>tv1-720p.mp4</div>
<div>tv2-720p.mp4</div>

<div>tv10-720p.mp4</div>
</details>
<details>
<summary> 七大罪 </summary>
<div> 七大罪 B 站 00 合集.mp4</div>
</details>
<div> 珍藏动漫网盘地址.txt</div>
<div> 我们的小美好.mp4</div>
</details>

CSS 的主要工作就是绘制菜单前面的加号和减号图形,例如我们可以借助 background 线性渐变,相关 CSS 如下:

details {
padding-left: 20px;
}
summary::before {
content: ”;
display: inline-block;
width: 12px; height: 12px;
border: 1px solid #999;
background: linear-gradient(to right, #999, #999) no-repeat center, linear-gradient(to top, #999, #999) no-repeat center;
background-size: 2px 10px, 10px 2px;
vertical-align: -2px;
margin-right: 6px;
margin-left: -20px;
}
[open] > summary::before {
background: linear-gradient(to right, #999, #999) no-repeat center;
background-size: 10px 2px;
}

效果即达成!
五、如果只想要 details/summary 的语义不要行为
如果只想要 <details> 元素,<summary> 元素的语义,但是并不需要点击展开收起的行为,该怎么处理呢?
例如,某评论,或者某帖子有标题和正文,非常符合详情 - 概要 - 内容的语义,但是希望是纯展示的,点击时候不收起,可以这么处理:
1.<summary> 标签设置 tabindex=”-1″ 让键盘无法访问;2. 设置 CSS:

summary {
outline: 0;
pointer-events: none;
}

这样就不能点,也不会有 outline 轮廓。
六、兼容性以及 Polyfill
兼容性如下图:

除了 IE 和 Edge 浏览器,大好河山一片绿,至少移动端可以用得比较开心。
如果想要在桌面 web 网页使用 <details> 元素的棒棒哒特性,我们可以对其进行 Polyfill
对键盘访问,事件 toggle 都做了兼容。
如果开发策略是对不支持的 IE 进行特异处理,则下面的 JS 判断是否支持 <details> 元素的脚本可能对你有用:
var isSupportDetails = ‘open’ in document.createElement(‘details’);
最后,无 JS 实现的好处有:
省了代码,加载快了;实现更简单了,开发快了;JS 还没加载交互也能进行,体验好了;键盘无障碍和 aria 阅读设备无障碍天然支持,体验档次高了。
这里推荐一下我的前端学习交流群:784783012,里面都是学习前端的,如果你想制作酷炫的网页,想学习知识。自己整理了一份 2018 最全面前端学习资料,从最基础的 HTML+CSS+JS 到移动端 HTML5 到各种框架的学习资料都有整理,送给每一位前端小伙伴,有想学习 web 前端的,或是转行,或是大学生,还有工作中想提升自己能力的,正在学习的小伙伴欢迎加入学习。

退出移动版