Гибкий переход: растягивание (или сжатие) по размеру содержимого - PullRequest
9 голосов
/ 24 февраля 2020

Я кодировал скрипт (с помощью пользователя здесь), который позволяет мне расширять выбранный div и заставить другие div вести себя соответствующим образом, растягивая их в равной степени, чтобы соответствовать оставшемуся пространству (кроме первого, ширина которого фиксирована) .

А вот картинка того, чего я хочу достичь:

enter image description here

Для этого я использую flex и переходы.

Работает хорошо, но сценарий jQuery задает значение растяжения "400%" (что отлично подходит для тестирования).

Теперь я бы хотел, чтобы выбранный div расширялся / сжимался до точного соответствия содержимое вместо фиксированного значения «400%».

Понятия не имею, как мне это сделать.

Возможно ли это?

Я пытался клонировать div Приспособьте его к содержанию, получите его значение и затем используйте это значение для перехода, НО это означает, что у меня есть начальная ширина в процентах, но целевое значение в пикселях. Это не сработает.

И если я преобразую значение пикселя в процентах, то результат не совсем соответствует содержанию по какой-либо причине.

Во всех случаях это кажется немного сложного способа добиться того, чего я хочу в любом случае.

Нет ли какого-либо свойства flex, которое можно было бы переместить, чтобы соответствовать содержимому выбранного div?

Вот код ( отредактировано / упрощено, поскольку для лучшего чтения ):

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>

Вот jsfiddle:

https://jsfiddle.net/zajsLrxp/1/

РЕДАКТИРОВАТЬ: Вот мое рабочее решение с помощью всех вас (размеры обновляются при изменении размера окна + количество делений и ширина первого столбца динамически рассчитывается):

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>

А вот и полноценный пример на работе (стили + заполнение + дополнительные данные):

https://jsfiddle.net/zrqLowx0/2/

Спасибо всем!

Ответы [ 3 ]

4 голосов
/ 24 февраля 2020

Это можно решить, используя max-width и calc().

Сначала замените width: 100% на flex: 1 для делений в CSS, чтобы они росли, что лучше в этом случае. Кроме того, используйте переход для max-width.

. Теперь мы должны сохранить некоторые соответствующие значения:

  • Количество div, которые будут анимированы (переменная divsLength) - В данном случае 3.
  • Общая ширина, используемая для фиксированного div и границ (переменная extraSpace) - в данном случае - 39px.

С этими двумя переменными мы можем установите значение max-width (переменная defaultMaxWidth) по умолчанию для всех элементов div, а также используйте их позже. Вот почему они хранятся по всему миру.

defaultMaxWidth равно calc((100% - extraSpace)/divsLength).

Теперь давайте введем функцию щелчка:

Чтобы расширить div, ширина целевого текста будет сохранена в переменной с именем textWidth и будет применена к div как max-width.. Используется .getBoundingClientRect().width (так как он возвращает значение с плавающей запятой).

Для остальных элементов div создается calc() для max-width, которые будут применены к ним.
Это: calc(100% - textWidth - extraScape)/(divsLength - 1).
Расчетный результат - это ширина каждого оставшегося элемента. div должен быть.

При нажатии на расширенный div, то есть для возврата к нормальному состоянию, значение по умолчанию max-width применяется снова ко всем .div элементам.

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>

Этот подход работает динамически и должен работать при любом разрешении.
Единственное значение, которое вам нужно для жесткого кода, - это переменная extraSpace.

3 голосов
/ 26 февраля 2020

Вам нужно разобраться с функциями ширины или калибровки c. У Flexbox было бы решение.

Чтобы сделать все деления равными (не первым), мы используем 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>

Определите правила flex для вашего обычного div и выбранного div. transition: flex 1s; твой друг. Для выбранного нам не нужно Flex Grow, поэтому мы используем 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;
}

Добавлять выбранный класс каждый раз, когда пользователь нажимает div. Вы также можете использовать переключатель для второго щелчка, чтобы сохранить выбранные элементы на карте и показать несколько выбранных элементов (конечно, не с этим примером кода).

$(document).on("click", ".div:not(:first-child)", function(e) {
  const expanded = $('.selected');
  $(this).addClass("selected");
  if (expanded) {
    expanded.removeClass("selected");
  }
});

https://jsfiddle.net/f3ao8xcj/

2 голосов
/ 27 февраля 2020

После нескольких пробных версий это, похоже, мое самое короткое и простое решение.

Все, что по сути нужно сделать, - это по умолчанию Flexbox растянуть элементы <div> до их пределов, но когда <span> нажатие, ограничение длины <div> до <span> ширина ...

псевдокод:

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

Я разбил CSS на разделы «соответствующий механизм» и «только для глаз» для удобства чтения (и повторного использования кода).

Код сильно прокомментировано, поэтому здесь не так много текста ...

Причудка Каким-то образом возникает дополнительная задержка в transition при переключении div с max-width: 100% на max-width = span width. Я проверил это поведение в Chrome, Edge, IE11 и Firefox (все W10), и все, кажется, имеют эту причуду. Либо происходит какое-то внутреннее восстановление в браузере c, либо, может быть, время transition используется дважды («по ощущениям»). Наоборот, как ни странно, никакой дополнительной задержки нет.

Однако при коротком времени перехода (например, 150 мс, как я сейчас использую) эта дополнительная задержка не / почти не заметна. (Хороший вопрос к другому, ТАК вопрос ...)

$(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>

ОБНОВЛЕНИЕ

Новая версия, которая сбрасывает все остальные <div>. Я действительно ненавижу скачок, но это из-за растяжения Flexbox и значения transition. Без transition скачков не видно. Вам нужно попробовать то, что вам подходит.

Я только добавил document.querySelectorAll() к коду javascript.

$(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>
...