57

How do I add an expand/collapse transition?

function showHide(shID) {
    if (document.getElementById(shID)) {
        if (document.getElementById(shID + '-show').style.display != 'none') {
            document.getElementById(shID + '-show').style.display = 'none';
            document.getElementById(shID).style.display = 'block';
        } else {
            document.getElementById(shID + '-show').style.display = 'inline';
            document.getElementById(shID).style.display = 'none';
        }
    }
}
.more {
    display: none;
    padding-top: 10px;
}

a.showLink,
a.hideLink {
    text-decoration: none;
    -webkit-transition: 0.5s ease-out;
    background: transparent url('down.gif') no-repeat left;
}

a.hideLink {
    background: transparent url('up.gif') no-repeat left;
}
Here is some text.
<div class="readmore">
    <a href="#" id="example-show" class="showLink" onclick="showHide('example');return false;">Read more</a>
    <div id="example" class="more">
        <div class="text">Here is some more text: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum vitae urna nulla. Vivamus a purus mi. In hac habitasse platea dictumst. In ac tempor quam. Vestibulum eleifend vehicula ligula, et cursus nisl gravida sit amet. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.</div>
        <p><a href="#" id="example-hide" class="hideLink" onclick="showHide('example');return false;">Hide</a></p>
    </div>
</div>

http://jsfiddle.net/Bq6eK/1

TylerH
  • 20,799
  • 66
  • 75
  • 101
Felix
  • 1,533
  • 1
  • 15
  • 27
  • As an aside, apparently animating the height property directly is expensive. And using scale instead is complex: https://developers.google.com/web/updates/2017/03/performant-expand-and-collapse – mtyson Jan 13 '20 at 21:57

6 Answers6

53

This is my solution that adjusts the height automatically:

function growDiv() {
  var growDiv = document.getElementById('grow');
  if (growDiv.clientHeight) {
    growDiv.style.height = 0;
  } else {
    var wrapper = document.querySelector('.measuringWrapper');
    growDiv.style.height = wrapper.clientHeight + "px";
  }
  document.getElementById("more-button").value = document.getElementById("more-button").value == 'Read more' ? 'Read less' : 'Read more';
}
#more-button {
  border-style: none;
  background: none;
  font: 16px Serif;
  color: blue;
  margin: 0 0 10px 0;
}

#grow input:checked {
  color: red;
}

#more-button:hover {
  color: black;
}

#grow {
  -moz-transition: height .5s;
  -ms-transition: height .5s;
  -o-transition: height .5s;
  -webkit-transition: height .5s;
  transition: height .5s;
  height: 0;
  overflow: hidden;
}
<input type="button" onclick="growDiv()" value="Read more" id="more-button">

<div id='grow'>
  <div class='measuringWrapper'>
    <div class="text">Here is some more text: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum vitae urna nulla. Vivamus a purus mi. In hac habitasse platea dictumst. In ac tempor quam. Vestibulum eleifend vehicula ligula, et cursus nisl gravida sit
      amet. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.</div>
  </div>
</div>

I used the workaround that r3bel posted: Can you use CSS3 to transition from height:0 to the variable height of content?

pushkin
  • 9,575
  • 15
  • 51
  • 95
Felix
  • 1,533
  • 1
  • 15
  • 27
  • 16
    `.measuringWrap` is unnecessary. You can use `.growDiv`'s scrollHeight property http://jsfiddle.net/9ccjE/293/ – Guria Aug 31 '15 at 10:51
  • 2
    And this transition is made via JS but not CSS as asked in the question. – Igor Buts Dec 28 '18 at 15:44
  • @IgorButs The asker is typically entitled to go with whatever implementation they want. – TylerH Apr 01 '19 at 13:44
  • I would remove the height after a timeout of .5 ms when expanding to ensure that the div will resize if window or container size changes. – hansmaad Oct 08 '20 at 14:57
11

this should work, had to try a while too.. :D

function showHide(shID) {
  if (document.getElementById(shID)) {
    if (document.getElementById(shID + '-show').style.display != 'none') {
      document.getElementById(shID + '-show').style.display = 'none';
      document.getElementById(shID + '-hide').style.display = 'inline';
      document.getElementById(shID).style.height = '100px';
    } else {
      document.getElementById(shID + '-show').style.display = 'inline';
      document.getElementById(shID + '-hide').style.display = 'none';
      document.getElementById(shID).style.height = '0px';
    }
  }
}
#example {
  background: red;
  height: 0px;
  overflow: hidden;
  transition: height 2s;
  -moz-transition: height 2s;
  /* Firefox 4 */
  -webkit-transition: height 2s;
  /* Safari and Chrome */
  -o-transition: height 2s;
  /* Opera */
}

a.showLink,
a.hideLink {
  text-decoration: none;
  background: transparent url('down.gif') no-repeat left;
}

a.hideLink {
  background: transparent url('up.gif') no-repeat left;
}
Here is some text.
<div class="readmore">
  <a href="#" id="example-show" class="showLink" onclick="showHide('example');return false;">Read more</a>
  <div id="example" class="more">
    <div class="text">
      Here is some more text: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum vitae urna nulla. Vivamus a purus mi. In hac habitasse platea dictumst. In ac tempor quam. Vestibulum eleifend vehicula ligula, et cursus nisl gravida sit amet.
      Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.
    </div>
    <p>
      <a href="#" id="example-hide" class="hideLink" onclick="showHide('example');return false;">Hide</a>
    </p>
  </div>
</div>
pushkin
  • 9,575
  • 15
  • 51
  • 95
r3bel
  • 1,350
  • 8
  • 12
  • Good, but how do I avoid setting the height in the javascript? The length of the content inside the more class is going to vary. – Felix Jul 17 '12 at 00:46
  • 3
    as written [here](http://stackoverflow.com/a/3512194/1517380) this is not possible atm (since you wanted to have an animation right?) except with [a expensive workaround described here](http://stackoverflow.com/questions/3149419/can-you-use-css3-to-transition-from-height0-to-the-variable-height-of-content) – r3bel Jul 17 '12 at 01:18
  • i edited your jsfiddle again and added a "workaround" for dynamic height, but you have to get the height of the dropdown each time when the dropdown height changes with javascript: [jsfiddle](http://jsfiddle.net/Bq6eK/217/). i do not guarantee you that this works with all browsers correctly :D – r3bel Jul 17 '12 at 23:55
  • This is perhaps not the best solution because it is not responsive. When using the CSS with `overflow: hidden` it hides the "hide" link. – JGallardo Oct 22 '15 at 20:05
2

OMG, I tried to find a simple solution to this for hours. I knew the code was simple but no one provided me what I wanted. So finally got to work on some example code and made something simple that anyone can use no JQuery required. Simple javascript and css and html. In order for the animation to work you have to set the height and width or the animation wont work. Found that out the hard way.

<script>
        function dostuff() {
            if (document.getElementById('MyBox').style.height == "0px") {

                document.getElementById('MyBox').setAttribute("style", "background-color: #45CEE0; height: 200px; width: 200px; transition: all 2s ease;"); 
            }
            else {
                document.getElementById('MyBox').setAttribute("style", "background-color: #45CEE0; height: 0px; width: 0px; transition: all 2s ease;"); 
             }
        }
    </script>
    <div id="MyBox" style="height: 0px; width: 0px;">
    </div>

    <input type="button" id="buttontest" onclick="dostuff()" value="Click Me">
Deathstalker
  • 794
  • 10
  • 8
1

http://jsfiddle.net/Bq6eK/215/

I did not modify your code for this solution, I wrote my own instead. My solution isn't quite what you asked for, but maybe you could build on it with existing knowledge. I commented the code as well so you know what exactly I'm doing with the changes.

As a solution to "avoid setting the height in JavaScript", I just made 'maxHeight' a parameter in the JS function called toggleHeight. Now it can be set in the HTML for each div of class expandable.

I'll say this up front, I'm not super experienced with front-end languages, and there's an issue where I need to click the 'Show/hide' button twice initially before the animation starts. I suspect it's an issue with focus.

The other issue with my solution is that you can actually figure out what the hidden text is without pressing the show/hide button just by clicking in the div and dragging down, you can highlight the text that's not visible and paste it to a visible space.

My suggestion for a next step on top of what I've done is to make it so that the show/hide button changes dynamically. I think you can figure out how to do that with what you already seem to know about showing and hiding text with JS.

Kijewski
  • 25,517
  • 12
  • 101
  • 143
  • I made two minor edits: http://jsfiddle.net/Bq6eK/215/. `onclick="..."` should return false, and the height should be set directly for the element, not in the CSS. Otherwise to have to click it twice to open the box, because `elem.style` contains only style set directly for the element, but not the effective styles. – Kijewski Jul 17 '12 at 21:20
0

Achieving with this animation trick.

const toggleButton = document.getElementById('toggleButton');
const wrapper = document.querySelector('.wrapper');

toggleButton.addEventListener('click', () => {
  wrapper.classList.toggle('open');
});
.expandable {
  min-height: 0;
  background: gray;
  color: white;
}

/* Initially, set the grid rows to take up no space (height 0).
   This effectively hides the content inside the wrapper element. */
.wrapper {
  display: grid;
  overflow: hidden;
  transition: grid-template-rows 400ms;
  grid-template-rows: 0fr;  /* 0fr means 0 fraction of available space */
}

/* When the 'open' class is applied to the wrapper element, set the grid rows to take up all available space (height 1fr).
   This expands the wrapper and shows the content inside. */
.wrapper.open {
  grid-template-rows: 1fr;  /* 1fr means 1 fraction of available space */
}
<button id="toggleButton">
  Toggle
</button>

<div class="wrapper">
  <div class="expandable">
    Here is some more text: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum vitae urna nulla. Vivamus a purus mi. In hac habitasse platea dictumst. In ac tempor quam. Vestibulum eleifend vehicula ligula, et cursus nisl gravida sit amet.
    Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.
  </div>
</div>
Penny Liu
  • 15,447
  • 5
  • 79
  • 98
-4

Here's a solution that doesn't use JS at all. It uses checkboxes instead.

You can hide the checkbox by adding this to your CSS:

.container input{
    display: none;
}

And then add some styling to make it look like a button.

Here's the source code that I modded.

  • 3
    The solution does not apply a height according to the amount of text. In the CSS you have to decide the height of the checked div: `.ac-container input:checked ~ article.ac-small{ height: 140px; }` I need a solution that applies the height according to the content. – Felix Aug 06 '12 at 22:40