Tony J Watson
Tony J Watson

Reputation: 709

CSS Expand / Contract Animation to Show/Hide Content

I am trying to create a box that can expand and collapse with a simple slide out animation. If you run the example below, the idea is that it starts with one red line and when you click the button it separates into two read lines and gently expands to reveal the content like pulling a draw out of a table.

I've tried both transform, animation, relative: positioning with top, and i'm unable to get the desired effect.

The containing box should expand in size

function expandContract() {
   const el = document.getElementById("expand-contract")
   el.classList.toggle('expanded')
   el.classList.toggle('collapsed')
}
#container {
   border: 1px solid black;
   padding: 15px;
}

#top-section {
  border-bottom: 1px solid red;
}

#expand-contract {
  border-bottom: 1px solid red;
}

.expand-contract {
   transform: translateY(-100%)
   overflow: hidden;
}

@keyframes slide-in {
    100% {
        transform: translateY(0%)
    }
}

.expanded {
   background-color: green;
   animation-name: slide-in;
   animation-duration: 1s;
}

.collapsed {
   background-color: red;
   transform: translateY(-100%)
}
<div id="container">
  <div id="top-section">
    This is always displayed
  </div>
  
  <div id="expand-contract" class="expanded">
    This section expands and contracts
  
    <table>
      <tr><td>test1</td></tr>
      <tr><td>test2</td></tr>
      <tr><td>test3</td></tr>
      <tr><td>test4</td></tr>
    </table>
  </div>
  
  <div id="bottom-section">
    This section is always displayed
  </div>
</div>

<button onclick="expandContract()">Expand/Contract</button>

Upvotes: 14

Views: 65085

Answers (3)

Daniel Iftimie
Daniel Iftimie

Reputation: 233

Here is a workaround without JS just CSS & HTML tricks, making use of <input type="checkbox" /> in order to collapse/expand your content.

Below the snipped illustrates a dismissible alert. The important thing here is that the .wrapper needs to have overflow: hidden and we play with .alert's height.

If the .alert has padding we need to add a negative margin to the .alert when it's checked so that it behaves like it has been removed from context.

The collapse can either be triggered from the X button inside the .alert or from the Toggle button below.

.wrapper {
  overflow: hidden;
}

.alert {
  font-family: sans-serif;
  font-size: 16px;
  border-radius: 4px;
  padding: 20px;
  display: flex;
  align-items: center;
  gap: 16px;
  transition: visibility .3s linear, opacity .3s ease, margin-top .3s ease, height .3s ease;
  border: 1px solid #ccc;
  background: #FEECCA;
}

.body {
    flex: 1;
}

.check {
  visibility: hidden;
  position: absolute;
  top: -9999px;
  left: -9999px;
}
    
.check:checked + .alert {
  height: 0;
  margin-top: -40px; /* if your collapsible element has padding, use the top & bottom padding value */
  visibility: hidden;
  opacity: 0;
}

.close {
  cursor: pointer;
}
    
.button {
  padding:15px 20px;
  font-family: sans-serif;
  font-size: 16px;
  display: inline-block;
  border: 1px solid #ccc;
  cursor: pointer;
}
<div class="wrapper">
  <input type="checkbox" class="check" id="check1">
  <div class="alert">
    <div class="body">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse nulla elit, iaculis vitae facilisis quis, sollicitudin elementum lacus. Nunc vitae tellus vel mi ullamcorper luctus. Nunc laoreet ligula diam.</div>
    <label class="close" for="check1" tabindex="0" aria-label="Close">
      <svg class="icon-close" width="12" height="12" aria-hidden="true" viewBox="0 0 32 32">
        <path d="M2.897 0.434l28.303 28.303c0.579 0.579 0.579 1.518 0 2.096s-1.518 0.579-2.096 0l-28.303-28.303c-0.579-0.579-0.579-1.518 0-2.096s1.518-0.579 2.096 0z"></path>
        <path d="M0.8 29.469l28.303-28.303c0.579-0.579 1.518-0.579 2.096 0s0.579 1.518 0 2.096l-28.303 28.303c-0.579 0.579-1.518 0.579-2.096 0s-0.579-1.518 0-2.096z"></path>
      </svg>
    </label>
  </div>
</div>

<br/>
<br/>

<label class="button" for="check1" tabindex="0" aria-label="Toggle" role="button">Toggle Collapse/Expand</label>

Upvotes: 2

AirBlack
AirBlack

Reputation: 145

here is an example that uses jquery

hope to help you

function expandContract() {
    $header = $(".header");
    $content = $("#expand-contract")
    $content.slideToggle(500, function () {
        $header.text(function () {
            return $content.is(":visible") ? "Collapse" : "Expand";
        });
    });
};
.container {
    width:100%;
    border:1px solid #d3d3d3;
}
.container div {
    width:100%;
}
.header {
    background-color:#d3d3d3;
    padding: 2px;
    cursor: pointer;
    font-weight: bold;
}
.container .expanded {
    display: none;
    padding : 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="container">
    <div id="top-section">
      This is always displayed
    </div>
   <div id="expand-container">
    <div class="expanded" id="expand-contract">
         <table>
            <tr><td>test1</td></tr>
            <tr><td>test2</td></tr>
            <tr><td>test3</td></tr>
            <tr><td>test4</td></tr>
        </table>
    </div>
  </div>
</div>
<button class="header" onclick="expandContract()">Expand/Contract</button>

also see codepen here.

Upvotes: 4

smashed-potatoes
smashed-potatoes

Reputation: 2222

You can achieve this using the CSS transition along with toggled styles. Initially you may think to transition the height (from 0 to initial so that it expands dynamically based on height) but unfortunately CSS transition doesn't properly handle this.

Instead, you can wrap it in a container of its own with overflow: hidden and then use a margin-top: -100% to hide it, and 0 to show it.

Here is your code with this modification:

function expandContract() {
   const el = document.getElementById("expand-contract")
   el.classList.toggle('expanded')
   el.classList.toggle('collapsed')
}
#container {
   border: 1px solid black;
   padding: 15px;
}

#top-section {
  border-bottom: 1px solid red;
}

#expand-container {
  overflow: hidden;
}

#expand-contract {
  border-bottom: 1px solid red;
  margin-top: -100%;
  transition: all 1s;
}

#expand-contract.expanded {
   background-color: green;
   margin-top: 0;
}
<div id="container">
  <div id="top-section">
    This is always displayed
  </div>
  
  <div id="expand-container">
    <div id="expand-contract" class="expanded">
      This section expands and contracts
  
      <table>
        <tr><td>test1</td></tr>
        <tr><td>test2</td></tr>
        <tr><td>test3</td></tr>
        <tr><td>test4</td></tr>
      </table>
    </div>
  </div>
  
  <div id="bottom-section">
    This section is always displayed
  </div>
</div>

<button onclick="expandContract()">Expand/Contract</button>

Upvotes: 31

Related Questions