爬行的蜗牛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
热搜: golang Linux PHP
查看: 7|回复: 0

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

[复制链接]

152

主题

48

回帖

1137

积分

管理员

积分
1137
发表于 昨天 22:17 | 显示全部楼层 |阅读模式
在原生HTML折叠面板的基础上,通过CSS和JavaScript增加了动画效果,并动态计算每个部分的高度。如果用户禁用了JavaScript,界面也能保持正常功能。

效果演示
640.gif
HTML
  1. <div class="wrapper">
  2.   <section class="details-group">
  3.     <details class="details" open>
  4.       <summary class="details__summary">
  5.         &lt;details&gt; and &lt;summary&gt;
  6.       </summary>
  7.       <div class="details__content">
  8.         <p>The &lt;details&gt; and &lt;summary&gt; elements are used to allow common accordion-style functionality without relying on JavaScript.</p>
  9.       </div>
  10.     </details>

  11.     <details class="details">
  12.       <summary class="details__summary">Making it better</summary>
  13.       <div class="details__content">
  14.         <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>
  15.         <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>
  16.         <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>
  17.       </div>
  18.     </details>

  19.     <details class="details">
  20.       <summary class="details__summary">Settings</summary>
  21.       <div class="details__content">
  22.         <ul>
  23.           <li>
  24.             <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.
  25.           </li>
  26.           <li>
  27.             <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.
  28.           </li>
  29.         </ul>
  30.       </div>
  31.     </details>

  32.     <details class="details">
  33.       <summary class="details__summary">Shameless Plug</summary>
  34.       <div class="details__content">
  35.         <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>
  36.       </div>
  37.     </details>
  38.   </section>
  39. </div>
复制代码
  1. @import url('https://fonts.googleapis.com/css?family=Oswald:300|Roboto:300,500');

  2. $spacing: 24px;
  3. $plus-size: 16px;
  4. $plus-thickness: 2px;
  5. $speed: 300ms;
  6. $easing: ease-in-out;
  7. $gray-dark: #546E7A;
  8. $gray: #CFD8DC;
  9. $gray-light: #ECEFF1;
  10. $primary: #00ACC1;

  11. html, body {
  12.   min-height: 100%;
  13. }

  14. html {
  15.   overflow-y: scroll;
  16.   font-size: 18px;
  17. }

  18. body {
  19.   font-size: 1rem;
  20.   font-weight: 300;
  21.   font-family: Roboto, sans-serif;
  22.   line-height: 1.4;
  23.   color: $gray-dark;
  24.   background-color: $gray-light;
  25. }

  26. p, ul, li {
  27.   margin: 0;
  28.   padding: 0;
  29.   margin-bottom: $spacing;
  30.   
  31.   &:last-child {
  32.     margin-bottom: 0;
  33.   }
  34. }

  35. a { color: $primary; }

  36. code { background: $gray-light; }

  37. .wrapper {
  38.   max-width: 640px;
  39.   margin-left: auto;
  40.   margin-right: auto;
  41.   padding: $spacing*2;
  42. }

  43. .details-group {
  44.   border: 1px solid $gray;
  45.   border-radius: 5px;
  46.   background-color: white;
  47. }

  48. .details {
  49.   overflow: hidden;
  50.   border-bottom: 1px solid $gray;
  51.   transition: height $speed $easing;
  52.   
  53.   &:last-child {
  54.     border-bottom: 0;
  55.   }
  56.   
  57.   &__summary,
  58.   &__content {
  59.     padding: $spacing;
  60.   }
  61.   
  62.   &__summary {
  63.     position: relative;
  64.     list-style: none; // Hide the marker (proper method)
  65.     padding-left: $spacing*2;
  66.     outline: 0;
  67.     cursor: pointer;
  68.     font-size: 1.4rem;
  69.     font-family: Oswald;
  70.     text-transform: uppercase;
  71.     transition: color $speed $easing;
  72.    
  73.     [open] > & {
  74.       color: $primary;
  75.     }
  76.    
  77.     // Hide the marker in Webkit
  78.     &::-webkit-details-marker {
  79.       display: none;
  80.     }
  81.    
  82.     // Our replacement marker
  83.     &:before,
  84.     &:after {
  85.       content: "";
  86.       position: absolute;
  87.     }
  88.    
  89.     &:before {
  90.       left: $spacing/2 + $plus-size/2;
  91.       top: 50%;
  92.       height: $plus-thickness;
  93.       margin-top: -$plus-thickness/2;
  94.       width: $plus-size;
  95.       background: $primary;
  96.     }
  97.    
  98.     &:after {
  99.       left: $spacing/2 + $plus-size;
  100.       top: 50%;
  101.       height: $plus-size;
  102.       margin-top: -$plus-size/2;
  103.       width: $plus-thickness;
  104.       margin-left: -$plus-thickness/2;
  105.       background: $primary;
  106.       transition: all $speed $easing;
  107.     }
  108.    
  109.     [open] &:after {
  110.       opacity: 0;
  111.       transform: translateY(25%);
  112.     }
  113.   }
  114.   
  115.   &__content {
  116.     padding-top: 0;
  117.     padding-left: $spacing*2;
  118.   }
  119. }
复制代码
  1. class Details {
  2.   constructor(el, settings) {
  3.     this.group    = el;
  4.     this.details  = this.group.getElementsByClassName("details");
  5.     this.toggles  = this.group.getElementsByClassName("details__summary");
  6.     this.contents = this.group.getElementsByClassName("details__content");   
  7.    
  8.     // Set default settings if necessary
  9.     this.settings = Object.assign({
  10.       speed: 300,
  11.       one_visible: false
  12.     }, settings);
  13.    
  14.     // Setup inital positions
  15.     for (let i = 0; i < this.details.length; i++) {
  16.       const detail  = this.details[i];
  17.       const toggle  = this.toggles[i];
  18.       const content = this.contents[i];
  19.       
  20.       // Set transition-duration to match JS setting
  21.       detail.style.transitionDuration = this.settings.speed + "ms";
  22.       
  23.       // Set initial height to transition from
  24.       if (!detail.hasAttribute("open")) {
  25.         detail.style.height = toggle.clientHeight + "px";
  26.       } else {
  27.         detail.style.height = toggle.clientHeight + content.clientHeight + "px";
  28.       }
  29.     }
  30.    
  31.     // Setup click handler
  32.     this.group.addEventListener("click", (e) => {      
  33.       if (e.target.classList.contains("details__summary")) {
  34.         e.preventDefault();
  35.         
  36.         let num = 0;
  37.         for (let i = 0; i < this.toggles.length; i++) {
  38.           if (this.toggles[i] === e.target) {
  39.             num = i;
  40.             break;
  41.           }
  42.         }
  43.         
  44.         if (!e.target.parentNode.hasAttribute("open")) {
  45.           this.open(num);
  46.         } else {
  47.           this.close(num);
  48.         }
  49.       }
  50.     });
  51.   }
  52.   
  53.   open(i) {
  54.     const detail = this.details[i];
  55.     const toggle = this.toggles[i];
  56.     const content = this.contents[i];
  57.    
  58.     // If applicable, hide all the other details first
  59.     if (this.settings.one_visible) {
  60.       for (let a = 0; a < this.toggles.length; a++) {
  61.         if (i !== a) this.close(a);
  62.       }
  63.     }
  64.    
  65.     // Update class
  66.     detail.classList.remove("is-closing");
  67.    
  68.     // Get height of toggle
  69.     const toggle_height = toggle.clientHeight;
  70.    
  71.     // Momentarily show the contents just to get the height
  72.     detail.setAttribute("open", true);
  73.     const content_height = content.clientHeight;
  74.     detail.removeAttribute("open");
  75.    
  76.     // Set the correct height and let CSS transition it
  77.     detail.style.height = toggle_height + content_height + "px";
  78.    
  79.     // Finally set the open attr
  80.     detail.setAttribute("open", true);
  81.   }
  82.   
  83.   close(i) {
  84.     const detail = this.details[i];
  85.     const toggle = this.toggles[i];
  86.    
  87.     // Update class
  88.     detail.classList.add("is-closing");
  89.    
  90.     // Get height of toggle
  91.     const toggle_height = toggle.clientHeight;
  92.    
  93.     // Set the height so only the toggle is visible
  94.     detail.style.height = toggle_height + "px";
  95.    
  96.     setTimeout(() => {
  97.       // Check if still closing
  98.       if (detail.classList.contains("is-closing"))
  99.         detail.removeAttribute("open");
  100.         detail.classList.remove("is-closing");
  101.     }, this.settings.speed);
  102.   }
  103. }

  104. (() => {
  105.   const els = document.getElementsByClassName("details-group");
  106.   
  107.   for (let i = 0; i < els.length; i++) {
  108.     const details = new Details(els[i], {
  109.       speed: 500,
  110.       one_visible: true
  111.     });
  112.   }
  113. })();
复制代码


您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表