I have a group of expandable divs inside a CSS flex container .
Codepen - https://codepen.io/vijayvmenon/pen/bGNRwvd
CSS:
.collapsible {
background-color: #777;
color: white;
cursor: pointer;
padding: 18px;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 15px;
}
.content {
padding: 0 18px;
max-height: 0;
overflow: hidden;
transition: max-height 0.2s ease-out;
background-color: #f1f1f1;
}
When I click on any div, the other divs in the row also expand. How can I fix this by expanding only the div I clicked and not expanding the other divs in the row?
I am using the flex container because im building a responsive page and there are more components, which are not included here. Is it possible to do with a flex/grid container ? Please let me know.
Any other solution by which I can wrap the divs and make it responsive should be fine.
I created a similar issue where I am using the polymer framework - Collapsible div inside a CSS grid container expands full width
Since I didn't get any help here, thought of creating one in Vanilla Javascript. Please help.
Note: I don't know the actual number of items beforehand and it is rendered using a "dom-repeat" in polymer library.So I can only have a container div enclosing the complete set of items. I can't have enclosing divs for each column of items (unless there is a way to do that)
Here is the code below (vanilla JS as I don't use polymer). It is responsive and will adjust the amount of column based on your item width (which I assume you will change using media-query). The idea is:
Measure the item width you specified in CSS by creating a dummy item
Adjust the number of columns needed based on measured item width. It is necessary to wrap the items in columns because it shouldn't be possible to do what you want by wrapping everything in one single flex container.
Create the elements dynamically using vanilla JS (use polymer in your case)
Add items to the columns one by one
The logic for clicking is still the same as yours
Here is the full working example:
let container = document.querySelector('.container')
let columnAmount = (() => {
let containerWidth = parseInt(getComputedStyle(container).width)
let dummyItem = document.createElement('div')
let itemWidth
dummyItem.classList.add('item')
container.appendChild(dummyItem)
itemWidth = parseInt(getComputedStyle(dummyItem).width)
dummyItem.remove()
return Math.floor(containerWidth / itemWidth)
})()
let newColumns = []
for (let i = 0; i < columnAmount; i++) {
newColumns.push(document.createElement('div'))
newColumns[i].classList.add('item')
container.appendChild(newColumns[i])
}
let childAmount = 11 // Change this to your needs
let newChild
let newCollapsibleButton
let newContent
let newContentParagraph
for (let i = 0; i < childAmount; i++) {
newChild = document.createElement('div')
newCollapsibleButton = document.createElement('button')
newContent = document.createElement('div')
newContentParagraph = document.createElement('p')
newChild.classList.add('item--inner')
newCollapsibleButton.classList.add('collapsible')
newContent.classList.add('content')
newCollapsibleButton.appendChild(document.createTextNode(`Open Section ${i + 1}`))
newContentParagraph.appendChild(document.createTextNode(`Section ${i + 1} Details`))
newContent.appendChild(newContentParagraph)
newChild.appendChild(newCollapsibleButton)
newChild.appendChild(newContent)
newColumns[i % columnAmount].appendChild(newChild)
}
let collapsibleButtons = document.querySelectorAll(".collapsible");
for (let i = 0; i < collapsibleButtons.length; i++) {
collapsibleButtons[i].addEventListener('click', function() {
let content = this.nextElementSibling;
this.classList.toggle("active");
content.style.maxHeight = content.style.maxHeight ? null : `${content.scrollHeight}px`
});
}
.container {
display:flex;
justify-content:space-between;
flex-wrap:wrap;
}
.item {
width: 30%;
}
.item--inner {
width: 100%;
}
.collapsible {
background-color: #777;
color: white;
cursor: pointer;
padding: 18px;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 15px;
}
.content {
padding: 0 18px;
max-height: 0;
overflow: hidden;
transition: max-height 0.2s ease-out;
background-color: #f1f1f1;
}
<div class = "container">
</div>
Note that it doesn't handle page resizing, i.e. the items will be at that fixed state on page load (handle page resizing whenever necessary). For convenience, adjust the screen size of this JSFiddle: here and see how it reacts to different screen sizes. Running on the above snippet only allows one column of items as on load, the container size is pretty limited. I have adjusted so that an item has item--inner and allows the item to always be 30% of the container size. If you don't want it to always be 30% of its container size, simply correct the CSS styling of item. For easier code adjustment, see JSFiddle here: here.
Note: Change item width back to 400px if you want to see the two-columned version and the vertically stacked version. Make sure to adjust the screen size before the page has loaded.
Well, this is my solution...
it create a new div on each column, and dispach all item inside. on window resize it create more or less columns, to respect responsive case
here is my code:
(function () {
const Container = document.querySelector('.container')
, All_item = [...document.querySelectorAll('.item')]
;
if ( All_item.length===0 ) return
const items_W = All_item[0].offsetWidth // get item width
, divGpr = document.createElement('div') // base col element
, flexCol = [] // array of ihm cols
;
divGpr.style.width = items_W+'px'; // perfect size
function SetNewCols() //
{
let nbCol = Math.max(1, Math.floor( Container.offsetWidth / items_W ) )
, len = flexCol.length;
if (nbCol===len) return // col unchanged
for(let k=0;k<nbCol;k++) // create new Col
{ flexCol.push( Container.appendChild(divGpr.cloneNode(true))) }
let k=0;
for(item of All_item)
{
flexCol[len+k].appendChild(item) // dispach item in new col
k = ++k % nbCol // future col of
}
for(i=0;i<len;i++)
{ Container.removeChild( flexCol.shift() ) } // remove old empty cols
if(k>0)
{ flexCol[( flexCol.length -1)].appendChild(item) }
}
SetNewCols(); // first Attempt
window.addEventListener('resize', SetNewCols);
document.querySelectorAll(".container button").forEach(Koll=>
{
Koll.onclick=e=>
{
let content = Koll.nextElementSibling;
content.style.maxHeight = Koll.classList.toggle("active")
? content.scrollHeight + "px"
: 0;
}
})
})();
.container {
display:flex;
justify-content:space-between;
flex-wrap:wrap;
}
.container .item {
width:400px;
}
.container .item > button {
background-color: #777;
color: white;
cursor: pointer;
padding: 18px;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 15px;
}
.container .item .content {
padding: 0 18px;
max-height: 0;
overflow: hidden;
transition: max-height 0.2s ease-out;
background-color: #f1f1f1;
}
<div class="container">
<div class="item">
<button>Open Section 1</button>
<div class="content">
<p>Section 1 details</p>
</div>
</div>
<div class="item">
<button>Open Section 2</button>
<div class="content">
<p>Section 2 details</p>
</div>
</div>
<div class="item">
<button>Open Section 3</button>
<div class="content">
<p>Section 3 details</p>
</div>
</div>
<div class="item">
<button>Open Section 4</button>
<div class="content">
<p>Section 4 details</p>
</div>
</div>
<div class="item">
<button>Open Section 5</button>
<div class="content">
<p>Section 5 details</p>
</div>
</div>
<div class="item">
<button>Open Section 6</button>
<div class="content">
<p>Section 6 details</p>
</div>
</div>
</div>
What you could do is to create a column-like structure, grouping all column elements into a div with class item:
<div class="container">
<div class="item">
<div class="item">
<button class="collapsible">Open Section 1</button>
<div class="content">
<p>Section 1 details</p>
</div>
</div>
<div class="item">
<button class="collapsible">Open Section 4</button>
<div class="content">
<p>Section 4 details</p>
</div>
</div>
</div>
...
</div>
I leave you the modified CodePen! Hope this is what you wanted to do!
In my approach, I'll use $(document).on('click', '.collapsible', function () {});
let $collBtn = '.item > .collapsible'; // all collapse
$(document).on('click', '.collapsible', function (e) {
e.preventDefault();
$($collBtn).removeClass('active');
$(this).addClass('active');
});
Once .collabsible has a class active it will expand the element like a togleClass(). Base on my experience when I have a multiple buttons and by using $(document).on('click', '.item', function () {}) It's equivalent of finding a class && buttons in the same elements. Hopefully, this can help. :)
#Vijay:- This is not an issue but a normal behaviour. When you click on any div, the other divs in the row are not expanding but the content is taking it's place and pushing other elements below that row to provide space for content to appear. You can only go with Position relative and absolute logic with the same HTML structure.
*{
box-sizing:border-box;
}
.item{
position:relative;
}
.content {
padding: 0 18px;
max-height: 0;
overflow: hidden;
transition: max-height 0.2s ease-out;
background-color: #f1f1f1;
position:absolute;
top:100%;
left:0;
right:0;
}
Related
As I was playing around trying to build a simple text carousel, I came across an issue I am having a difficult time understanding.
The layout is simple. I have a wrapper and the text I want the carousel to cycle through.
The issue I am having, however, seems as far as I can tell to be coming from the setInterval method. After the animation cycles through all the text and returns to the beginning, there is a strange overlap between the first and second text that is displayed. The first text will animate, but then will return to replace the second text temporarily.
Any help in understanding what is causing this error to render in this way would be greatly appreciated.
let animateSlide = setInterval(moveSlide, 1200);
function moveSlide() {
let carousel = document.getElementById("wordCarousel");
let firstSlide = carousel.children[0];
let createID = document.createAttribute("id");
createID.value = "active";
firstSlide.setAttributeNode(createID);
carousel.appendChild(carousel.firstChild);
carousel.children[carousel.children.length - 1].removeAttribute("id");
}
/* Carousel Styles */
#wordCarousel {
height: 36px;
overflow: hidden;
}
.carouselSlide {
color: #555;
font-size: 36px;
}
#active {
margin-top: 0px;
animation-name: example;
animation-duration: 1.2s;
animation-timing-function: ease;
}
#keyframes example {
from {
margin-top: 0px;
}
to {
margin-top: -40px;
}
}
<div id="wordCarousel">
<div class="carouselSlide">
Item 1
</div>
<div class="carouselSlide">
Item 2
</div>
<div class="carouselSlide">
Item 3
</div>
<div class="carouselSlide">
Item 4
</div>
</div>
Don't rely on setInterval when dealing with CSS animation. You will never have a perfect synchronization. Better consider events like animationiteration/animationend/animationstart
Here is a better idea will less of code an easier to handle.
let carousel = document.querySelector('#wordCarousel div');
carousel.addEventListener('animationiteration', () => {
carousel.appendChild(carousel.children[0]);
});
#wordCarousel {
height: 36px;
overflow: hidden;
}
.carouselSlide {
color: #555;
font-size: 36px;
line-height:100%; /* line-height will make sure the height is equal to 36px, font-size alone isn't enough */
}
#wordCarousel > div {
height:100%;
animation: example 1s infinite;
}
#keyframes example {
to {
transform: translateY(-100%);
}
}
<div id="wordCarousel">
<div>
<div class="carouselSlide">
Item 1
</div>
<div class="carouselSlide">
Item 2
</div>
<div class="carouselSlide">
Item 3
</div>
<div class="carouselSlide">
Item 4
</div>
</div>
</div>
I have coded a script (with the help of a user here) which allows me to expand a selected div and make the other divs behave accordingly by stretching equally to fit the remaining space (except the first one which width is fixed).
And here is a picture of what I want to achieve:
For that I use flex and transitions.
It works well, but the jQuery script specifies a "400%" stretch value (which is great for testing).
Now I would like the selected div to expand/shrink to exactly fit the content instead of the "400%" fixed value.
I have no idea how I could do that.
Is it possible ?
I tried to clone the div, fit it to the content, get its value and then use this value to transition BUT this means I have an initial width in percentages but a target value in pixels. That doesn't work.
And if I convert the pixel value in percentages, then the result doesn't exactly fit the content for whatever reason.
In all cases, this seems a bit of a complicated way to achieve what I want anyway.
Isn't there any flex property that could be transitioned in order to fit the content of a selected div?
Here is the code (edited/simplified since for a better read) :
var expanded = '';
$(document).on("click", ".div:not(:first-child)", function(e) {
var thisInd =$(this).index();
if(expanded != thisInd) {
//fit clicked fluid div to its content and reset the other fluid divs
$(this).css("width", "400%");
$('.div').not(':first').not(this).css("width", "100%");
expanded = thisInd;
} else {
//reset all fluid divs
$('.div').not(':first').css("width", "100%");
expanded = '';
}
});
.wrapper {
overflow: hidden;
width: 100%;
margin-top: 20px;
border: 1px solid black;
display: flex;
justify-content: flex-start;
}
.div {
overflow: hidden;
white-space: nowrap;
border-right: 1px solid black;
text-align:center;
}
.div:first-child {
min-width: 36px;
background: #999;
}
.div:not(:first-child) {
width: 100%;
transition: width 1s;
}
.div:not(:first-child) span {
background: #ddd;
}
.div:last-child {
border-right: 0px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Click on the div you want to fit/reset (except the first div)
<div class="wrapper">
<div class="div"><span>Fixed</span></div>
<div class="div"><span>Fluid (long long long long long text)</span></div>
<div class="div"><span>Fluid</span></div>
<div class="div"><span>Fluid</span></div>
</div>
Here is the jsfiddle:
https://jsfiddle.net/zajsLrxp/1/
EDIT: Here is my working solution with the help of you all (sizes updated on window resize + number of divs and first column's width dynamically calculated):
var tableWidth;
var expanded = '';
var fixedDivWidth = 0;
var flexPercentage = 100/($('.column').length-1);
$(document).ready(function() {
// Set width of first fixed column
$('.column:first-child .cell .fit').each(function() {
var tempFixedDivWidth = $(this)[0].getBoundingClientRect().width;
if( tempFixedDivWidth > fixedDivWidth ){fixedDivWidth = tempFixedDivWidth;}
});
$('.column:first-child' ).css('min-width',fixedDivWidth+'px')
//Reset all fluid columns
$('.column').not(':first').css('flex','1 1 '+flexPercentage+'%')
})
$(window).resize( function() {
//Reset all fluid columns
$('.column').not(':first').css('flex','1 1 '+flexPercentage+'%')
expanded = '';
})
$(document).on("click", ".column:not(:first-child)", function(e) {
var thisInd =$(this).index();
// if first click on a fluid column
if(expanded != thisInd)
{
var fitDivWidth=0;
// Set width of selected fluid column
$(this).find('.fit').each(function() {
var c = $(this)[0].getBoundingClientRect().width;
if( c > fitDivWidth ){fitDivWidth = c;}
});
tableWidth = $('.mainTable')[0].getBoundingClientRect().width;
$(this).css('flex','0 0 '+ 100/(tableWidth/fitDivWidth) +'%')
// Use remaining space equally for all other fluid column
$('.column').not(':first').not(this).css('flex','1 1 '+flexPercentage+'%')
expanded = thisInd;
}
// if second click on a fluid column
else
{
//Reset all fluid columns
$('.column').not(':first').css('flex','1 1 '+flexPercentage+'%')
expanded = '';
}
});
body{
font-family: 'Arial';
font-size: 12px;
padding: 20px;
}
.mainTable {
overflow: hidden;
width: 100%;
border: 1px solid black;
display: flex;
margin-top : 20px;
}
.cell{
height: 32px;
border-top: 1px solid black;
white-space: nowrap;
}
.cell:first-child{
background: #ccc;
border-top: none;
}
.column {
border-right: 1px solid black;
transition: flex 0.4s;
overflow: hidden;
line-height: 32px;
text-align: center;
}
.column:first-child {
background: #ccc;
}
.column:last-child {
border-right: 0px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span class="text">Click on the header div you want to fit/reset (except the first one which is fixed)</span>
<div class="mainTable">
<div class="column">
<div class="cell"><span class="fit">Propriété</span></div>
<div class="cell"><span class="fit">Artisan 45</span></div>
<div class="cell"><span class="fit">Waterloo 528</span></div>
</div>
<div class="column">
<div class="cell"><span class="fit">Adresse</span></div>
<div class="cell"><span class="fit">Rue du puit n° 45 (E2)</span></div>
<div class="cell"><span class="fit">Chaussée de Waterloo n° 528 (E1)</span></div>
</div>
<div class="column">
<div class="cell"><span class="fit">Commune</span></div>
<div class="cell"><span class="fit">Ixelles</span></div>
<div class="cell"><span class="fit">Watermael-Boitsfort</span></div>
</div>
<div class="column">
<div class="cell"><span class="fit">Ville</span></div>
<div class="cell"><span class="fit">Marche-en-Famenne</span></div>
<div class="cell"><span class="fit">Bruxelles</span></div>
</div>
<div class="column">
<div class="cell"><span class="fit">Surface</span></div>
<div class="cell"><span class="fit">120 m<sup>2</sup></span></div>
<div class="cell"><span class="fit">350 m<sup>2</sup></span></div>
</div>
</div>
And here is a fully fledged example at work (styles + padding + more data):
https://jsfiddle.net/zrqLowx0/2/
Thank you all !
It is possible to solve it using max-width and calc().
First, replace width: 100% with flex: 1 for the divs in CSS, so they will grow, which is better in this case. In addition, use transition for max-width.
Now, we have to store some relevant values:
The amount of divs that will be animated (divsLength variable) - 3 in this case.
The total width used for the fixed div and the borders (extraSpace variable) - 39px in this case.
With those 2 variables, we can set a default max-width (defaultMaxWidth variable) to all the divs, as well as using them later. That is why they are being stored globally.
The defaultMaxWidth is calc((100% - extraSpace)/divsLength).
Now, let's enter the click function:
To expand the div, the width of the target text will be stored in a variable called textWidth and it will be applied to the div as max-width. It uses .getBoundingClientRect().width (since it return the floating-point value).
For the remaining divs, it is created a calc() for max-width that will be applied to them.
It is: calc(100% - textWidth - extraScape)/(divsLength - 1).
The calculated result is the width that each remaining div should be.
When clicking on the expanded div, that is, to return to normal, the default max-width is applied again to all .div elements.
var expanded = false,
divs = $(".div:not(:first-child)"),
divsLength = divs.length,
extraSpace = 39, //fixed width + border-right widths
defaultMaxWidth = "calc((100% - " + extraSpace + "px)/" + divsLength + ")";
divs.css("max-width", defaultMaxWidth);
$(document).on("click", ".div:not(:first-child)", function (e) {
var thisInd = $(this).index();
if (expanded !== thisInd) {
var textWidth = $(this).find('span')[0].getBoundingClientRect().width;
var restWidth = "calc((100% - " + textWidth + "px - " + extraSpace + "px)/" + (divsLength - 1) + ")";
//fit clicked fluid div to its content and reset the other fluid divs
$(this).css({ "max-width": textWidth });
$('.div').not(':first').not(this).css({ "max-width": restWidth });
expanded = thisInd;
} else {
//reset all fluid divs
$('.div').not(':first').css("max-width", defaultMaxWidth);
expanded = false;
}
});
.wrapper {
overflow: hidden;
width: 100%;
margin-top: 20px;
border: 1px solid black;
display: flex;
justify-content: flex-start;
}
.div {
overflow: hidden;
white-space: nowrap;
border-right: 1px solid black;
text-align:center;
}
.div:first-child {
min-width: 36px;
background: #999;
}
.div:not(:first-child) {
flex: 1;
transition: max-width 1s;
}
.div:not(:first-child) span {
background: #ddd;
}
.div:last-child {
border-right: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
Click on the div you want to fit/reset (except the first div)
<div class="wrapper">
<div class="div"><span>Fixed</span></div>
<div class="div"><span>Fluid (long long long long text)</span></div>
<div class="div"><span>Fluid</span></div>
<div class="div"><span>Fluid</span></div>
</div>
This approach behaves dynamically and should work on any resolution.
The only value you need to hard code is the extraSpace variable.
You need to deal with the width or calc functions. Flexbox would have a solution.
To make all divs equal (not first one) we use flex: 1 1 auto.
<div class="wrapper">
<div class="div"><span>Fixed</span></div>
<div class="div"><span>Fluid (long long long long text)</span></div>
<div class="div"><span>Fluid</span></div>
<div class="div"><span>Fluid</span></div>
</div>
Define flex rules for your normal div and selected div. transition: flex 1s; is your friend. For selected one we don't need flex grow so we use flex: 0 0 auto;
.wrapper {
width: 100%;
margin-top: 20px;
border: 1px solid black;
display: flex;
}
.div {
white-space: nowrap;
border-right: 1px solid black;
transition: flex 1s;
flex: 1 1 auto;
}
.div.selected{
flex: 0 0 auto;
}
.div:first-child {
min-width: 50px;
background: #999;
text-align: center;
}
.div:not(:first-child) {
text-align: center;
}
.div:last-child {
border-right: 0px;
}
div:not(:first-child) span {
background: #ddd;
}
Add selected class each time when the user clicks a div. You can also use toggle for the second click so you can save selected items in a map and you can show multiple selected items (not with this code example of course).
$(document).on("click", ".div:not(:first-child)", function(e) {
const expanded = $('.selected');
$(this).addClass("selected");
if (expanded) {
expanded.removeClass("selected");
}
});
https://jsfiddle.net/f3ao8xcj/
After a few trial versions, this seems to be my shortest and most straighforward solution.
All that essentially needs to be done is have Flexbox stretch the <div> elements to their limits by default, but when <span> clicked, constraint the stretch of the <div> to <span> width ...
pseudo code:
when <span> clicked and already toggled then <div> max-width = 100%, reset <span> toggle state
otherwise <div> max-width = <span> width, set <span> toggle state
I have split the CSS into a 'relevant mechanism' and 'eye-candy only' section for easy reading (and code recyling).
The code is heavily commented, so not much text here...
Quirk Somehow there is an extra delay in the transition when switching the div from max-width: 100% to max-width = span width. I've checked this behaviour in Chrome, Edge, IE11 and Firefox (all W10) and all seem to have this quirk. Either some browser internal recalc going on, or maybe the transition time is used twice ('feels like'). Vice Versa, oddly enough, there is no extra delay.
However, with a short transition time (e.g. 150ms, as I am using now) this extra delay is not/hardly noticable. (Nice one for another SO question...)
$(document).on('click', '.wrapper>:not(.caption) span', function (e) {
// Save the current 'toggle' status
var elemToggled = e.target.getAttribute('toggled');
// Set parent max-width to maximum space or constraint to current child width
e.target.parentElement.style.maxWidth =
(elemToggled=="true") ? '100%' : parseFloat(window.getComputedStyle(e.target).width) + 'px';
// (Re)set child toggle state
e.target.setAttribute('toggled', (elemToggled=="true") ? false : true);
});
/*********************/
/* Wrapper mechanism */
/*********************/
.wrapper { /* main flexible parent container */
display : flex; /* [MANDATORY] Flexbox Layout container, can't FBL without */
flex-wrap: nowrap; /* [MANDATORY] default FBL, but important. wrap to next line messes things up */
flex-grow: 1; /* [OPTIONAL] Either: if '.wrapper' is a FBL child itself, allow it to grow */
width : 100%; /* [OPTIONAL] or : full parent width */
/* (Maybe a fixed value, otherwise redundant here as 'flex-grow' = 1) */
}
/* generic rule */
.wrapper>* { /* flexed child containers, being flexible parent containers themselves */
flex-grow : 1; /* [MANDATORY] important for this mechanism to work */
overflow: hidden; /* [MANDATORY] important, otherwise output looks messy */
display: flex; /* [MANDATORY] for FBL stretching */
justify-content: center;/* [MANDATORY] as per SOQ */
max-width : 100%; /* [OPTIONAL/MANDATORY], actually needed to trigger 'transition' */
}
/* exception to the rule */
.wrapper>.fixed { /* fixed child container */
flex-grow: 0; /* [MANDATORY] as per SOQ, don't allow grow */
}
/******************/
/* Eye-candy only */
/******************/
.wrapper {
border: 1px solid black;
}
.wrapper>:not(.fixed) {
transition: max-width 150ms ease-in-out;
}
.wrapper>:not(:last-child){
border-right: 1px solid black;
}
/* generic rule */
.wrapper>*>span {
white-space: nowrap;
background-color: #ddd;
}
/* exception to the rule */
.wrapper>.fixed>span {
background-color: #999;
}
/* debug helper: show all elements with outlines (put in <body>) */
[debug="1"] * { outline: 1px dashed purple }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="wrapper">
<div class="fixed"><span>Fixed</span></div>
<div><span>Fluid (long long long long long text)</span></div>
<div><span>Fluid</span></div>
<div><span>Fluid</span></div>
</div>
UPDATE
New version that resets all other <div>. I truly hate the jumpiness, but that is due to Flexbox stretching and the transition value. Without transition no jumps visible. You need to try out what works for you.
I only added document.querySelectorAll() to the javascript code.
$(document).on('click', '.wrapper>:not(.caption) span', function (e) {
var elemToggled = e.target.getAttribute('toggled'); // Toggle status
var elemWidth = parseFloat(window.getComputedStyle(e.target).width); // Current element width
// reset ALL toggles but 'this'...
document.querySelectorAll('.wrapper>:not(.caption) span')
.forEach( function (elem,idx) {
if (elem != this){
elem.parentElement.style.maxWidth = '100%';
elem.setAttribute('toggled',false);
};
});
// Set parent max-width to maximum space or constraint to current child width
e.target.parentElement.style.maxWidth =
(elemToggled=="true") ? '100%' : parseFloat(window.getComputedStyle(e.target).width) + 'px';
// (Re)set child toggle state
e.target.setAttribute('toggled', (elemToggled=="true") ? false : true);
});
/*********************/
/* Wrapper mechanism */
/*********************/
.wrapper { /* main flexible parent container */
display : flex; /* [MANDATORY] Flexbox Layout container, can't FBL without */
flex-wrap: nowrap; /* [MANDATORY] default FBL, but important. wrap to next line messes things up */
flex-grow: 1; /* [OPTIONAL] Either: if '.wrapper' is a FBL child itself, allow it to grow */
width : 100%; /* [OPTIONAL] or : full parent width */
/* (Maybe a fixed value, otherwise redundant here as 'flex-grow' = 1) */
}
/* generic rule */
.wrapper>* { /* flexed child containers, being flexible parent containers themselves */
flex-grow : 1; /* [MANDATORY] important for this mechanism to work */
overflow: hidden; /* [MANDATORY] important, otherwise output looks messy */
display: flex; /* [MANDATORY] for FBL stretching */
justify-content: center;/* [MANDATORY] as per SOQ */
max-width : 100%; /* [OPTIONAL/MANDATORY], actually needed to trigger 'transition' */
}
/* exception to the rule */
.wrapper>.fixed { /* fixed child container */
flex-grow: 0; /* [MANDATORY] as per SOQ, don't allow grow */
}
/******************/
/* Eye-candy only */
/******************/
.wrapper {
border: 1px solid black;
}
.wrapper>:not(.fixed) {
transition: max-width 150ms ease-in-out;
}
.wrapper>:not(:last-child){
border-right: 1px solid black;
}
/* generic rule */
.wrapper>*>span {
white-space: nowrap;
background-color: #ddd;
}
/* exception to the rule */
.wrapper>.fixed>span {
background-color: #999;
}
/* show all elements with outlines (put in <body>) */
[debug="1"] * { outline: 1px dashed purple }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="wrapper">
<div class="fixed"><span>Fixed</span></div>
<div><span>Fluid (long long long long long text)</span></div>
<div><span>Fluid</span></div>
<div><span>Fluid</span></div>
</div>
If you need only one row, there is a simpler solution based on this code : https://jsfiddle.net/jpeter06/a5cu52oy/
with the css flex modified for columns instead of rows :
.container {
flex-grow: 10;
flex-shrink: 0;
flex-basis: auto;
display: flex;
flex-direction: row;
width: 100%;
}
.item { min-width:30px;
flex-basis:30px;
overflow-x:hidden;
transition: flex-basis 500ms ease-in-out;
}
.expanded {
flex-basis: 20em;
}
html, body {
width: 100%; height: 100%;
margin: 0; padding: 0; border: 0; overflow: hidden;
}
html code :
<div class="container">
<div class="item" style="background: red">a<br/>a<br/>a<br/>a<br/>a<br/>a<br/>a<br/>a<br/></div>
<div class="item" style="background: green">b<br/>b<br/>b<br/>b</div>
<div class="item" style="background: blue">c<br/>c<br/>c<br/>c</div>
</div>
JS code :
$(document).ready(function() {
$(".item").click(function() {
$(this).addClass('expanded');
$(".item").not(this).each(function() {
$(this).removeClass("expanded");
});
});
});
I'm creating a grid of 1:1 squares. The user can keep adding squares and I want the size of the squares to be maintained at their aspect ratio but resize accordingly. The tricky part is I want the square to always be visible on the page - that is to say that there is no scrolling and the webpage would be responsive with width and height.
I have created an example that adds a square every second while testing this. However, I am unable to get it working with the height part. I have been able to get it working with the width.
setInterval(() => {
// console.log(document.getElementsByClassName('square-container')[0]);
document.getElementsByClassName('square-container')[0].innerHTML += ("<div class='square'></div>");
}, 1000);
.square-container {
display: flex;
flex-wrap: wrap;
}
.square {
position: relative;
flex-basis: calc(33.333% - 10px);
margin: 5px;
box-sizing: border-box;
background: red;
transition: background 1s;
}
.square::before {
content: "";
display: block;
padding-top: 100%;
}
<div class="square-container">
<div class="square"></div>
</div>
I'm not using any ui libraries like bootstrap, just vanilla html, css and javascript.
Use Float instead of wrap. set the square-container display block.
<div>
<div id="square-container">
<div id="square"></div>
<div id="square"></div>
<div id="square"></div>
<div id="square"></div>
<div id="square"></div>
<div id="square"></div>
<div id="square"></div>
<div id="square"></div>
</div>
</div>
#square-container{
display: block
}
#square{
float: left;
height: 100px;
width: 100px;
background-color: orangered;
margin: 1px;
}
Might I suggest a pure javascript approach?
Basically just set a inicial value and let javascript do all the calculating every time a square is added.
// Function to resize the squares
function resizeSquares(){
// Get the squares
squares = document.getElementsByClassName('square');
for (i=0;i<squares.length;i++) {
// Set the width of the square according to the window width
squares[i].style.width = document.body.clientWidth / squarePerRow + 'px';
// Set the height of the square to its width to keep the aspect ratio
squares[i].style.height = squares[i].style.width;
}
}
// Set initial square capacity for each row
squarePerRow = 3;
// Inicialize the size of the squares
resizeSquares();
setInterval(function(){
// Add a square
document.getElementById('container').innerHTML += '<div class="square"></div>';
// Check if squares exceeds the window
if(document.body.clientHeight > document.documentElement.clientHeight) {
// If they do, add one square capacity per row
squarePerRow = squarePerRow + 1;
}
// Resize the squares
resizeSquares()
}, 1000)
#container {
display: flex;
flex-wrap: wrap;
}
.square {
background: red;
border: 5px solid white;
box-sizing: border-box;
transition: all, 0.5s;
}
<div id="container">
<div class="square"></div>
</div>
My apologies if the subject is a bit non-descriptive. I'm having a hard time trying to explain what I'm trying to achieve in a one-liner.
But in a few sentences: I'm trying to have an element, a DIV in this case, move smoothly to its new position. But the caveat is that I'm not setting its position manually. It receives a new position because I'm removing other DIVs from the page flow.
<!DOCTYPE html>
<html>
<head>
<style>
.block {
width: 200px;
height: 20px;
border: 1px solid black;
margin: 20px;
}
</style>
<script>
function removeBlock() {
document.getElementById("block2").style.display = "none";
}
</script>
</head>
<body>
<div id="block1" class="block">
This is block 1
</div>
<div id="block2" class="block">
This is block 2
</div>
<div id="block3" class="block">
This is block 3
</div>
<button type="button" onclick="removeBlock();">
Remove block 2
</button>
</body>
</html>
Here's the fiddle: https://jsfiddle.net/nfhycrkL/
If you click the button, Block 2 is hidden and Block 3 moves up. I want this move to be smooth. Is this at all possible? I don't want to use absolute positioning since the page is responsive and the position of the DIVs are depending on the page size.
Try This Solution
function removeBlock()
{
document.getElementById("block2").style.height = "0px";
document.getElementById("block2").style.margin = "0px";
document.getElementById("block2").style.borderWidth = "0px";
document.getElementById("block2").style.fontSize = "0px";
}
.block {
width: 200px;
height: 20px;
border: 1px solid black;
margin: 20px;
}
#block2 {
transition:all 0.5s linear;
}
<div id="block1" class="block">
This is block 1
</div>
<div id="block2" class="block">
This is block 2
</div>
<div id="block3" class="block">
This is block 3
</div>
<button type="button" onclick="removeBlock();">
Remove block 2
</button>
You could add a new class to an element with javascript that you want to hide and do css transition.
Here's a small example with remove and toggle options https://jsfiddle.net/nfhycrkL/9/
html:
<div id="block1" class="block">
This is block 1
</div>
<div id="block2" class="block">
This is block 2
</div>
<div id="block3" class="block">
This is block 3
</div>
<button type="button" onclick="toggleBlock();">
Toggle block 2
</button>
<button type="button" onclick="removeBlock();">
Remove block 2
</button>
js :
function toggleBlock() {
document.getElementById("block2").classList.toggle('block-hidden')
}
function removeBlock() {
document.getElementById("block2").classList.add('block-hidden')
}
css:
.block {
width: 200px;
height: 20px;
border: 1px solid black;
margin: 0 20px 20px;
overflow:hidden;
transition: all .25s;
}
.block-hidden {
height: 0px;
margin: 0px;
border: none;
}
$(document).ready(function(){
$("button").click(function(){
//document.getElementById("block2").style.display = "none";
$("#block2").fadeOut(1000);
});
});
.block {
width: 200px;
height: 20px;
border: 1px solid black;
margin: 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.css" rel="stylesheet"/>
<div id="block1" class="block">
This is block 1
</div>
<div id="block2" class="block">
This is block 2
</div>
<div id="block3" class="block">
This is block 3
</div>
<button type="button">
Remove block 2
</button>
use Jquery effects. I hope this helps.
Here's a simple example in vanillaJS with a CSS transition
Jsfiddle demo
Update your style adding a transition for the .block element
CSS
.block {
width: 200px;
height: 20px;
border: 1px solid black;
margin: 20px 20px 0;
max-height: 500px;
transition: opacity 0s 0s, margin .25s 0s, max-height .25s 0s;
}
.removedBlock {
box-sizing: border-box;
opacity: 0;
margin: 0;
max-height: 0;
}
so that the function can trigger a max-height animation by adding the removedBlock class
<div id="block1" class="block">
This is block 1
</div>
<div id="block2" class="block">
This is block 2
</div>
<div id="block3" class="block">
This is block 3
</div>
<button type="button" onclick="removeBlock('block2');">
Remove block 2
</button>
JS
function removeBlock(id) {
var block = document.getElementById(id);
block.classList.add('removedBlock');
}
When you do a removal, the element disappears due the opacity set to 0, then margin and max-height will make the block collapsing.
Note that since a transition can't be triggered to/from an auto value I've set a huge starting max-height for this purpose. If you want to see a smoother transition either change that property with a lower value or simply increase the duration of the transition.
A more refined version could instead get the height of the element before applying the transition e.g.
function removeBlock(id) {
var block = document.getElementById(id);
var blockHeight = block.offsetHeight;
block.style.height = blockHeight + 'px';
block.classList.add('removedBlock');
}
so the style becomes
.block {
width: 200px;
height: 20px;
border: 1px solid black;
margin: 20px 20px 0;
transition: opacity 0s 0s, margin .5s 0s, height .5s 0s;
}
.removedBlock {
box-sizing: border-box;
opacity: 0;
margin: 0;
height: 0 !important;
}
JsFiddle
Thanks everybody for your answers! And although all of them work somewhat, they do not work as soon as the layout becomes more complex, or if you try to hide/show more/other objects.
So I spend the past few hours creating a Javascript solution that I think will work in any situation (and on any browser too).
In short, how it works is that you "mark" as many elements as you like to be hidden/shown with the SetDisplay() function (see the first button). Once that has been done, you call the same SetDisplay function without any parameters and see the magic happen! The Javascript actually quickly removes the elements and let the page reflow (all invisible to the viewer). It then examines the new positions, reinserts the elements to hide and move all other elements to their new position by setting style.transition and by using position:relative and new top and left values. Once it's done with the transition, it hides the elements permanently, resets all changed style values and let the page reflow again.
SetDisplay( "block2", "none" );
SetDisplay( "block3", "none" );
SetDisplay( "block4", "none" );
SetDisplay();
You can reinsert elements the same way (the second button).
SetDisplay( "block2", "" );
SetDisplay();
https://jsfiddle.net/yq7xor5j/3/
(Edit: made a change to the fiddle to correct a small bug)
Working on creating functionality where when the user clicks on one of the products (each of the elements have the same assigned ID card-reveal) it adds a CSS class to the container specifically clicked (active state) to show information for that specific item and then finally, when the user clicks the cancel button the CSS class is removed (activate state gone).
Unfortunately I have run to a few hiccups where when I click on the 1st element it adds the class to that element but the other elements I click do not add the class, as well the close button does not function at all. I would like to finish the solution in Pure Javascript. Also if you see a few classie() methods, I am using Classie.js to help with CSS class toggling.
Any help will be appreciated! Thank You!
Html
<a id="card-reveal" class="card-view" href="javascript:void(0)"><h3 class='hover-title'>View More</h3></a>
<div class="card-cover">
<span class="card-exit"></span>
<a class="card-follow" href="javascript:void(0)">Follow {{object.product_website_name}}.com</a>
<a class="card-buy" target="_blank" href="{{object.product_slug_url}}">Buy {{object.product_name }}</a>
<a id="card-close" class="card-info" href="javascript:void(0)"><span class="icon-indie_web-03"></span></a>
<ul class="card-social">
<label>Share</label>
<li><span class="icon-indie_web-04"></span></li>
<li><span class="icon-indie_web-05"></span></li>
</ul>
</div>
CSS
.card-cover {
width:100%;
height: 100%;
background: none repeat scroll 0% 0% rgba(255, 91, 36, 0.9);
color: #FFF;
display: block;
position: absolute;
opacity: 0;
z-index:200;
overflow: hidden;
-webkit-transform:translate3d(0, 400px, 0);
transform:translate3d(0, 400px, 0);
-webkit-backface-visibility:hidden;
backface-visibility: hidden;
-webkit-transition-property:opacity, transform;
transition-property:opacity, transform;
-webkit-transition-duration:0.2s;
transition-duration:0.2s;
-webkit-transition-timing-function:cubic-bezier(0.165, 0.84, 0.44, 1);
transition-timing-function:cubic-bezier(0.165, 0.84, 0.44, 1);
-webkit-transition-delay: 0s;
transition-delay: 0s;
}
.card-cover.card--active {
opacity: 1;
-webkit-transform:translate3d(0, 0, 0);
transform:translate3d(0, 0px, 0);
}
JS below:
var cardContainer = document.querySelector('.card-cover'),
cardTargets = Array.prototype.slice.call( document.querySelectorAll( '#card-reveal' ) ),
eventType = mobilecheck() ? 'touchstart' : 'click',
cardClose = document.getElementById('card-close'),
resetMenu = function() {
classie.remove( cardContainer, 'card--active' );
},
resetMenuClick = function( ) {
cardCloseaddEventListener(globalMenuEventType, function() {
resetMenu();
document.removeEventListener(eventType, resetMenuClick);
}, false);
};
cardTargets.forEach(function (element, index) {
if( element.target ) {
element.addEventListener(eventType, function( event ) {
event.preventDefault();
event.stopPropagation();
classie.add(cardContainer, 'card--active');
document.addEventListener(eventType, resetMenuClick);
} ,false);
}
});
There are two simple ways I can think of doing something like this.
First, if you can't designate ID's for each card (which it sounds like you can't), you're going to have to go by class names. Like it was mentioned in the comments, you really don't want to use the same ID for multiple elements.
Part of the reason for this is, as you can see from my examples below, that the .getElementById() method is only meant to return one element, where the other methods like .getElementsByClassName() will return an array of elements.
The problem we're trying to solve is that the sub-content you want to display/hide has to be attached to the element you click somehow. Since we're not using ID's and you can't really rely on class names to be unique between elements, I'm putting the div with the information inside a container with the element that toggles it.
Inside a container div, are two divs for content. One is the main content that's always visible, the other is the sub-content that only becomes visible if the main content is clicked (and becomes invisible when clicked again).
The benefit of this method is that since there are no ID's to worry about, you can copy/paste the cards and they'll each show the same behaviour.
var maincontent = document.getElementsByClassName("main-content");
// Note: getElemenstByClassName will return an array of elements (even if there's only one).
for (var i = 0; i < maincontent.length; i++) {
//For each element in the maincontent array, add an onclick event.
maincontent[i].onclick = function(event) {
//What this does is gets the first item, from an array of elements that have the class 'sub-content', from the parent node of the element that was clicked:
var info = event.target.parentNode.getElementsByClassName("sub-content")[0];
if (info.className.indexOf("show") > -1) { // If the 'sub-content' also contains the class 'show', remove the class.
info.className = info.className.replace(/(?:^|\s)show(?!\S)/g, '');
} else { // Otherwise add the class.
info.className = info.className + " show";
}
}
}
.container {
border: 1px solid black;
width: 200px;
margin: 5px;
}
.main-content {
margin: 5px;
cursor: pointer;
}
.sub-content {
display: none;
margin: 5px;
}
.show {
/* The class to toggle */
display: block;
background: #ccc;
}
<div class="container">
<div class="main-content">Here is the main content that's always visible.</div>
<div class="sub-content">Here is the sub content that's only visible when the main content is clicked.</div>
</div>
<div class="container">
<div class="main-content">Here is the main content that's always visible.</div>
<div class="sub-content">Here is the sub content that's only visible when the main content is clicked.</div>
</div>
<div class="container">
<div class="main-content">Here is the main content that's always visible.</div>
<div class="sub-content">Here is the sub content that's only visible when the main content is clicked.</div>
</div>
The second method, would be to use one div for the content that you want to show/hide, and clicking on an element will toggle both its visibility and it's content.
I'll use the previous example as a base, but ideally you would have some kind of MVVM framework like react, knockout, or angular to help you with filling in the content. For the sake of this example, I'm just going to use the text from the div of sub-content.
var info = document.getElementById("Info");
var maincontent = document.getElementsByClassName("main-content");
for (var i = 0; i < maincontent.length; i++) { //getElemenstByClassName will return an array of elements (even if there's only one).
maincontent[i].onclick = function(event) { //For each element in the maincontent array, add an onclick event.
//This does the same as before, but I'm getting the text to insert into the info card.
var text = event.target.parentNode.getElementsByClassName("sub-content")[0].innerHTML;
info.innerHTML = text; // Set the text of the info card.
info.style.display = "block"; //Make the info card visible.
}
}
info.onclick = function(event) {
info.style.display = "none"; // If the info card is ever clicked, hide it.
}
.container {
border: 1px solid black;
width: 200px;
margin: 5px;
padding: 5px;
}
.main-content {
margin: 5px;
cursor: pointer;
}
.sub-content {
display: none;
margin: 5px;
}
#Info {
cursor: pointer;
display: none;
}
<div id="Info" class="container">Here is some test information.</div>
<div class="container">
<div class="main-content">Link 1.</div>
<div class="sub-content">You clicked link 1.</div>
</div>
<div class="container">
<div class="main-content">Link 2.</div>
<div class="sub-content">You clicked link 2.</div>
</div>
<div class="container">
<div class="main-content">Link 3.</div>
<div class="sub-content">You clicked link 3.</div>
</div>