1.背景
最近在做一个业务,大致是页面上有一个限时秒杀区域,这个秒杀区域内有三个商品卡片,这三个卡片要在区域内平均分布。
这还不简单? 有手就会,先来搭个demo:
<div class="wrapper">
<div class="item">商品卡片</div>
<div class="item">商品卡片</div>
<div class="item last">商品卡片</div>
</div>
.wrapper {
display: flex;
justify-content: space-between;
align-items: center;
width: 400px;
height: 300px;
background: rosybrown;
border-radius: 20px;
}
.item {
display: flex;
justify-content: center;
align-items: center;
flex: 1; // ----> 注意这个
margin-right: 10px;
height: 150px;
background: pink;
border-radius: 20px
}
.last{
margin-right: 0;
}
搞定!
现在流行模块化开发,因为不是真正的业务代码,就暂且简单的把上面的.wrapper当成一个父组件,里面的商品卡片当成一个个子组件就好了。
里面的商品卡片组件不是我开发的,在这个需求中我的任务是负责页面一些整体的东西比如分享、背景图、调用客户端方法设置页面标题等(简单来说就是打杂。。咳咳,打工人要有打工人的觉悟),具体的商品卡片使用其他同事之前开发的业务组件,当我把商品卡片组件放入我的wrapper组件的时候,问题来了:
大概的代码是这样的:
<div class="wrapper">
<div class="item">
<div class="content">商品卡片商品卡片商品卡片商品卡片商品卡片商品卡片商品卡片商品卡片</div>
</div>
<div class="item">商品卡片</div>
<div class="item last">商品卡片</div>
</div>
.wrapper {
display: flex;
justify-content: space-between;
align-items: center;
width: 400px;
height: 300px;
background: rosybrown;
border-radius: 20px;
}
.item {
display: flex;
justify-content: center;
align-items: center;
flex: 1;
margin-right: 10px;
height: 150px;
background: pink;
border-radius: 20px
}
.content {
width: 500px; =====> 假设这是商品卡片内容的宽度。
}
.last{
margin-right: 0;
}
出现了类似上图的情况,因为要均分,所以商品卡片的宽度其实不好写死,也就是说,商品卡片组件的宽度是由里面的内容决定的,这很好理解。
但是!
我不是设置了flex:1 吗? 难道内容不应该保持在三分之一内吗,怎么向外延申了呢?(flex布局很常见的一个误区:给子元素设置了flex属性,很自然的就认为,它会按比例分配父元素的宽度)。
因为真实的业务场景比现在这个demo复杂的多,当时看到样式图片啥的都变形了,眉头一皱,大事不妙,容我排查两个小时(摸鱼中...)
于是开始上MDN查flex:1的具体细节:
也就是说flex:1其实就相当于:
这个我都知道,但是对于flex-grow、flex-shrink、flex-basis的具体细节,我还是有点忘记了的,于是继续查:
2.哎嘿嘿嘿,知识点来喽~
flex-grow
首先看flex-grow:
flex-grow决定了子盒子在拥有自身宽度之后,占父盒子剩余宽度的比例。
假如父盒子是400px,三个子盒子宽度都是100px,那么剩余宽度就是100px 如果所有的子盒子flex-grow都是1,那么会平分这100px。
假如有一个盒子flex-grow是2,其余两个盒子flex-grow是1,那么会把100px分为4份,flex-grow:2的盒子会得到二分之一,即50px,其余两个盒子各得到四分之一,即25px。
flex-shrink
flex-shrink决定了当子盒子的宽度之和大于父盒子的宽度时,子盒子要怎么去压缩。
同样假如父盒子的宽度是400px,有三个子盒子a、b、c,每个盒子宽度是200px,那么子盒子的宽度之和600px比父盒子的宽度400px还要大于200px,所以子盒子会压缩。
当子盒子a的flex-shrink设置为0时,意味着这个子盒子不压缩,既然不压缩,那就是原本的宽度200px,那其他的两个盒子b、c就只能挤在另外200px的空间里了,假如子盒子b 的flex-shrink设置为1,子盒子c的flex-shrink设置为2,那么就相当于b盒子压缩到剩余宽度的三分之二,,c盒子压缩到剩余宽度的三分之一,那么子盒子b的宽度就应该为133.33px(理论上),c盒子的宽度就应该为66.66px(理论上)。
flex-basis
flex-basis没什么好说的类似于width属性,决定了子盒子在主轴方向上的初始大小,如果子盒子同时设置了flex-basis和width,flex-basis的优先级更高。
3.flex:1
回到问题上,在我的代码中,我给每个商品卡片都设置了flex:1 这意味着每个商品卡片的flex-grow为1、flex-shrink为1、flex-basis为0
当第一个商品卡片中的内容没有超过父元素三分之一的时候,表现一切正常
但是一旦第一个商品卡片的内容超过了三分之一,那么三个商品卡片的总宽度就大于父元素,既然子元素的总宽度和大于父元素的宽度,那么flex-hrink:1会生效,三个子元素应该会压缩。
事实上,子盒子确实压缩了,我们看:
在第一个商品卡片的内容没有超出父元素的三分之一时,三个盒子的宽度很正常,但是超出之后,后面两个商品卡片的宽度压缩到了只有文字内容的宽度,可见它们确实压缩了。
所以目前为止的结论是,商品卡片内容超父元素宽度时,商品卡片确实压缩了,但是没有压缩到父元素宽度范围内。
那是什么导致商品卡片压缩之后还是超出父元素的宽度,有没有办法解决这个问题呢?
4.真相是...
经过我好一番查找资料,真相渐渐浮出水面...
原来,浏览器默认为flex容器的子元素设置了 "min-width: auto;", 即flex子元素的最小宽度不能小于其内容的宽度
那么就是说商品卡片的宽度是min-width: auto的计算宽度 + flex:1 的计算宽度之和,而不是只是flex:1的宽度。
那么这就好办了,我们只需要为第一个商品卡片设置min-width:0,去覆盖浏览器默认设置的min-width: auto,将商品卡片的宽度变为只由flex:1来决定就好了。
另外,还有一个办法,就是g给第一个商品卡片添加overflow:hidden属性,利用BFC不会对盒子外部的布局产生影响的特性,将内容限制在第一个商品卡片正常宽度范围内。
...
累了,码完这篇文章,不得不感慨,CSS真是博大精深,是真细啊!