Reputation: 709
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
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
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
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