Elements 和Elements 非常适合实现手风琴 UI
创建手风琴 UI 时使用元素和元素
元素
HTML 结构很简单。可以看到,从标签名称就可以理解其结构。
如果您想创建手风琴所需的最小打开/关闭动作,则不需要 CSS;HTML 就足够了。
<details>
<summary>概要</summary>
详细内容
</details>
添加CSS 样式优化
接下来,我们将使用 CSS 更改外观。我们将使用以下图标创建一个手风琴 UI。
<details class="js-details">
<summary class="js-summary">
<span class="summary_inner">
标题标题 <span class="icon"></span>
</span>
</summary>
<div class="content js-content">
<div class="content_inner">
内容部分内容部分内容部分内容部分内容部分内容部分
</div>
</div>
</details>
details {
margin: 0 auto;
width: 70%;
summary {
/* display: list-item;清除默认三角图标样式 */
display: block;
.summary_inner {
cursor: pointer;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 16px 24px;
border: 1px solid #d2beff;
font-weight: bold;
color: #002255;
.icon {
display: block;
position: relative;
width: 24px;
margin-left: 6px;
flex-shrink: 0;
transform-origin: center 43%;
transition: transform 0.4s;
&::before,
&::after {
content: "";
position: absolute;
display: block;
width: 15px;
height: 3px;
background-color: #7050ff;
}
&::before {
left: 0;
transform: rotate(45deg);
}
&::after {
right: 0;
transform: rotate(-45deg);
}
}
}
}
summary::-webkit-details-marker {
/* Safari中修改相应的值 */
display: none;
}
/* details 内不直接指定padding */
.content {
overflow: hidden;
background-color: #f0f2ff;
&_inner {
padding: 24px 48px;
display: flex;
flex-direction: column;
gap: 16px;
}
}
}
/* 打开内容展示时 旋转三角哦图标 */
details[open] .icon {
transform: rotate(180deg);
}
使用JS添加动画 优化展示效果
通过单击或使用键盘来打开和关闭手风琴。这次,我们将使用标准浏览器功能Web Animations API来为手风琴的内容制作动画。
<details class="js-details">
<summary class="js-summary">
<span class="summary_inner">
标题标题标题标 <span class="icon"></span>
</span>
</summary>
<div class="content js-content">
<div class="content_inner">
内容部分内容部分内容部分内容部分内容部分内容部分
</div>
</div>
</details>
details {
margin: 10px auto;
width: 70%;
summary {
/* display: list-item;清除默认三角图标样式 */
display: block;
.summary_inner {
cursor: pointer;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 16px 24px;
border: 1px solid #d2beff;
font-weight: bold;
color: #002255;
.icon {
display: block;
position: relative;
width: 24px;
margin-left: 6px;
flex-shrink: 0;
transform-origin: center 43%;
transition: transform 0.4s;
&::before,
&::after {
content: "";
position: absolute;
display: block;
width: 15px;
height: 3px;
background-color: #7050ff;
}
&::before {
left: 0;
transform: rotate(45deg);
}
&::after {
right: 0;
transform: rotate(-45deg);
}
}
}
}
summary::-webkit-details-marker {
/* Safari中修改相应的值 */
display: none;
}
/* details 内不直接指定padding */
.content {
overflow: hidden;
background-color: #f0f2ff;
&_inner {
padding: 24px 48px;
display: flex;
flex-direction: column;
gap: 16px;
}
}
}
/* is-opened js控制类添加时展示 */
details.is-opened .icon {
transform: rotate(180deg);
}
document.addEventListener("DOMContentLoaded", () => {
setUpAccordion();
});
/**
* JS 原生 (Web Animations API) animate()
*/
const setUpAccordion = () => {
const details = document.querySelectorAll(".js-details");
const RUNNING_VALUE = "running"; // 动画运行添加属性值
const IS_OPENED_CLASS = "is-opened";
details.forEach((element) => {
const summary = element.querySelector(".js-summary");
const content = element.querySelector(".js-content");
summary.addEventListener("click", (event) => {
// 禁用默认行为
event.preventDefault();
// 防止连点, 动画执行时 点击无效
if (element.dataset.animStatus === RUNNING_VALUE) {
return;
}
// 判断当前 details 的 open 属性
if (element.open) {
// 关闭时操作
// 切换图标操作类
element.classList.toggle(IS_OPENED_CLASS);
// 运行动画
const closingAnim = content.animate(
closingAnimKeyframes(content),
animTiming
);
// 指定动画运行时 使用的值
element.dataset.animStatus = RUNNING_VALUE;
// 手风琴 关闭后
closingAnim.onfinish = () => {
// 删除自定义属性 open
element.removeAttribute("open");
// 删除动画运行时的值
element.dataset.animStatus = "";
};
} else {
//打开手风琴时的处理
//添加自定义 open属性
element.setAttribute("open", "true");
// 添加样式
element.classList.toggle(IS_OPENED_CLASS);
// 执行打开动画
const openingAnim = content.animate(
openingAnimKeyframes(content),
animTiming
);
// 赋值动画运行时 状态
element.dataset.animStatus = RUNNING_VALUE;
// 动画运行完成后 置空
openingAnim.onfinish = () => {
element.dataset.animStatus = "";
};
}
});
});
};
/**
* 动画 配置
*/
const animTiming = {
duration: 400,
easing: "ease-out"
};
/**
* 关闭时的 关键帧
*/
const closingAnimKeyframes = (content) => [
{
height: content.offsetHeight + 'px', // height: "auto" 设置auto时 不能很好的计算
opacity: 1,
}, {
height: 0,
opacity: 0,
}
];
/**
* 打开时 关键帧
*/
const openingAnimKeyframes = (content) => [
{
height: 0,
opacity: 0,
}, {
height: content.offsetHeight + 'px',
opacity: 1,
}
];
参考示例
首尾呼应,主题鲜明,收束有力。
?批判性评语?