Flex 过渡:拉伸(或收缩)以适应内容
Posted
技术标签:
【中文标题】Flex 过渡:拉伸(或收缩)以适应内容【英文标题】:Flex transition: Stretch (or shrink) to fit content 【发布时间】:2020-06-07 13:12:06 【问题描述】:我编写了一个脚本(在此处的用户的帮助下),它允许我扩展选定的 div 并通过平均拉伸以适应剩余空间来使其他 div 相应地运行(除了宽度固定的第一个) .
这是我想要实现的目标的图片:
为此,我使用 flex 和 transitions。
效果很好,但是 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/
编辑:在大家的帮助下,这是我的工作解决方案(窗口大小更新 + div 数和动态计算的第一列宽度):
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/
谢谢大家!
【问题讨论】:
请在您的问题中添加最终结果,显示您在这一切之后创建的内容。我对它的样子很感兴趣。谢谢! (...也许对它的用途有所了解?...) 好的,我会在清除脚本中所有不必要的元素后立即执行此操作(我猜几分钟后)。也就是说,我该怎么做?即使 Azametzin 的答案非常好,我是否只是编辑原始帖子并添加我的看法?我改变了一些东西,但 90% 是一样的(不过我很乐意展示它).. 重点是:我模拟一种 excel 电子表格,我希望人们能够单击特定列以适应内容。通常一个表可以,但是当以我想要的方式调整它们的大小时,列的行为不正确(一次很容易扩展一列,但很难让其他列实时行为以占用剩余空间的方式flex 会为 div 做。它们只是不规则地扩展。我做了一些研究,我想要的对于表格来说是不可能的)。我不使用表或数据表还有其他原因,但这超出了本文的范围。 很简单,只需点击“编辑”,转到问题的末尾并开始添加一些简单的思考,包括一个新的 sn-p(全部)。确保默认“隐藏 sn-p”(左下角复选框),减少对“新读者”的干扰/分心。我的第一个想法是你将把它用于一些花哨的“口琴”式小部件、图标和所有东西。电子表格,嗯,我确实有一些全屏,使用此处讨论的相同原则(如果您有兴趣),向上/向下/向左/向右伸展。 太好了,您在发布最终结果时付出了额外的努力。让回答关于 SO 的问题更有意义!恭喜... 【参考方案1】:使用max-width
和calc()
可以解决它。
首先,将 CSS 中的 div 替换为 flex: 1
的 width: 100%
,这样它们就会增长,这在这种情况下会更好。此外,对max-width
使用过渡。
现在,我们必须存储一些相关的值:
将被动画化的 div 数量(divsLength
变量) - 在这种情况下为 3。
用于固定 div 和边框(extraSpace
变量)的总宽度 - 在本例中为 39 像素。
使用这 2 个变量,我们可以为所有 div 设置默认的 max-width
(defaultMaxWidth
变量),并在以后使用它们。这就是它们被全局存储的原因。
defaultMaxWidth
是 calc((100% - extraSpace)/divsLength)
。
现在,让我们进入点击函数:
为了扩展 div,目标文本的宽度将存储在名为 textWidth
的变量中,并将作为 max-width.
应用于 div 它使用 .getBoundingClientRect().width
(因为它返回浮点值)。
对于剩余的 div,它会为 max-width
创建一个 calc()
,并将应用于它们。
它是: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
变量。
【讨论】:
是的,这是一个需要精确计算和深入理解flexbox收缩效果的问题。我还没有一个“完美”的解决方案,但我想在未来再试一次,所以我再次为你的问题添加了书签,试图弄清楚如何正确地做到这一点。这是一个很好的挑战。 仅供参考,我编辑了代码,以使其更易于阅读和测试。我添加了跨度,这是一个很好的补充。我还没有添加最大宽度的过渡,但是到目前为止,它们的效果并不好。 不错!反正我要出去几个小时,慢慢来;-) 哦,我刚看到你编辑的评论(期待更新评论)。 getBoundingClientRect() 的使用非常聪明,我没有想到(考虑到 39 px 固定宽度也是如此)。我将进行更多测试,看看它是否适用于任何窗口大小和任何数量的 Fluid div。我会尽快给您回复。非常感谢! PS:无论出于何种原因,您的解决方案都适用于 jQuery 3.3.1,但不适用于 3.4.1。 我之前忘记评论了。多么奇怪,我用 jQuery 3.4.1 对其进行了测试,它运行良好。我在 javascript 中稍微更改了答案,并放入了 jQuery 版本 3.4.1。 :)。此外,另一个回答的人以更简单的方式解决了这个问题。他只是忘记从已经扩展的类中删除类,但它工作得很好。我试着从你的代码开始,它变得更复杂了,但解决起来很有趣。【参考方案2】:您需要处理 width 或 calc 函数。 Flexbox 会有一个解决方案。
为了使所有 div 相等(不是第一个),我们使用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>
为您的普通 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/
【讨论】:
谢谢。我必须研究你的案例,因为它与我想要的相反,我不确定如何扭转它(也许它是微不足道的)。默认情况下,我希望所有流体 div 都一样大。只有当我点击一个时,这个才适合它的内容(并且其他流体 div 平均共享剩余空间)。如果我在同一个 div 上再次单击,所有 Fluid div 都会重置,再次平等地共享空间,而不用担心它们的内容。所以,如果我没记错的话,这与你所做的完全相反。尚不确定如何扭转这种情况。 但是,如果这可以逆转,我真的很喜欢您的解决方案是多么优雅和简单,因为不需要弄乱 css 宽度属性。 Flex 似乎只是在内部处理这个问题。也就是说,我有一种预感,以相反的方式实现会更加困难。等着瞧。感谢您的跟进。 @BachirMessaouri 再次变得相当简单。请查看更新版本。 我喜欢你脚本的优雅,但你的解决方案只考虑了我的部分要求。我的要求不仅是要适应内容,还包括在单击流体 div 之前、期间和之后平均共享剩余空间。你的脚本不能很好地解决这个问题,如果不是的话。这就是让整个事情成为大脑燃烧器的原因。我在原始帖子中添加了一张图片,以使我的需求非常清楚。上面的另一个脚本正是从您未能交付的地方开始的。奇怪的是,您的两个脚本的混合可能会起作用。我正在测试,一旦完成,我会回复你。 @BachirMessaouri 您在这里缺少的是,没有人需要为您的案例提供完美的解决方案。Flex transition: Stretch (or shrink) to fit content
是您的问题标题,这个答案是如何做到这一点的简单示例。您有责任为您的案件找到解决方案。【参考方案3】:
经过几个试用版后,这似乎是我最短且最直接的解决方案。
本质上需要做的就是让 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 分为“相关机制”和“仅吸引眼球”部分。
代码被大量注释,所以这里没有太多文字......
怪癖不知何故,当将div
从max-width: 100%
切换到max-width = span width
时,transition
会出现额外的延迟。我已经在 Chrome、Edge、IE11 和 Firefox(都是 W10)中检查了这种行为,并且似乎都有这个怪癖。一些浏览器内部重新计算正在进行,或者transition
时间被使用了两次(“感觉像”)。反之亦然,奇怪的是,没有额外的延迟。
但是,由于转换时间很短(例如 150 毫秒,正如我现在使用的那样),这种额外的延迟并不/几乎不明显。 (对于另一个 SO 问题来说很好......)
$(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
没有可见的跳转。您需要尝试适合自己的方法。
我只在 javascript 代码中添加了document.querySelectorAll()
。
$(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>
【讨论】:
非常感谢您的建议。它不符合我的需要(但它是一个很好的起点),因为当我们单击一个流体 div 时,它很合适,但其他两个 div 不会平均扩展以占用剩余空间。这是问题的 50%。是的,也有这种古怪之处。 Azametzin 的脚本 100% 完成了这项工作,没有任何古怪之处。我最终将他的脚本和使用 flex 过渡而不是 max-width 混合在一起,它就像一个魅力。非常感谢您的帮助! 第二个@Azametzin 的评论:在这个传奇故事中,有很多有趣的日子。 :)。也许下次从图像开始? 好的。我认为解释就足够了,但显然,我错了。图像从昨天开始就在那里。但我明白你的意思。我将在几分钟内编辑我的原始消息以显示我的自定义脚本。非常感谢!【参考方案4】:如果您只需要一行,有一个基于此代码的更简单的解决方案:https://jsfiddle.net/jpeter06/a5cu52oy/ 为列而不是行修改了 css flex:
.container
flex-grow: 10;
flex-shrink: 0;
flex-basis: auto;
display: flex;
flex-direction: row;
width: 100%;
.item min-width:30px;
flex-basis:30px;
overflow-x:hidden;
transition: flex-basis 500ms ease-in-out;
.expanded
flex-basis: 20em;
html, body
width: 100%; height: 100%;
margin: 0; padding: 0; border: 0; overflow: hidden;
html代码:
<div class="container">
<div class="item" style="background: red">a<br/>a<br/>a<br/>a<br/>a<br/>a<br/>a<br/>a<br/></div>
<div class="item" style="background: green">b<br/>b<br/>b<br/>b</div>
<div class="item" style="background: blue">c<br/>c<br/>c<br/>c</div>
</div>
JS 代码:
$(document).ready(function()
$(".item").click(function()
$(this).addClass('expanded');
$(".item").not(this).each(function()
$(this).removeClass("expanded");
);
);
);
【讨论】:
以上是关于Flex 过渡:拉伸(或收缩)以适应内容的主要内容,如果未能解决你的问题,请参考以下文章