Transition flexible: étirer (ou rétrécir) pour s'adapter au contenu

9

J'ai codé un script (avec l'aide d'un utilisateur ici) qui me permet d'agrandir un div sélectionné et de faire en sorte que les autres div se comportent en conséquence en s'étirant également pour s'adapter à l'espace restant (sauf le premier dont la largeur est fixe).

Et voici une photo de ce que je veux réaliser:

entrez la description de l'image ici

Pour cela j'utilise des flex et des transitions.

Cela fonctionne bien, mais le script jQuery spécifie une valeur d'étirement "400%" (ce qui est idéal pour les tests).

Maintenant, je voudrais que le div sélectionné s'agrandisse / rétrécisse pour s'adapter exactement au contenu au lieu de la valeur fixe "400%".

Je ne sais pas comment je pourrais faire ça.

C'est possible ?

J'ai essayé de cloner le div, de l'adapter au contenu, d'obtenir sa valeur et d'utiliser cette valeur pour la transition MAIS cela signifie que j'ai une largeur initiale en pourcentage mais une valeur cible en pixels. Ça ne marche pas.

Et si je convertis la valeur en pixels en pourcentages, le résultat ne correspond pas exactement au contenu pour une raison quelconque.

Dans tous les cas, cela semble un peu compliqué pour réaliser ce que je veux de toute façon.

N'y a-t-il pas une propriété flex qui pourrait être transférée afin de s'adapter au contenu d'un div sélectionné?

Voici le code ( édité / simplifié depuis pour une meilleure lecture ):

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>

Voici le jsfiddle:

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

EDIT: Voici ma solution de travail avec l'aide de vous tous (tailles mises à jour lors du redimensionnement de la fenêtre + nombre de divs et largeur de la première colonne calculée dynamiquement):

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>

Et voici un exemple à part entière au travail (styles + rembourrage + plus de données):

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

Merci à tous !

Bachir Messaouri
la source
1
Veuillez être assez aimable pour ajouter un résultat final à votre question montrant ce que vous avez créé après tout cela. Je suis vraiment intéressé par son apparence. Merci! (... peut-être un aperçu de son utilisation? ...)
Rene van der Lende
Ok, je le ferai dès que j'aurai nettoyé mon script de tous les éléments inutiles (en quelques minutes je suppose). Cela dit, comment dois-je faire cela? Dois-je simplement modifier le message d'origine et ajouter mon point de vue à ce sujet, même si la réponse d'Azametzin est parfaitement correcte? Je change quelques choses mais c'est 90% pareil (je serais heureux de le montrer quand même) ..
Bachir Messaouri
Voici le point: j'émule une sorte de feuille de calcul Excel et je veux que les gens puissent cliquer sur une colonne particulière pour s'adapter au contenu. Normalement, une table ferait l'affaire mais les colonnes ne se comportent pas correctement quand il s'agit de les redimensionner comme je le souhaite (facile à développer une colonne à la fois mais il est difficile de faire en sorte que les autres se comportent en temps réel afin d'occuper l'espace restant de la manière flex ferait pour les divs. Ils se développent juste de façon erratique.J'ai fait quelques recherches et ce que je veux est impossible avec les tableaux). Il y a d'autres raisons pour lesquelles je n'utilise pas de table ou de dataTable mais cela sort du cadre de cet article.
Bachir Messaouri
1
Assez simple, appuyez simplement sur "modifier", allez à la fin de la question et commencez à ajouter quelques réflexions prozaic, y compris un nouvel extrait (tout cela). Assurez-vous de «masquer l'extrait de code» par défaut (case à cocher en bas à gauche), moins intrusif / distrayant pour les «nouveaux lecteurs». Ma première pensée a été que vous alliez l'utiliser pour des widgets, des icônes et tout le reste. Feuille de calcul hein, j'ai un plein écran, qui s'étend partout en haut / en bas / à gauche / à droite en utilisant les mêmes principes discutés ici (si vous êtes intéressé).
Rene van der Lende
2
Super, vous avez fait un effort supplémentaire pour afficher le résultat final. Rend les réponses aux questions tellement PLUS gratifiantes! Cudos ...
Rene van der Lende

Réponses:

4

Il est possible de le résoudre en utilisant max-widthet calc().

Tout d'abord, remplacez width: 100%par flex: 1pour les divs en CSS, afin qu'ils grandissent, ce qui est mieux dans ce cas. De plus, utilisez transition pour max-width.

Maintenant, nous devons stocker des valeurs pertinentes:

  • Le nombre de divs qui seront animés ( divsLengthvariable) - 3 dans ce cas.
  • La largeur totale utilisée pour le div fixe et les bordures ( extraSpacevariable) - 39px dans ce cas.

Avec ces 2 variables, nous pouvons définir une valeur par défaut max-width( defaultMaxWidthvariable) pour toutes les divs, ainsi que les utiliser plus tard. C'est pourquoi ils sont stockés dans le monde.

Le defaultMaxWidthest calc((100% - extraSpace)/divsLength).

Maintenant, entrons dans la fonction de clic:

Pour développer le div, la largeur du texte cible sera stockée dans une variable appelée textWidthet elle sera appliquée au div tel max-width.qu'il l'utilise .getBoundingClientRect().width(car il renvoie la valeur à virgule flottante).

Pour les div restants, il est créé un calc()pour max-widthqui leur sera appliqué.
Il est: calc(100% - textWidth - extraScape)/(divsLength - 1).
Le résultat calculé est la largeur de chaque div restante.

Lorsque vous cliquez sur le div étendu, c'est-à-dire pour revenir à la normale, la valeur par défaut max-widthest à nouveau appliquée à tous les .divéléments.

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>

Cette approche se comporte de manière dynamique et devrait fonctionner sur n'importe quelle résolution.
La seule valeur dont vous avez besoin pour coder en dur est la extraSpacevariable.

Azametzin
la source
1
Oui, c'est un problème qui nécessite un calcul précis et une meilleure compréhension de l'effet de rétrécissement de la flexbox. Je n'ai pas encore de solution "parfaite" mais je voudrais réessayer à l'avenir, j'ai donc ajouté une nouvelle fois votre question à vos favoris pour essayer de comprendre comment le faire correctement. C'est un bon défi.
Azametzin
1
Pour info, j'ai édité le code depuis pour le rendre plus simple à lire et à tester. J'ai ajouté les portées qui sont un bon ajout. Je n'ai pas ajouté les transitions max-width cependant jusqu'à présent, elles ne fonctionnaient pas très bien.
Bachir Messaouri
1
Cool. Je viens de le résoudre. J'édite la réponse.
Azametzin
1
Agréable! Prenez votre temps car je suis absent pendant quelques heures de toute façon ;-)
Bachir Messaouri
1
Oooh, je viens de voir votre commentaire édité (je m'attendais à un commentaire de mise à jour). L'utilisation de getBoundingClientRect () est très intelligente et cela ne m'est pas venu à l'esprit (de même pour la prise en compte de la largeur fixe de 39 px). Je vais tester un peu plus pour voir si cela fonctionne pour n'importe quelle taille de fenêtre et n'importe quel nombre de divs Fluid. Je reviendrai vers toi. Merci beaucoup! PS: votre solution fonctionne avec jQuery 3.3.1 mais PAS avec 3.4.1, quelle qu'en soit la raison.
Bachir Messaouri
3

Vous devez gérer les fonctions largeur ou calc. Flexbox aurait une solution.

Pour rendre toutes les div égales (pas la première) que nous utilisons 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>

Définissez des règles de flexion pour votre div normal et votre div sélectionné. transition: flex 1s;est votre ami. Pour celui que nous avons sélectionné, nous n'avons pas besoin de flex flex, nous utilisons donc 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;
}

Ajoutez la classe sélectionnée chaque fois que l'utilisateur clique sur une div. Vous pouvez également utiliser la bascule pour le deuxième clic afin de pouvoir enregistrer les éléments sélectionnés dans une carte et vous pouvez afficher plusieurs éléments sélectionnés (pas avec cet exemple de code bien sûr).

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

https://jsfiddle.net/f3ao8xcj/

ouragan
la source
Je vous remercie. Je dois étudier votre cas, car il fait le contraire de ce que je veux et je ne sais pas encore comment inverser cela (c'est peut-être trivial). Je veux, par défaut, que toutes les divisions fluides soient également grandes. ce n'est que lorsque je clique sur celui-ci que celui-ci correspond à son contenu (et que les autres divs fluides partagent également l'espace restant). Et si je clique une deuxième fois sur la même div, toutes les divs Fluid sont réinitialisées, partageant à nouveau l'espace de la même manière sans se soucier de leur contenu. Donc, si je ne me trompe pas, c'est exactement le contraire de ce que vous avez fait. Je ne sais pas encore comment inverser cela.
Bachir Messaouri
Cependant, si cela peut être inversé, j'aime vraiment l'élégance et la simplicité de votre solution car il n'est pas nécessaire de jouer avec l'attribut css width. Flex semble gérer cela en interne. Cela dit, j'ai le pressentiment qu'il sera plus difficile de réaliser l'inverse. Attend et regarde. Merci pour le suivi.
Bachir Messaouri
1
@ BachirMessaouri, c'est encore assez simple. Veuillez consulter la version mise à jour.
ouragan le
J'adore l'élégance de votre script mais vos solutions ne prennent en compte qu'une partie de ma demande. Ma demande ne concerne pas seulement l'adaptation du contenu, mais également le partage de l'espace restant avant, pendant et après avoir cliqué sur les divs fluides. Votre script ne répond pas très bien, sinon pas du tout. Et c'est ce qui fait que tout cela brûle le cerveau. J'ai ajouté une photo dans le message d'origine pour clarifier mes besoins. L'autre script ci-dessus commence exactement là où le vôtre échoue. Il semble étrangement qu'un mélange de vos deux scripts puisse fonctionner. Je teste et une fois terminé, je te recontacterai.
Bachir Messaouri
@BachirMessaouri Ce qui vous manque ici, c'est que personne n'a besoin de fournir une solution parfaite pour votre cas. Flex transition: Stretch (or shrink) to fit contentest le titre de votre question et cette réponse est un exemple simple de la façon de le faire. Il vous appartient de trouver la solution à votre cas.
ouragan
2

Après quelques versions d'essai, cela semble être ma solution la plus courte et la plus simple.

Tout ce qui doit essentiellement être fait est d' avoir Flexbox étirer les <div>éléments à leurs limites par défaut, mais quand on <span>clique dessus, contrainte l'étirement de la <div>à la <span>largeur ...

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

J'ai divisé le CSS en une section «mécanisme pertinent» et «eye-candy only» pour une lecture facile (et recylage du code).

Le code est fortement commenté, donc pas beaucoup de texte ici ...

Quirk D'une façon ou d'une autre, il y a un délai supplémentaire lors du transitionpassage divde max-width: 100%à max-width = span width. J'ai vérifié ce comportement dans Chrome, Edge, IE11 et Firefox (tous W10) et tous semblent avoir cette bizarrerie. Soit un recalcul interne du navigateur est en cours, soit peut-être que le transitiontemps est utilisé deux fois («on dirait»). Vice Versa, curieusement, il n'y a pas de délai supplémentaire.

Cependant, avec un temps de transition court (par exemple 150 ms, comme je l'utilise actuellement), ce retard supplémentaire n'est pas / à peine perceptible. (Sympa pour une autre question SO ...)

MISE À JOUR

Nouvelle version qui réinitialise tous les autres <div>. Je déteste vraiment la nervosité, mais cela est dû à l'étirement de la Flexbox et à sa transitionvaleur. Sans transitionaucun saut visible. Vous devez essayer ce qui fonctionne pour vous.

J'ai seulement ajouté document.querySelectorAll()au code javascript.

René van der Lende
la source
Merci beaucoup pour votre proposition. Cela ne correspond pas à mes besoins (mais c'est un très bon point de départ) car lorsque nous cliquons sur une div fluide, elle s'adapte bien, mais les deux autres div ne s'étendent pas de manière égale pour occuper l'espace restant. Ce qui représente 50% du problème. Et oui, il y a aussi cette bizarrerie. Le script d'Azametzin fait le travail à 100% sans excentricité. J'ai fini par mélanger son script et l'utilisation de la transition flex au lieu de la largeur maximale et cela fonctionne comme un charme. Merci beaucoup pour votre aide !
Bachir Messaouri
Appui à la remarque de @Azametzin: Ce fut beaucoup de jours amusants dans cette saga. :). Peut-être la prochaine fois commencer avec l'image?
Rene van der Lende
D'accord. Je pensais que l'explication était suffisante mais visiblement, j'avais tort. L'image est là depuis hier cependant. Mais je comprends votre point. Je vais modifier mon message d'origine pour afficher mon script personnalisé en quelques minutes. Merci beaucoup !
Bachir Messaouri
Message original mis à jour avec la solution personnalisée!
Bachir Messaouri