Transición flexible: estirar (o reducir) para ajustar el contenido

9

He codificado un script (con la ayuda de un usuario aquí) que me permite expandir un div seleccionado y hacer que los otros div se comporten en consecuencia al estirarlo por igual para adaptarse al espacio restante (excepto el primero cuyo ancho es fijo).

Y aquí hay una imagen de lo que quiero lograr:

ingrese la descripción de la imagen aquí

Para eso uso flex y transiciones.

Funciona bien, pero el script jQuery especifica un valor de estiramiento de "400%" (que es excelente para probar).

Ahora me gustaría que el div seleccionado se expanda / reduzca para ajustarse exactamente al contenido en lugar del valor fijo "400%".

No tengo idea de cómo podría hacer eso.

Es posible ?

Traté de clonar el div, ajustarlo al contenido, obtener su valor y luego usar este valor para la transición PERO esto significa que tengo un ancho inicial en porcentajes pero un valor objetivo en píxeles. Eso no funciona

Y si convierto el valor del píxel en porcentajes, entonces el resultado no se ajusta exactamente al contenido por el motivo que sea.

En todos los casos, esto parece una forma un poco complicada de lograr lo que quiero de todos modos.

¿No hay alguna propiedad flexible que se pueda cambiar para ajustarse al contenido de un div seleccionado?

Aquí está el código ( editado / simplificado para una mejor lectura ):

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>

Aquí está el jsfiddle:

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

EDITAR: Aquí está mi solución de trabajo con la ayuda de todos ustedes (tamaños actualizados en el cambio de tamaño de la ventana + número de divs y el ancho de la primera columna calculado dinámicamente):

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>

Y aquí hay un ejemplo completo en el trabajo (estilos + relleno + más datos):

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

Gracias a todos !

Bachir Messaouri
fuente
1
Tenga la amabilidad de agregar un resultado final a su pregunta que muestre lo que creó después de todo esto. Estoy realmente interesado en cómo se ve. ¡Gracias! (... ¿quizás una idea de para qué se usa? ...)
Rene van der Lende
Ok, lo haré tan pronto como limpie mi script de todos los elementos innecesarios (en unos minutos, supongo). Dicho esto, ¿cómo hago eso? ¿Solo edito la publicación original y agrego mi opinión sobre esto a pesar de que la respuesta de Azametzin está perfectamente bien?
Cambio
Aquí está el punto: emulo una especie de hoja de cálculo de Excel y quiero que las personas puedan hacer clic en una columna en particular para ajustar el contenido. Normalmente, una tabla funcionaría, pero las columnas no se comportan correctamente cuando se trata de cambiar el tamaño de la forma que quiero (es fácil expandir una columna a la vez, pero es difícil hacer que las demás se comporten en tiempo real para ocupar el espacio restante de la manera deseada) flex lo haría para divs. Simplemente se expanden de manera errática. Investigué un poco y lo que quiero es imposible con las tablas). Hay otras razones por las que no uso una tabla o tabla de datos, pero está fuera del alcance de esta publicación.
Bachir Messaouri
1
Es bastante fácil, simplemente presione 'editar', vaya al final de la pregunta y comience a agregar algunas reflexiones prozaicas, incluido un nuevo fragmento (todo). Asegúrese de 'ocultar fragmento' de forma predeterminada (casilla de verificación inferior izquierda), menos intrusivo / distractor para 'nuevos lectores'. Lo primero que pensé fue que lo ibas a usar para algunos artilugios, íconos y todo tipo de 'armónica'. Hoja de cálculo eh, tengo algo de pantalla completa, que se extiende por todo el lugar hacia arriba / abajo / izquierda / derecha, con los mismos principios discutidos aquí (si está interesado).
Rene van der Lende
2
Genial, hiciste un esfuerzo adicional publicando el resultado final. ¡Hace que responder preguntas sobre TAN sea mucho más gratificante! Cudos ...
Rene van der Lende

Respuestas:

4

Es posible resolverlo usando max-widthy calc().

Primero, reemplace width: 100%con flex: 1los divs en CSS, para que crezcan, lo que es mejor en este caso. Además, use la transición para max-width.

Ahora, tenemos que almacenar algunos valores relevantes:

  • La cantidad de divs que se animarán ( divsLengthvariable) - 3 en este caso.
  • El ancho total utilizado para el div fijo y los bordes ( extraSpacevariable) - 39px en este caso.

Con esas 2 variables, podemos establecer un valor predeterminado max-width( defaultMaxWidthvariable) para todos los divs, así como usarlos más adelante. Es por eso que se almacenan a nivel mundial.

El defaultMaxWidthes calc((100% - extraSpace)/divsLength).

Ahora, ingresemos la función de clic:

Para expandir el div, el ancho del texto de destino se almacenará en una variable llamada textWidthy se aplicará al div como lo max-width.usa .getBoundingClientRect().width(ya que devuelve el valor de coma flotante).

Para los divs restantes, se crea un calc()para max-widthque se les aplicará.
Que es: calc(100% - textWidth - extraScape)/(divsLength - 1).
El resultado calculado es el ancho que debe tener cada div restante.

Al hacer clic en el div expandido, es decir, para volver a la normalidad, el valor predeterminado max-widthse aplica nuevamente a todos los .divelementos.

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>

Este enfoque se comporta dinámicamente y debería funcionar en cualquier resolución.
El único valor que necesita para codificar es la extraSpacevariable.

Azametzin
fuente
1
Sí, este es un problema que requiere un cálculo preciso y una mayor comprensión del efecto retráctil flexbox. Todavía no tengo una solución "perfecta", pero me gustaría volver a intentarlo en el futuro, así que agregué su pregunta a otro lugar para intentar descubrir cómo hacerlo correctamente. Es un buen reto.
Azametzin
1
Solo para tu información, edité el código para que sea más fácil de leer y probar. Agregué los tramos, que son una buena adición. Sin embargo, no he agregado las transiciones de ancho máximo hasta ahora, no funcionaron muy bien.
Bachir Messaouri
1
Frio. Solo lo resolví. Estoy editando la respuesta.
Azametzin
1
¡Agradable! Tómese su tiempo ya que estoy fuera por un par de horas de todos modos ;-)
Bachir Messaouri
1
Oooh, acabo de ver tu comentario editado (esperaba un comentario de actualización). El uso de getBoundingClientRect () es muy inteligente y no se me ocurrió (lo mismo para tener en cuenta el ancho fijo de 39 px). Probaré un poco más para ver si funciona para cualquier tamaño de ventana y cualquier número de divisiones Fluid. Volvere a ti. ¡Muchas gracias! PD: su solución funciona con jQuery 3.3.1 pero NO con 3.4.1, por cualquier razón.
Bachir Messaouri
3

Debes ocuparte de las funciones ancho o calc. Flexbox tendría una solución.

Para hacer todos los divs iguales (no el primero) usamos 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>

Defina reglas flexibles para su div normal y div seleccionado. transition: flex 1s;es tu amigo. Para el seleccionado no necesitamos crecimiento flexible, así que usamos 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;
}

Agregue la clase seleccionada cada vez que el usuario haga clic en un div. También puede usar alternar para el segundo clic para poder guardar los elementos seleccionados en un mapa y puede mostrar varios elementos seleccionados (no con este ejemplo de código, por supuesto).

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

https://jsfiddle.net/f3ao8xcj/

huracán
fuente
Gracias. Tengo que estudiar su caso porque hace lo contrario de lo que quiero y todavía no estoy seguro de cómo revertirlo (tal vez sea trivial). Quiero, por defecto, todos los divs fluidos sean igualmente grandes. Es solo cuando hago clic en uno que este se ajusta a su contenido (y los otros divs fluidos comparten igualmente el espacio restante). Y si hago clic por segunda vez en el mismo div, todos los div Fluid se restablecen, compartiendo el espacio nuevamente sin preocuparse por su contenido. Entonces, si no me equivoco, es exactamente lo contrario de lo que hiciste. Aún no estoy seguro de cómo revertir eso.
Bachir Messaouri
Sin embargo, si esto se puede revertir, realmente me gusta cuán elegante y simple es su solución, ya que no hay necesidad de jugar con el atributo css width. Flex parece manejar esto internamente. Dicho esto, tengo el presentimiento de que será más difícil lograr lo contrario. Espera y verás. Gracias por el seguimiento.
Bachir Messaouri
1
@BachirMessaouri es bastante simple de nuevo. Por favor vea la versión actualizada.
huracán
Me encanta la elegancia de su guión, pero sus soluciones solo consideran partes de mi solicitud. Mi solicitud no solo se trata de ajustar el contenido, sino también de compartir el espacio restante por igual antes, durante y después de hacer clic en los divs fluidos. Su guión no aborda eso muy bien, si no es que en absoluto. Y eso es lo que hace que todo sea un quemador de cerebro. Agregué una imagen en la publicación original para aclarar mis necesidades. La otra secuencia de comandos anterior comienza exactamente donde el suyo no se puede entregar. Parece extraño que una combinación de ambos scripts pueda funcionar. Estoy probando y una vez hecho, volveré a contactarte.
Bachir Messaouri
@BachirMessaouri Lo que se está perdiendo aquí es que nadie necesita proporcionar una solución perfecta para su caso. Flex transition: Stretch (or shrink) to fit contentes el título de su pregunta y esta respuesta es el simple ejemplo de cómo hacerlo. Es su responsabilidad encontrar la solución para su caso.
huracán
2

Después de algunas versiones de prueba, esta parece ser mi solución más corta y directa.

Todo lo que hay en esencia hay que hacer es estirar Flexbox tienen los <div>elementos a sus límites por defecto, pero cuando <span>se hace clic, la restricción del tramo de la <div>de <span>ancho ...

pseudocódigo:

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

He dividido el CSS en una sección de 'mecanismo relevante' y 'solo para los ojos' para facilitar la lectura (y el reciclaje del código).

El código está muy comentado, así que no hay mucho texto aquí ...

Quirk De alguna manera hay un retraso adicional en el transitioncuando se cambia divde max-width: 100%a max-width = span width. He comprobado este comportamiento en Chrome, Edge, IE11 y Firefox (todos W10) y todos parecen tener esta peculiaridad. O se está produciendo algún recálculo interno del navegador, o tal vez el transitiontiempo se usa dos veces ('parece') Viceversa, por extraño que parezca, no hay retraso adicional.

Sin embargo, con un tiempo de transición corto (p. Ej., 150 ms, como lo estoy usando ahora), este retraso adicional no es / apenas se nota. (Agradable para otra pregunta SO ...)

ACTUALIZAR

Nueva versión que restablece todas las demás <div>. Realmente odio el nerviosismo, pero eso se debe al estiramiento de Flexbox y al transitionvalor. Sin transitionsaltos visibles. Necesita probar lo que funciona para usted.

Solo agregué document.querySelectorAll()el código javascript.

Rene van der Lende
fuente
Muchas gracias por tu propuesta. No se ajusta a mis necesidades (pero es un muy buen punto de partida) ya que cuando hacemos clic en un div fluido, se ajusta bien, pero los otros dos div no se expanden por igual para ocupar el espacio restante. Que es el 50% del problema. Y sí, también existe esta peculiaridad. El guión de Azametzin hace el trabajo al 100% sin peculiaridades. Terminé mezclando tanto su guión como el uso de la transición flexible en lugar del ancho máximo y funciona de maravilla. Muchas gracias por su ayuda !
Bachir Messaouri
Secundando el comentario de @Azametzin: Fueron muchos días divertidos en esta saga. :). ¿Quizás la próxima vez comience con la imagen?
Rene van der Lende
Okay. Pensé que la explicación era suficiente, pero obviamente estaba equivocado. Sin embargo, la imagen está allí desde ayer. Pero entiendo tu punto. Editaré mi mensaje original para mostrar mi script personalizado en cuestión de minutos. Muchas gracias !
Bachir Messaouri
¡Publicación original actualizada con la solución personalizada!
Bachir Messaouri