简体   繁体   中英

Flex transition: Stretch (or shrink) to fit content

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 (eg 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");    
    });
  });
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM