Elastyczne przejście: Rozciągnij (lub zmniejsz), aby dopasować zawartość

9

Zakodowałem skrypt (z pomocą tutaj użytkownika), który pozwala mi rozwinąć wybrany div i sprawić, aby inne div zachowywały się odpowiednio, rozciągając się równo, aby dopasować się do pozostałej przestrzeni (z wyjątkiem pierwszego, którego szerokość jest stała).

A oto zdjęcie tego, co chcę osiągnąć:

wprowadź opis zdjęcia tutaj

Do tego używam flexu i przejść.

Działa dobrze, ale skrypt jQuery określa „400%” wartość rozciągnięcia (co jest świetne do testowania).

Teraz chciałbym, aby wybrany div rozszerzył / zmniejszył, aby dokładnie pasował do zawartości zamiast stałej wartości „400%”.

Nie mam pojęcia, jak to zrobić.

Czy to możliwe ?

Próbowałem sklonować div, dopasować go do treści, uzyskać jego wartość, a następnie użyć tej wartości do przejścia, ALE oznacza to, że mam początkową szerokość w procentach, ale wartość docelową w pikselach. To nie działa.

A jeśli przekonwertuję wartość piksela w procentach, wynik nie pasuje dokładnie do treści z jakiegokolwiek powodu.

We wszystkich przypadkach i tak wydaje się to nieco skomplikowanym sposobem na osiągnięcie tego, czego chcę.

Czy nie ma żadnej właściwości flex, którą można by przenieść, aby dopasować ją do zawartości wybranego div?

Oto kod ( edytowany / uproszczony, ponieważ dla lepszego odczytu ):

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>

Oto jsfiddle:

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

EDYCJA: Oto moje działające rozwiązanie z pomocą was wszystkich (rozmiary aktualizowane przy zmianie rozmiaru okna + liczba div i szerokość pierwszej kolumny obliczana dynamicznie):

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>

A oto pełny przykład pracy (style + padding + więcej danych):

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

Dziękuję wam wszystkim !

Bachir Messaouri
źródło
1
Bądź uprzejmy, aby dodać końcowy wynik do pytania pokazujący, co stworzyłeś po tym wszystkim. Naprawdę interesuje mnie to, jak to wygląda. Dzięki! (... może jakiś wgląd w to, do czego służy? ...)
Rene van der Lende
Ok, zrobię to, jak tylko wyczyszczę skrypt ze wszystkich niepotrzebnych elementów (chyba za kilka minut). To powiedziawszy, jak to zrobić? Czy po prostu edytuję oryginalny post i dodam swoje zdanie na ten temat, mimo że odpowiedź Azametzina jest w porządku?
Zmieniam
Chodzi o to: emuluję rodzaj arkusza kalkulacyjnego programu Excel i chcę, aby ludzie mogli klikać określoną kolumnę w celu dopasowania do treści. Zwykle robiłaby to tabela, ale kolumny nie zachowują się poprawnie, jeśli chodzi o zmianę ich rozmiaru tak, jak chcę (łatwe rozwijanie jednej kolumny na raz, ale trudno jest sprawić, aby pozostałe zachowywały się w czasie rzeczywistym, aby zajmować pozostałą przestrzeń flex zrobiłby dla div. Po prostu rozwijają się nieregularnie. Przeprowadziłem badania i to, czego chcę, jest niemożliwe przy tabelach). Są inne powody, dla których nie używam tabeli ani dataTable, ale wykracza to poza zakres tego postu.
Bachir Messaouri
1
Łatwo, po prostu wciśnij „edytuj”, przejdź do końca pytania i zacznij dodawać prozaiczne przemyślenia, w tym nowy fragment (całość). Pamiętaj, aby domyślnie „ukryć fragment” (pole wyboru u dołu po lewej), mniej natrętny / rozpraszający dla „nowych czytelników”. Moją pierwszą myślą było to, że użyjesz go do stworzenia fantazyjnego widżetu, ikon i wszystkich podobnych do „harmonijki”. Arkusz kalkulacyjny huh, mam jakiś pełny ekran, rozciągam się wszędzie w górę / w dół / w lewo / w prawo, układając się według tych samych zasad omówionych tutaj (jeśli jesteś zainteresowany).
Rene van der Lende
2
Świetnie, że zrobiłeś o wiele więcej, publikując wynik końcowy. Sprawia, że ​​odpowiadanie na pytania dotyczące SO jest o wiele bardziej satysfakcjonujące! Cudos ...
Rene van der Lende

Odpowiedzi:

4

Można to rozwiązać za pomocą max-widthi calc().

Po pierwsze, należy wymienić width: 100%z flex: 1na div w CSS, więc będą rosnąć, co jest lepsze w tym przypadku. Ponadto użyj przejścia dla max-width.

Teraz musimy przechowywać pewne istotne wartości:

  • Ilość div, które będą animowane ( divsLengthzmienne) - 3 w tym przypadku.
  • Całkowita szerokość użyta dla ustalonego elementu div i ramek ( extraSpacezmienna) - w tym przypadku 39 pikseli.

Za pomocą tych 2 zmiennych możemy ustawić wartość domyślną max-width( defaultMaxWidthzmienną) dla wszystkich div, a także użyć ich później. Dlatego są one przechowywane na całym świecie.

defaultMaxWidthJest calc((100% - extraSpace)/divsLength).

Teraz przejdźmy do funkcji kliknięcia:

Aby rozwinąć div, szerokość tekstu docelowego zostanie zapisana w zmiennej o nazwie textWidthi zostanie zastosowana do div zgodnie z max-width.jej użyciem .getBoundingClientRect().width(ponieważ zwraca wartość zmiennoprzecinkową).

Dla pozostałych div, to tworzony jest calc()za max-widthktóre będą stosowane do nich.
To jest: calc(100% - textWidth - extraScape)/(divsLength - 1).
Obliczony wynik to szerokość, jaką powinien mieć każdy pozostały div.

Po kliknięciu rozwiniętego div, to znaczy, aby powrócić do normalności, ustawienie domyślne max-widthjest ponownie stosowane do wszystkich .divelementów.

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>

To podejście zachowuje się dynamicznie i powinno działać na dowolnej rozdzielczości.
Jedyną wartością, której potrzebujesz do twardego kodu, jest extraSpacezmienna.

Azametzin
źródło
1
Tak, jest to problem wymagający dokładnych obliczeń i lepszego zrozumienia efektu kurczenia się flexboksa. Nie mam jeszcze „idealnego” rozwiązania, ale chciałbym spróbować ponownie w przyszłości, dlatego dodałem do zakładek twoje pytanie po raz kolejny, aby spróbować dowiedzieć się, jak zrobić to dobrze. To dobre wyzwanie.
Azametzin
1
Tylko do wiadomości, edytowałem kod, aby ułatwić czytanie i testowanie. Dodałem rozpiętości, które są dobrym dodatkiem. Nie dodałem jednak przejść o maksymalnej szerokości, ponieważ jak dotąd nie działały świetnie.
Bachir Messaouri
1
Fajne. Właśnie to rozwiązałem. Edytuję odpowiedź.
Azametzin
1
Miły! W każdym razie nie spiesz się, bo jestem na kilka godzin ;-)
Bachir Messaouri
1
Oooh, właśnie widziałem Twój edytowany komentarz (spodziewałem się komentarza dotyczącego aktualizacji). Zastosowanie getBoundingClientRect () jest bardzo sprytne i nie przyszło mi do głowy (to samo w przypadku uwzględnienia stałej szerokości 39 pikseli). Przetestuję trochę więcej, aby zobaczyć, czy to działa dla dowolnego rozmiaru okna i dowolnej liczby div płynów. Wrócę do ciebie. Dziękuję Ci bardzo! PS: Twoje rozwiązanie działa z jQuery 3.3.1, ale NIE z 3.4.1, z jakiegokolwiek powodu.
Bachir Messaouri
3

Musisz poradzić sobie z funkcjami szerokości lub obliczania. Flexbox miałby rozwiązanie.

Aby wszystkie div były równe (nie pierwsze), używamy 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>

Zdefiniuj reguły flex dla normalnej div i wybranej div. transition: flex 1s;jest twoim przyjacielem. W przypadku wybranych nie potrzebujemy elastycznego wzrostu, więc używamy 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;
}

Dodaj wybraną klasę za każdym razem, gdy użytkownik kliknie div. Możesz także użyć przełącznika dla drugiego kliknięcia, aby zapisać wybrane elementy na mapie i wyświetlić wiele wybranych elementów (oczywiście nie z tym przykładem kodu).

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

https://jsfiddle.net/f3ao8xcj/

huragan
źródło
Dziękuję Ci. Muszę przestudiować twój przypadek, ponieważ robi to coś przeciwnego do tego, czego chcę i nie jestem jeszcze pewien, jak to odwrócić (może to trywialne). Chcę, aby domyślnie wszystkie płynne div były równe. tylko wtedy, gdy kliknę jeden, ten pasuje do jego zawartości (a pozostałe płynne div dzielą równo pozostałą przestrzeń). A jeśli kliknę drugi raz na ten sam div, wszystkie div Fluid zresetują się, ponownie dzieląc równe miejsce bez obawy o ich treść. Więc, jeśli się nie mylę, jest to dokładne przeciwieństwo tego, co zrobiłeś. Nie jestem jeszcze pewien, jak to odwrócić.
Bachir Messaouri
Jeśli jednak można to odwrócić, naprawdę podoba mi się to, jak eleganckie i proste jest twoje rozwiązanie, ponieważ nie ma potrzeby mieszania się z atrybutem szerokości css. Flex wydaje się po prostu sobie z tym poradzić. Powiedziawszy, mam przeczucie, że trudniej będzie osiągnąć odwrotną drogę. Poczekaj i zobacz. Dzięki za kontynuację.
Bachir Messaouri
1
@BachirMessaouri to znowu dość proste. Zobacz zaktualizowaną wersję.
huragan
Uwielbiam elegancję twojego skryptu, ale twoje rozwiązania uwzględniają tylko część mojej prośby. Moja prośba dotyczy nie tylko dopasowania zawartości, ale także równego dzielenia pozostałej przestrzeni przed, podczas i po kliknięciu fluid div. Twój skrypt nie radzi sobie tak dobrze, jeśli nie w ogóle. I to sprawia, że ​​cała ta sprawa płonie. Dodałem zdjęcie w oryginalnym poście, aby moje potrzeby były bardzo jasne. Drugi powyższy skrypt zaczyna się dokładnie tam, gdzie twój nie dostarczy. Dziwnie wydaje się, że połączenie obu skryptów może działać. Testuję i kiedy to zrobię, oddzwonię do ciebie.
Bachir Messaouri
@BachirMessaouri Brakuje tutaj, nikt nie musi zapewniać idealnego rozwiązania dla twojej sprawy. Flex transition: Stretch (or shrink) to fit contentto tytuł twojego pytania, a ta odpowiedź jest prostym przykładem tego, jak to zrobić. Twoim obowiązkiem jest znaleźć rozwiązanie dla twojej sprawy.
huragan
2

Po kilku wersjach próbnych wydaje się to moim najkrótszym i najprostszym rozwiązaniem.

Wszystko, co w zasadzie trzeba zrobić, to domyślnie rozciągnąć <div>elementy do ich granic, ale po <span>kliknięciu ograniczenie rozciągnięcia <div>do <span>szerokości ...

pseudo kod:

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

Podzieliłem CSS na sekcję „odpowiedni mechanizm” i „tylko cukierki” dla łatwego czytania (i recyklingu kodu).

Kod jest mocno komentowany, więc nie ma tu dużo tekstu ...

Dziwactwo Jakoś występuje dodatkowe opóźnienie w transitionprzełączaniu divz max-width: 100%na max-width = span width. Sprawdziłem to zachowanie w Chrome, Edge, IE11 i Firefox (wszystkie W10) i wszystkie wydają się mieć takie dziwactwo. Trwa wewnętrzne przeliczanie przeglądarki, a może transitionczas jest używany dwukrotnie („wydaje się”). Odwrotnie, co dziwne, nie ma dodatkowego opóźnienia.

Jednak przy krótkim czasie przejścia (np. 150 ms, jak teraz używam), to dodatkowe opóźnienie nie jest / prawie nie jest zauważalne. (Miło dla drugiego SO pytanie ...)

AKTUALIZACJA

Nowa wersja, która resetuje wszystkie pozostałe <div>. Naprawdę nie lubię bluzy, ale wynika to z rozciągania Flexboksa i jego transitionwartości. Bez transitionwidocznych skoków. Musisz wypróbować, co Ci odpowiada.

Dodałem tylko document.querySelectorAll()do kodu javascript.

Rene van der Lende
źródło
Dziękuję bardzo za twoją propozycję. Nie odpowiada moim potrzebom (ale jest to bardzo dobry punkt wyjścia), ponieważ kiedy klikamy płynny div, pasuje dobrze, ale dwa pozostałe div nie rozszerzają się równo, aby zajmować pozostałą przestrzeń. Co stanowi 50% problemu. I tak, jest też to dziwactwo. Skrypt Azametzina wykonuje pracę w 100% bez żadnych dziwactw. Skończyłem mieszanie zarówno jego skryptu, jak i zastosowania przejścia fleksyjnego zamiast maksymalnej szerokości i działa to jak urok. Bardzo ci dziękuje za pomoc !
Bachir Messaouri
Oddanie uwagi @Azametzina: W tej sadze było dużo zabawy. :) Może następnym razem zacznij od obrazu?
Rene van der Lende
Ok. Myślałem, że wyjaśnienie było wystarczające, ale oczywiście się myliłem. Obraz jest tam jednak od wczoraj. Ale rozumiem o co ci chodzi. Zmienię oryginalną wiadomość, aby wyświetlić niestandardowy skrypt w ciągu kilku minut. Dziękuję Ci bardzo !
Bachir Messaouri
Oryginalny post zaktualizowany o niestandardowe rozwiązanie!
Bachir Messaouri