yycvip 发表于 昨天 22:17

优雅降级的<details>折叠面板

在原生HTML折叠面板的基础上,通过CSS和JavaScript增加了动画效果,并动态计算每个部分的高度。如果用户禁用了JavaScript,界面也能保持正常功能。

效果演示

HTML
<div class="wrapper">
<section class="details-group">
    <details class="details" open>
      <summary class="details__summary">
      &lt;details&gt; and &lt;summary&gt;
      </summary>
      <div class="details__content">
      <p>The &lt;details&gt; and &lt;summary&gt; 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>&lt;details&gt;</code> element will stay visible until the height transition is finished.</p>
      <p>The cool part is that since we&rsquo;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&rsquo;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&rsquo;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]
查看完整版本: 优雅降级的<details>折叠面板