下拉列表动画(JavaScript实现)

  要实现一个带动画效果的下拉列表,通常的做法是,将外部容器的高度设为一个固定值,设置其overflow为hidden, 同时为其应用CSS过渡属性,点击相应的按钮后改变外部容器的高度,如下面的实现。

<div id="p11-1-btn" onclick="toggleByHeight()">▼点我展开</div>
<ul id="p11-1" style="height: 0px;">
  <li style="background: #FF6666">第一</li>
  <li style="background: #FF9900">第二</li>
  <li style="background: #CCFF00">第三</li>
  <li style="background: #CC3399">第四</li>
</ul>
function toggleByHeight() {
  const containerStyle =  document.querySelector('#p11-1').style;
  const buttonExpand = document.querySelector('#p11-3-btn');
  if (containerStyle.height == '0px') {
      containerStyle.height = '120px';
      buttonExpand.innerText = '▲点我收起';
  } else {
    containerStyle.height = '0px';
    buttonExpand.innerText = '▼点我展开 ';
  }
}
#p11-1 {
  overflow:hidden;
  transition: height 1s;
  margin: 0px;
}

#p11-1 li {
  font-size: 16px;
  color: #ffffff;
  line-height: 30px;
  text-align: center;
  margin: 0px;
}

#p11-1-btn {
  cursor: pointer;
  background-color: #dcdcdc;
}

flex-basis

  上面的方法只适用于内部元素高度已知的情况。我们知道每个 li 的行高为30px, 由于 li 没有边框、内边距、外边距,则每个li 的高度就是30px, 全部展开后总高度为150px.
  但是现实中,我们并不总能知道内部元素的高度,而 display: nonedisplay: auto 之间是无法应用过渡效果的(具体为啥我也不知道),因此这种方法就不适用了,那么内部元素的高度应该如何计算出来呢?有人会说可以为父元素设置最大高度 max-height, 这样只要 max-height 的值大于内部元素的总高度就行,但这样做要求我们提供的 max-height 值不能过小也不能过大,过小则内部元素不能全部显示出来,过大则动画会出现延时。

  更好的方法是用JS去计算内部元素的高度,假设有如下的HTML结构,虽然外部容器的高度为0px, 但内部每个元素的高度仍旧为其原来的高度, 只是它被隐藏了起来而已,我们可以通过JS去计算内部元素的总高度。

<div id="p11-2" style="height: 0px;  overflow: hidden;">
  <div style="height: 40px; padding:10px">第一</div>
  <div style="height: 80px;">第二</div>
  <div style="height: 60px;">第三</div>
</div>

<script>
  const parent = document.querySelector('#p11-2');
  console.log(parent.style.height)  // 0px
  const childs = document.querySelectorAll('#p11-2 div');
  let totalHeight = 0;
  childs.forEach(child => {
    totalHeight += parseInt(child.style.height);
  });
  console.log(totalHeight)  // 180
</script>

  需要注意的是,你必须使用内联样式为元素指定高度,使用内部样式表或者外部样式表是无法正确通过JS获取高度值的。解决这个问题的办法是,获取该元素的 offsetHeight 而不是 height, offsetHeight 包含该元素的内容高度、垂直内边距和边框,这样一来,不管内部元素有没有内边距、边框,我们总能正确的计算其高度。

offsetHight

  下面利用这种方法重写一下上面的程序。

<div id="p11-3-btn" onclick="toggleByOffsetHeight()">▼点我展开</div>
<ul id="p11-3" style="height: 0px;">
  <li style="background: #FF6666; padding: 10px 0px">第一</li>
  <li style="background: #FF9900; border: solid 5px red">第二</li>
  <li style="background: #CCFF00; padding: 15px 0px">第三</li>
  <li style="background: #CC3399; border: double 3px blue">第四</li>
</ul>
function toggleByOffsetHeight() {
  const containerStyle = document.querySelector('#p11-3').style;
  const childs =  document.querySelectorAll('#p11-3 li');
  const buttonExpand = document.querySelector('#p11-3-btn');
  let totalHeight = 0;
  childs.forEach(child => {
    totalHeight += parseInt(child.offsetHeight);
  });
  if (containerStyle.height == '0px') {
      containerStyle.height = `${totalHeight}px`;
      buttonExpand.innerText = '▲点我收起';
  } else {
    containerStyle.height = '0px';
    buttonExpand.innerText = '▼点我展开 ';
  }
}
#p11-3 {
  overflow:hidden;
  transition: height 1s;
  margin: 0px;
}

#p11-3 li {
  font-size: 16px;
  color: #ffffff;
  line-height: 30px;
  text-align: center;
  margin: 0px;
}

#p11-3-btn {
  cursor: pointer;
  background-color: #dcdcdc;
}

flex-basis

  可以看到,无论内部元素占据的高度是什么,我们总能实现一个流畅的下拉列表过渡动画。

该篇博客内的代码已同步到Github

参考资料:
[1]. MDN文档 https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement/offsetHeight

posted @ 2020-03-30 14:51  梦醒时夜续  阅读(1849)  评论(0编辑  收藏  举报