手风琴-JS
#javascript #面试 #accordion

构建一个手风琴组件,该组件显示一个垂直堆叠的部分列表,每个部分都包含标题和内容片段。为您提供一些HTML,作为示例内容以及雪佛龙图标。

Image description

Image description

要求

  • 默认情况下,所有部分都折叠并隐藏在视图中。
  • 单击部分标题可切换内容。
    • 如果部分崩溃,则将扩展部分并将显示内容。
    • 如果扩展了部分,则该部分将折叠并隐藏内容。
  • 这些部分彼此独立。

笔记

  • 这个问题的重点是功能,而不是样式。不要花太多时间编写自定义CSS。 您可以修改标记(例如添加ID,数据属性,替换一些标签等),然后使用客户端渲染。
  • 您可能想考虑改善应用程序的用户体验并实施它们的方法(在面试中您会获得奖励信用)。

解决方案

config / api设计< / strong>
建立组件的复杂性的一部分是为其设计API。手风琴函数接受根元素$rootEl,其中将插入手风琴部分。手风琴函数的第二个参数是用于存储配置选项的对象。最低限度,我们将需要以下选项:

  • items:项目对象列表。每个项目都是带有字段的对象:
    • 值:手风琴项目的唯一标识符。
    • 标题:“要在手风琴标题中显示的文本标签。”
    • 内容:何时展示该部分的内容。

与我们典型的香草JS方法不同,我们选择不将任何状态保留在JavaScript这次,而依靠DOM来跟踪每个手风琴部分的状态,并根据该手风琴的状态扩展/折叠适当的元素。

accordion函数调用initattachEvents函数来通过渲染dom元素和附加必要的事件侦听器来设置手风琴组件。

init
此功能设置了整个组件生命周期中保留的DOM元素,也就是它们将永远不会被破坏。手风琴的部分(标题和内容)被渲染。

attachEvents
在此组件(即单击事件)中,仅需要一个事件侦听器才能添加到根元素中。我们利用事件委托,因此只能添加一个事件的侦听器,并且将为其所有孩子内容工作。 但是,我们必须小心检查要单击哪个元素,并确保我们仅在单击标题而不是内容时响应。

在手风琴标题上触发单击时,我们需要旋转图标并切换手风琴内容上的隐藏属性。

测试用例

  • 应显示所有提供的部分。
  • 单击崩溃的部分的标题应扩展。
  • 单击扩展的部分的标题应崩溃。
  • 测试所有部分都可以独立扩展和崩溃。
  • 测试您能够初始化组件的多个实例,每个实例都具有独立状态。

可访问性
交互式元素需要集中精力,因此我们将使用<button>进行标题。


html:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="./index.css" />
  </head>
  <body>
    <div id="accordion" class="accordion"></div>
    <!-- below is single item DOM example after runs accordion fn -->
    <!-- 
      <div>
        <div>
          HTML
          <span
            aria-hidden="true"
            class="accordion-icon"></span>
        </div>
        <div>
          The HyperText Markup Language or HTML is the
          standard markup language for documents designed to
          be displayed in a web browser.
        </div>
      </div>

    -->
    <script src="index.js"></script>
  </body>
</html>

JS:

const data = {
  sections: [
    {
      value: 'html',
      title: 'HTML',
      contents:
        'The HyperText Markup Language or HTML is the standard markup language for documents designed to be displayed in a web browser.',
    },
    {
      value: 'css',
      title: 'CSS',
      contents:
        'Cascading Style Sheets is a style sheet language used for describing the presentation of a document written in a markup language such as HTML or XML.',
    },
    {
      value: 'javascript',
      title: 'JavaScript',
      contents:
        'JavaScript, often abbreviated as JS, is a programming language that is one of the core technologies of the World Wide Web, alongside HTML and CSS.',
    },
  ],
};



(() => {
  function accordion($rootEl, { sections }) {
    function attachEvents() {
      // Use Event Delegation.
      $rootEl.addEventListener('click', (event) => {
        const target = event.target;
        if (
          target.tagName !== 'BUTTON' ||
          !target.classList.contains('accordion-item-title')
        ) {
          return;
        }

        // Find the icon and toggle the direction.
        const $icon = target.querySelector('.accordion-icon');
        $icon.classList.toggle('accordion-icon--rotated');

        //key point --->通过target.nextSibling找到内容,然后toggle内容的显示隐藏
        const $accordionContents = target.nextSibling;
        $accordionContents.hidden = !$accordionContents.hidden;
      });
    }

    function init() {
      const $accordionSections = document.createDocumentFragment();

      sections.forEach(({ value, title, contents }) => {
        const $accordionSection = document.createElement('div');
        $accordionSection.classList.add('accordion-item');

        const $accordionTitleBtn = document.createElement('button');
        $accordionTitleBtn.classList.add('accordion-item-title');
        $accordionTitleBtn.type = 'button';
        $accordionTitleBtn.setAttribute('data-value', value);

        const $accordionIcon = document.createElement('span');
        $accordionIcon.classList.add('accordion-icon');
        $accordionIcon.setAttribute('aria-hidden', 'true');

        $accordionTitleBtn.append(title, $accordionIcon);

        const $accordionSectionContents = document.createElement('div');
        $accordionSectionContents.classList.add('accordion-item-contents');
        $accordionSectionContents.hidden = true;
        $accordionSectionContents.textContent = contents;

        $accordionSection.append($accordionTitleBtn, $accordionSectionContents);
        $accordionSections.append($accordionSection);
      });

      $rootEl.appendChild($accordionSections);
    }

    init();
    attachEvents();
  }

  accordion(document.getElementById('accordion'), data);
})();

CSS:

.accordion {
  display: flex;
  flex-direction: column;
  width: 100%;
}

.accordion-item {
  display: flex;
  flex-direction: column;
  row-gap: 4px;
  padding: 4px 0;
}

.accordion-item:not(:first-child) {
  border-top: 1px solid #eee;
}

.accordion-item-title {
  align-items: center;
  border: none;
  background: none;
  cursor: pointer;
  font-weight: 500;
  padding: 4px;
  justify-content: space-between;
  text-align: start;
  display: flex;
}

.accordion-item-title:hover {
  background-color: #eee;
}

.accordion-icon {
  border: solid currentcolor;
  border-width: 0 2px 2px 0;
  display: inline-block;
  height: 8px;
  pointer-events: none;
  transform: translateY(-2px) rotate(45deg);
  width: 8px;
}

.accordion-icon--rotated {
  transform: translateY(2px) rotate(-135deg);
}

.accordion-item-contents {
  font-size: 14px;
  line-height: 1.2em;
  padding: 4px;
}