优雅降级的<details>折叠面板
在原生HTML折叠面板的基础上,通过CSS和JavaScript增加了动画效果,并动态计算每个部分的高度。如果用户禁用了JavaScript,界面也能保持正常功能。效果演示
HTML
<div class="wrapper">
<section class="details-group">
<details class="details" open>
<summary class="details__summary">
<details> and <summary>
</summary>
<div class="details__content">
<p>The <details> and <summary> elements are used to allow common accordion-style functionality without relying on JavaScript.</p>
</div>
</details>
<details class="details">
<summary class="details__summary">Making it better</summary>
<div class="details__content">
<p>With a little help from JavaScript, we can supplement the default behavior to make an accordion that is super smooth and nicely-styled, but still degrades gracefully.</p>
<p>This accordion uses JS to calculate heights so we can use CSS transitions without any max-height hacks. It also sets a timeout before removing the <code>open</code> attribute, so the content of the <code><details></code> element will stay visible until the height transition is finished.</p>
<p>The cool part is that since we’re using these modern HTML5 elements, the accordion should work just fine even with JavaScript disabled. <strong><em>Gasp!</em> A simple web component in 2018 that doesn’t completely fail without JS?! Sound the alarm!</strong>
</div>
</details>
<details class="details">
<summary class="details__summary">Settings</summary>
<div class="details__content">
<ul>
<li>
<strong><code>speed</code></strong> <em>(default: <code>300</code>)</em> Speed of the transition in milliseconds. Setting this in the plugin will override the value specified in the CSS.
</li>
<li>
<strong><code>one_visible</code></strong> <em>(default: <code>false</code>)</em> Determines whether details can be toggled independently, or if only one can be visible at a time.
</li>
</ul>
</div>
</details>
<details class="details">
<summary class="details__summary">Shameless Plug</summary>
<div class="details__content">
<p>If you liked this pen, check out <a target="_blank" href="https://keithpickering.github.io">my portfolio site</a>. I’m accepting new projects all the time, so drop me a line.</p>
</div>
</details>
</section>
</div>@import url('https://fonts.googleapis.com/css?family=Oswald:300|Roboto:300,500');
$spacing: 24px;
$plus-size: 16px;
$plus-thickness: 2px;
$speed: 300ms;
$easing: ease-in-out;
$gray-dark: #546E7A;
$gray: #CFD8DC;
$gray-light: #ECEFF1;
$primary: #00ACC1;
html, body {
min-height: 100%;
}
html {
overflow-y: scroll;
font-size: 18px;
}
body {
font-size: 1rem;
font-weight: 300;
font-family: Roboto, sans-serif;
line-height: 1.4;
color: $gray-dark;
background-color: $gray-light;
}
p, ul, li {
margin: 0;
padding: 0;
margin-bottom: $spacing;
&:last-child {
margin-bottom: 0;
}
}
a { color: $primary; }
code { background: $gray-light; }
.wrapper {
max-width: 640px;
margin-left: auto;
margin-right: auto;
padding: $spacing*2;
}
.details-group {
border: 1px solid $gray;
border-radius: 5px;
background-color: white;
}
.details {
overflow: hidden;
border-bottom: 1px solid $gray;
transition: height $speed $easing;
&:last-child {
border-bottom: 0;
}
&__summary,
&__content {
padding: $spacing;
}
&__summary {
position: relative;
list-style: none; // Hide the marker (proper method)
padding-left: $spacing*2;
outline: 0;
cursor: pointer;
font-size: 1.4rem;
font-family: Oswald;
text-transform: uppercase;
transition: color $speed $easing;
> & {
color: $primary;
}
// Hide the marker in Webkit
&::-webkit-details-marker {
display: none;
}
// Our replacement marker
&:before,
&:after {
content: "";
position: absolute;
}
&:before {
left: $spacing/2 + $plus-size/2;
top: 50%;
height: $plus-thickness;
margin-top: -$plus-thickness/2;
width: $plus-size;
background: $primary;
}
&:after {
left: $spacing/2 + $plus-size;
top: 50%;
height: $plus-size;
margin-top: -$plus-size/2;
width: $plus-thickness;
margin-left: -$plus-thickness/2;
background: $primary;
transition: all $speed $easing;
}
&:after {
opacity: 0;
transform: translateY(25%);
}
}
&__content {
padding-top: 0;
padding-left: $spacing*2;
}
}class Details {
constructor(el, settings) {
this.group = el;
this.details= this.group.getElementsByClassName("details");
this.toggles= this.group.getElementsByClassName("details__summary");
this.contents = this.group.getElementsByClassName("details__content");
// Set default settings if necessary
this.settings = Object.assign({
speed: 300,
one_visible: false
}, settings);
// Setup inital positions
for (let i = 0; i < this.details.length; i++) {
const detail= this.details;
const toggle= this.toggles;
const content = this.contents;
// Set transition-duration to match JS setting
detail.style.transitionDuration = this.settings.speed + "ms";
// Set initial height to transition from
if (!detail.hasAttribute("open")) {
detail.style.height = toggle.clientHeight + "px";
} else {
detail.style.height = toggle.clientHeight + content.clientHeight + "px";
}
}
// Setup click handler
this.group.addEventListener("click", (e) => {
if (e.target.classList.contains("details__summary")) {
e.preventDefault();
let num = 0;
for (let i = 0; i < this.toggles.length; i++) {
if (this.toggles === e.target) {
num = i;
break;
}
}
if (!e.target.parentNode.hasAttribute("open")) {
this.open(num);
} else {
this.close(num);
}
}
});
}
open(i) {
const detail = this.details;
const toggle = this.toggles;
const content = this.contents;
// If applicable, hide all the other details first
if (this.settings.one_visible) {
for (let a = 0; a < this.toggles.length; a++) {
if (i !== a) this.close(a);
}
}
// Update class
detail.classList.remove("is-closing");
// Get height of toggle
const toggle_height = toggle.clientHeight;
// Momentarily show the contents just to get the height
detail.setAttribute("open", true);
const content_height = content.clientHeight;
detail.removeAttribute("open");
// Set the correct height and let CSS transition it
detail.style.height = toggle_height + content_height + "px";
// Finally set the open attr
detail.setAttribute("open", true);
}
close(i) {
const detail = this.details;
const toggle = this.toggles;
// Update class
detail.classList.add("is-closing");
// Get height of toggle
const toggle_height = toggle.clientHeight;
// Set the height so only the toggle is visible
detail.style.height = toggle_height + "px";
setTimeout(() => {
// Check if still closing
if (detail.classList.contains("is-closing"))
detail.removeAttribute("open");
detail.classList.remove("is-closing");
}, this.settings.speed);
}
}
(() => {
const els = document.getElementsByClassName("details-group");
for (let i = 0; i < els.length; i++) {
const details = new Details(els, {
speed: 500,
one_visible: true
});
}
})();
页:
[1]