Transição flexível: estique (ou reduza) para ajustar-se ao conteúdo

9

Eu codifiquei um script (com a ajuda de um usuário aqui) que me permite expandir uma div selecionada e fazer com que as outras divs se comportem de acordo, esticando igualmente para ajustar o espaço restante (exceto o primeiro cuja largura é fixa).

E aqui está uma imagem do que eu quero alcançar:

insira a descrição da imagem aqui

Para isso eu uso flex e transitions.

Funciona bem, mas o script jQuery especifica um valor de extensão "400%" (o que é ótimo para teste).

Agora, eu gostaria que a div selecionada fosse expandida / reduzida para se ajustar exatamente ao conteúdo, em vez do valor fixo "400%".

Não tenho ideia de como eu poderia fazer isso.

É possível ?

Tentei clonar a div, ajustá-la ao conteúdo, obter seu valor e, em seguida, usar esse valor para fazer a transição, MAS isso significa que tenho uma largura inicial em porcentagens, mas um valor de destino em pixels. Isso não funciona.

E se eu converter o valor do pixel em porcentagens, o resultado não se ajustará exatamente ao conteúdo por qualquer motivo.

Em todos os casos, essa parece uma maneira um pouco complicada de conseguir o que quero de qualquer maneira.

Não existe nenhuma propriedade flex que possa ser transferida para ajustar o conteúdo de uma div selecionada?

Aqui está o código ( editado / simplificado, para uma melhor leitura ):

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>

Aqui está o jsfiddle:

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

EDIT: Aqui está a minha solução de trabalho com a ajuda de todos (tamanhos atualizados no redimensionamento da janela + número de divs e largura da primeira coluna calculada dinamicamente):

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>

E aqui está um exemplo completo no trabalho (estilos + preenchimento + mais dados):

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

Obrigado a todos!

Bachir Messaouri
fonte
11
Tenha a gentileza de adicionar um resultado final à sua pergunta mostrando o que você criou depois de tudo isso. Estou genuinamente interessado no que parece. Obrigado! (... talvez algumas dicas sobre o que é usado? ...)
Rene van der Lende
Ok, farei isso assim que limpei meu script de todos os elementos desnecessários (em alguns minutos, eu acho). Dito isto, como faço isso? Eu apenas edito a postagem original e adiciono minha opinião sobre isso, embora a resposta do Azametzin esteja perfeitamente bem? Eu mudo algumas coisas, mas é 90% o mesmo (eu ficaria feliz em mostrar isso) ..
Bachir Messaouri
Aqui está o ponto: eu emulo uma espécie de planilha do Excel e quero que as pessoas possam clicar em uma coluna específica para ajustar-se ao conteúdo. Normalmente, uma tabela funcionaria, mas as colunas não estão se comportando corretamente quando se trata de redimensioná-las da maneira que eu quero (fácil de expandir uma coluna por vez, mas é difícil fazer com que as outras se comportem em tempo real, a fim de ocupar o espaço restante da maneira flex faria para divs. Eles apenas se expandem erraticamente. Fiz algumas pesquisas e o que eu quero é impossível com tabelas). Há outras razões pelas quais não uso uma tabela ou dataTable, mas está fora do escopo desta postagem.
Bachir Messaouri 28/02
11
Com bastante facilidade, basta clicar em 'editar', ir para o final da pergunta e começar a adicionar algumas considerações pró-áicas, incluindo um novo trecho (tudo isso). Certifique-se de "ocultar o snippet" por padrão (caixa de seleção inferior esquerda), menos invasivo / perturbador para os "novos leitores". Meu primeiro pensamento foi que você o usaria para alguns widgets, ícones e tudo mais. Planilha huh, eu tenho uma tela inteira, estendendo-se por todo o lado para cima / baixo / esquerda / direita, usando os mesmos princípios discutidos aqui (se você estiver interessado).
Rene van der Lende 28/02
2
Ótimo, você foi a milha extra postando o resultado final. Torna a resposta a perguntas sobre MUITO mais gratificante! Cudos ...
Rene van der Lende 28/02

Respostas:

4

É possível resolvê-lo usando max-widthe calc().

Primeiro, substitua width: 100%com flex: 1os divs em CSS, então eles vão crescer, o que é melhor neste caso. Além disso, use a transição para max-width.

Agora, temos que armazenar alguns valores relevantes:

  • A quantidade de divs que serão animadas ( divsLengthvariável) - 3 neste caso.
  • A largura total usada para a div fixa e as bordas ( extraSpacevariável) - 39px neste caso.

Com essas duas variáveis, podemos definir um padrão max-width( defaultMaxWidthvariável) para todas as divs, além de usá-las posteriormente. É por isso que eles estão sendo armazenados globalmente.

O defaultMaxWidthé calc((100% - extraSpace)/divsLength).

Agora, vamos inserir a função click:

Para expandir a div, a largura do texto de destino será armazenada em uma variável chamada textWidthe será aplicada à div como max-width.ela usa .getBoundingClientRect().width(uma vez que retorna o valor do ponto flutuante).

Para as divs restantes, é criado um calc()para max-widthque será aplicado a elas.
É: calc(100% - textWidth - extraScape)/(divsLength - 1).
O resultado calculado é a largura que cada div restante deve ter.

Ao clicar na div expandida, ou seja, para retornar ao normal, o padrão max-widthé aplicado novamente a todos os .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>

Essa abordagem se comporta dinamicamente e deve funcionar em qualquer resolução.
O único valor que você precisa para codificar é a extraSpacevariável

Azametzin
fonte
11
Sim, este é um problema que requer um cálculo preciso e uma maior compreensão do efeito de redução do flexbox. Ainda não tenho uma solução "perfeita", mas gostaria de tentar novamente no futuro. Por isso, marquei a sua pergunta em outro momento para tentar descobrir como fazê-la corretamente. É um bom desafio.
Azametzin 24/02
11
Apenas para sua informação, editei o código para facilitar a leitura e o teste. Eu adicionei os vãos, que são uma boa adição. Não adicionei as transições de largura máxima, no entanto, até agora, elas não funcionaram muito bem.
Bachir Messaouri 25/02
11
Legal. Eu apenas resolvi isso. Estou editando a resposta.
Azametzin 25/02
11
Agradável! Não se
apresse
11
Acabei de ver seu comentário editado (estava esperando um comentário de atualização). O uso de getBoundingClientRect () é muito inteligente e não me ocorreu (o mesmo por levar em consideração a largura fixa de 39 px). Vou testar um pouco mais para ver se funciona para qualquer tamanho de janela e qualquer número de divs do Fluid. Eu voltarei para você. Muito obrigado! PS: sua solução funciona com o jQuery 3.3.1, mas NÃO com o 3.4.1, por qualquer motivo.
Bachir Messaouri 26/02
3

Você precisa lidar com as funções width ou calc. O Flexbox teria uma solução.

Para tornar todos os divs iguais (não o primeiro), 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 regras flex para sua div normal e div selecionada. transition: flex 1s;é seu amigo. Para o selecionado, não precisamos de flex grow, então 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;
}

Adicione a classe selecionada sempre que o usuário clicar em uma div. Você também pode usar a alternância para o segundo clique, para salvar os itens selecionados em um mapa e mostrar vários itens selecionados (não com este exemplo de código, é claro).

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

https://jsfiddle.net/f3ao8xcj/

furacão
fonte
Obrigado. Eu tenho que estudar o seu caso, porque faz o oposto do que eu quero e não tenho certeza de como reverter isso ainda (talvez seja trivial). Eu quero, por padrão, todos os divs fluidos serem igualmente grandes. é somente quando clico em um que este se encaixa em seu conteúdo (e os outros divs fluidos compartilham igualmente o espaço restante). E se eu clicar uma segunda vez na mesma div, todas as divs do Fluid serão redefinidas, compartilhando igualmente o espaço novamente, sem se preocupar com o conteúdo delas. Então, se não me engano, é exatamente o oposto do que você fez. Ainda não sei como reverter isso.
Bachir Messaouri
No entanto, se isso puder ser revertido, eu realmente gosto do quão elegante e simples é sua solução, pois não há necessidade de mexer no atributo de largura de css. Flex parece lidar com isso internamente. Dito isto, tenho um palpite de que será mais difícil conseguir o caminho oposto. Espere e veja. Obrigado pelo seguimento.
Bachir Messaouri 26/02
11
@BachirMessaouri é bastante simples novamente. Por favor, veja a versão atualizada.
furacão
Adoro a elegância do seu script, mas suas soluções consideram apenas partes do meu pedido. Minha solicitação não é apenas sobre o ajuste do conteúdo, mas também sobre o compartilhamento do espaço restante antes, durante e depois de clicar nas divs fluidas. Seu script não aborda isso muito bem, se não for o caso. E é isso que torna a coisa toda um queimador de cérebro. Adicionei uma foto no post original para deixar minhas necessidades muito claras. O outro script acima começa exatamente onde o seu falha ao fornecer. Estranhamente, parece que uma mistura dos dois scripts pode funcionar. Estou testando e, uma vez terminado, volto para você.
Bachir Messaouri 27/02
@BachirMessaouri O que você está perdendo aqui é que ninguém precisa fornecer uma solução perfeita para o seu caso. Flex transition: Stretch (or shrink) to fit contenté o título da sua pergunta e esta resposta é um exemplo simples de como fazê-lo. É sua responsabilidade encontrar a solução para o seu caso.
furacão
2

Após algumas versões de teste, essa parece ser minha solução mais curta e direta.

Tudo o que precisa, essencialmente, a ser feito é ter Flexbox esticar os <div>elementos para os seus limites, por padrão, mas quando <span>clicado, restrição do trecho da <div>de <span>largura ...

pseudo-có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

Dividi o CSS em uma seção 'mecanismo relevante' e 'apenas colírio para os olhos' para facilitar a leitura (e a reciclagem de código).

O código é muito comentado, então não há muito texto aqui ...

Quirk De alguma forma, há um atraso extra transitionao alternar divde de max-width: 100%para max-width = span width. Eu verifiquei esse comportamento no Chrome, Edge, IE11 e Firefox (todos os W10) e todos parecem ter essa peculiaridade. Ou algum recálculo interno do navegador está acontecendo, ou talvez o transitiontempo seja usado duas vezes ('parece'). Vice-Versa, por incrível que pareça, não há atraso extra.

No entanto, com um curto tempo de transição (por exemplo, 150ms, como estou usando agora) esse atraso extra não é / dificilmente perceptível. (Bom para outra pergunta SO ...)

ATUALIZAR

Nova versão que redefine todas as outras <div>. Eu realmente odeio o jumpiness, mas isso é devido ao alongamento do Flexbox e ao transitionvalor. Sem transitionsaltos visíveis. Você precisa experimentar o que funciona para você.

Eu adicionei apenas document.querySelectorAll()o código javascript.

Rene van der Lende
fonte
Muito obrigado pela sua proposta. Ele não se encaixa nas minhas necessidades (mas é um ponto de partida muito bom), pois quando clicamos em uma div fluida, ela se encaixa bem, mas as outras duas divs não se expandem igualmente para ocupar o espaço restante. O que representa 50% da questão. E sim, também há essa peculiaridade. O script de Azametzin faz o trabalho 100% sem esquisitices. Acabei misturando o script dele e o uso da transição flexível em vez da largura máxima e funciona como um encanto. Muito obrigado pela sua ajuda!
Bachir Messaouri 28/02
Destacando a observação de Azametzin: Foram muitos dias divertidos nessa saga. :). Talvez da próxima vez comece com a imagem?
Rene van der Lende
Está bem. Eu pensei que a explicação era suficiente, mas obviamente, eu estava errado. A imagem está lá desde ontem. Mas entendi o seu ponto. Editarei minha mensagem original para mostrar meu script personalizado em questão de minutos. Muito obrigado !
Bachir Messaouri 28/02
Postagem original atualizada com a solução personalizada!
Bachir Messaouri 28/02