打印

RD8027: 各浏览器中 'min-width' 或 'max-width' 值单位为百分比的 IMG 元素的包含块的 'shrink-to-fit' 算法存在差异

作者: 王军

标准参考

1. IMG 元素宽度算法

IMG 元素为拥有内在宽度的内联替换元素,CSS 2.1 第10章明确规定了内联替换元素的宽度算法: 如果元素的 'width' 和 'height' 特性计算值为 'auto' 且元素拥有内在宽度,则使用该内在宽度作为 'width' 的使用值。

关于内联替换元素的宽度计算的更多信息,请参考 CSS 2.1 10.3.2 Inline, replaced elements

2. 'min-width' 和 'max-width'

'min-width' 和 'max-width' 限定内容宽度的范围。定义值如下:

  • <length>:指定一个固定的最小或最大的使用宽度;
  • <percentage>:指定一个确定使用值的百分比。百分比的计算基于生成框的包含块的宽度。如果包含块的宽度是负值,则其使用值是0。 CSS 2.1 没有定义若包含块的宽度依赖于其内元素的宽度时如何布局;
  • none:(仅用于'max-width')对框的宽度没有限制。

下述算法描述了这两个特性如何影响 'width' 特性的使用值:

  1. 首先根据 CSS 2.1 10.3 Calculating widths and margins 中定义的规则计算得到一个临时的使用宽度值;
  2. 如果临时的宽度使用值大于 'max-width' 特性指定的值,则再应用第一条中的规则将 'max-width' 的计算值作为 'width' 特性的计算值;
  3. 如果结果宽度值小于 'min-width' 特性值,则再应用第一条中的规则将 'min-width' 的值作为 'width' 特性的计算值。

关于 'min-width' 和 'max-width' 的更多信息,请参考 CSS 2.1 10.4 Minimum and maximum widths: 'min-width' and 'max-width'

3. 'shrink-to-fit' 算法

shrink-to-fit 可以理解为 "收缩至合适" ,代表了浏览器对于计算后宽度为 'auto' 时的非替换浮动元素宽度计算所要遵循的标准。此外除了非替换浮动元素,对非替换绝对定位元素、非替换行内块元素的宽度为 auto 时也遵循此计算方式。

根据 W3C CSS2.1 规范中的描述,shrink-to-fit 的宽度计算方法与 'table-layout' 特性为 'auto'(即自动表格布局)时对于单元格的宽度计算方法类似。大致为:

  • 计算格式化内容时除了发生明确的换行外不发生换行的时首选宽度(preferred width),以及首选最小宽度(preferred minimum width),比如,尝试所有可能的换行。而 CSS2.1 没有定义精确的算法。
  • 其次,在这种情况下,找出可用宽度(available width),这个宽度为包含块减去 'margin-left'、'border-left-width'、'padding-left'、'padding-right'、'border-right-width'、'margin-right'以及所有相关滚动条的宽度。

综上所述:

shrink-to-fit 的宽度 = min ( max (首选最小宽度, 可用宽度) ,
                首选宽度)

关于 shrink-to-fit 的更多资料,请参考 CSS2.1 规范 10.3.5 Floating, non-replaced elements 中的内容。

问题描述

当 IMG 元素没有设置 'width' 特性且设置了值单位为百分比的 'min-width' 或 'max-width' 特性, 则在各浏览器中该 IMG 元素的包含块的 'shrink-to-fit' 算法存在差异。

造成的影响

该问题可能导致在视觉展现上各浏览器存在差异,也可能造成页面布局混乱。

受影响的浏览器

所有浏览器  

问题分析

该问题的核心原因在于各浏览器在对元素采用 'shrink-to-fit' 算法计算宽度的时候, 选取的首选最小宽度首选宽度不同,因此导致了计算结果的差异。

对于 IE8(S) Firefox Chrome Safari Opera,主要差异来源于首选最小宽度的取值;而 IE7(S) 则比较特殊,下面会单独讨论它的处理方式。 由于 IE6 IE7(Q) IE8(Q) 不支持 'min-width' 和 'max-width' 特性,因此这些浏览器不在本文讨论范围之列。

分析以下代码:

<!doctype html> <html> <head> <style
                type="text/css"> .abs { position : absolute; } .cont { width:40px; background:red; padding:5px;
                position:absolute; left:100px; } .stf { float:left; border:5px solid; padding:5px; background:gold; }
                </style> </head> <body style="background:white;font:12px Arial;"> <div
                style="position:absolute;top:0;">min-width:0%;</div> <div class="cont" style="top:0;">
                <div id="stf1" class="stf"> <img id="img1" src="google_small.gif" style="min-width:0%;" />
                </div> </div> <div style="position:absolute;top:100px;">max-width:0%;</div>
                <div class="cont" style="top:100px;"> <div id="stf2" class="stf"> <img id="img2"
                src="google_small.gif" style="max-width:0%;" /> </div> </div> <div
                style="position:absolute;top:200px;">min-width:50%;</div> <div class="cont"
                style="top:200px;"> <div id="stf3" class="stf"> <img id="img1" src="google_small.gif"
                style="min-width:50%;" /> </div> </div> <div
                style="position:absolute;top:300px;">max-width:50%;</div> <div class="cont"
                style="top:300px;"> <div id="stf4" class="stf"> <img id="img2" src="google_small.gif"
                style="max-width:50%;" /> </div> </div> <div
                style="position:absolute;top:400px;">min-width:150%;</div> <div class="cont"
                style="top:400px;"> <div id="stf5" class="stf"> <img id="img1" src="google_small.gif"
                style="min-width:150%;" /> </div> </div> <div
                style="position:absolute;top:500px;">max-width:150%;</div> <div class="cont"
                style="top:500px;"> <div id="stf6" class="stf"> <img id="img2" src="google_small.gif"
                style="max-width:150%;" /> </div> </div> <div id="info"
                style="position:absolute;top:600px;"></div> <script type="text/javascript"> window.onload
                = function(){ var $ = function(id){return document.getElementById(id);}; function info(msg){
                $("info").innerHTML += "computed width : " + msg + "px<br />"; } function getComputedStyle(id){
                return window.getComputedStyle ? window.getComputedStyle($(id),null) : $(id).currentStyle; } var cs1 =
                getComputedStyle("stf1"), cs2 = getComputedStyle("stf2"), cs3 = getComputedStyle("stf3"), cs4 =
                getComputedStyle("stf4"), cs5 = getComputedStyle("stf5"), cs6 = getComputedStyle("stf6");
                info($("stf1").offsetWidth - parseInt(cs1.paddingLeft) - parseInt(cs1.paddingRight) -
                parseInt(cs1.borderLeftWidth) - parseInt(cs1.borderRightWidth)); info($("stf2").offsetWidth -
                parseInt(cs2.paddingLeft) - parseInt(cs2.paddingRight) - parseInt(cs2.borderLeftWidth) -
                parseInt(cs2.borderRightWidth)); info($("stf3").offsetWidth - parseInt(cs3.paddingLeft) -
                parseInt(cs3.paddingRight) - parseInt(cs3.borderLeftWidth) - parseInt(cs3.borderRightWidth));
                info($("stf4").offsetWidth - parseInt(cs4.paddingLeft) - parseInt(cs4.paddingRight) -
                parseInt(cs4.borderLeftWidth) - parseInt(cs4.borderRightWidth)); info($("stf5").offsetWidth -
                parseInt(cs5.paddingLeft) - parseInt(cs5.paddingRight) - parseInt(cs5.borderLeftWidth) -
                parseInt(cs5.borderRightWidth)); info($("stf6").offsetWidth - parseInt(cs6.paddingLeft) -
                parseInt(cs6.paddingRight) - parseInt(cs6.borderLeftWidth) - parseInt(cs6.borderRightWidth)); }
                </script> </body></html>

以上代码分别测试了 IMG 元素的 'min-width' 和 'max-width' 特性值为 0%、50% 和 150% 的情况下各浏览器如何计算其包含块的宽度, 页面底部使用脚本依次得出这些包含块宽度的最终计算值。图片 "google_small.gif" 内在宽度为 114px,DIV.cont 的宽度为 40px, DIV.stf 为浮动元素,它的 'border-width' 和 'padding' 均为 5px,其宽度需要使用 'shrink-to-fit' 算法决定。根据算法公式,我们能得到可用宽度均为 20px。

各浏览器中表现如下:

IE8(S) Firefox Opera Chrome Safari IE7(S)

修改 DIV.cont 的宽度为 200px,重新计算各包含块宽度,这时我们能得到可用宽度为 180px,各浏览器中表现如下:

IE8(S) Firefox Chrome Safari Opera IE7(S)

综合以上测试结果,我们可以得出在 IE8(S) Firefox Chrome Safari Opera 下设置了 'min-width' 或 'max-width' 值单位为百分比的 IMG 元素的包含块的 'shrink-to-fit' 算法:

IE8(S) Firefox Opera Chrome Safari
min( max(图片内在宽度, 可用宽度), 图片内在宽度)1 min( max(0, 可用宽度), 图片内在宽度)

注1: 本文中可用宽度分别为 20px 和 180px,图片内在宽度为 114px。

从源码中我们也可以发现 Webkit 在计算 'shrink-to-fit' 的首选最小宽度的时候,当遇到 IMG 元素的 'min-width' 和 'max-width' 值单位为百分比的时候, 会直接将首选最小宽度置为0,这和我们得出的结论是一致的。

关键代码位于 RenderImage.cpp 文件的最底部:

void RenderImage::calcPrefWidths() { ASSERT(prefWidthsDirty()); int borderAndPadding =
                borderAndPaddingWidth(); m_maxPrefWidth = calcReplacedWidth(false) + borderAndPadding; if
                (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength)
                m_maxPrefWidth = min(m_maxPrefWidth, style()->maxWidth().value() + (style()->boxSizing() ==
                CONTENT_BOX ? borderAndPadding : 0)); if (style()->width().isPercent() ||
                style()->height().isPercent() || style()->maxWidth().isPercent() ||
                style()->maxHeight().isPercent() || style()->minWidth().isPercent() ||
                style()->minHeight().isPercent()) m_minPrefWidth = 0; else m_minPrefWidth = m_maxPrefWidth;
                setPrefWidthsDirty(false); }

而 IE7(S) 则首先对比 IMG 元素的内在宽度和它的 'min-width'、'max-width',然后得出一个宽度的计算值, 再使用此计算值作为 'shrink-to-fit' 的首选最小宽度和首选宽度参与计算包含块的宽度计算。

解决方案

在包含块的宽度计算需要依赖其内 IMG 元素时,尽量避免给 IMG 元素的 'min-width' 和 'max-width' 特性设置百分比单位的值。

参见

知识库

相关问题

测试环境

操作系统版本: Windows 7 Ultimate build 7600
浏览器版本: IE7
IE8
Firefox 3.6.10
Chrome 7.0.524.0 dev
Safari 5.0.2(7533.18.5)
Opera 10.62
测试页面: IMG_min-max-width_percent.html
本文更新时间: 2010-09-21

关键字

IMG min-width max-width percentage shrink-to-fit