打印

BW5011: Chrome Safari 中拥有 'float' 和 'overflow' 特性值不为 'visible' 的元素在祖先容器布局大小被更改后存在渲染问题

作者:钱宝坤

标准参考

无。

问题描述

WebKit 内核浏览器中,当使用脚本对拥有 'float' 特性以及 'overflow' 特性值不为 'visible' 特性的布局进行变更盒模型操作后,Chrome Safari 的 Reflow 计算会出现偏差,导致之后的 Repaint 操作时,无法渲染出布局内容。

造成的影响

由于页面局部没有被更新,可能会导致局部内容丢失,处于丢失内容中的功能将不可用。

受影响的浏览器

Chrome Safari  

问题分析

从现有资料中得知,Reflow1 作为一个名词概念,最初出是在 Mozilla.org 中被提及,它表示了所有浏览器的布局引擎对元素实际位置的计算过程。在 Reflow 计算完成后,渲染引擎会根据布局数据绘制出页面内容,于是最终现实效果出现在浏览器窗口中,这个渲染过程被称作 Repaint1。如果页面中发生了影响整体布局的情况,将触发 Reflow 以及其后的 Repaint 动作,如:

  • 调整窗口大小
  • 改变字体
  • 影响到盒模型大小的修改
  • 文档树结构变更

详细说明可以参考 Mozilla.org 站点内文章:Notes on HTML Reflow

注 1: 任何浏览器都存在 Reflow 及 Repaint 概念,有可能各个浏览器厂商有各自不同的命名方式,为避免混淆,本文中统一使用 Reflow、Repaint 单词来说明布局计算和布局渲染这两个概念。

我们看一个实例,例子中构造了出现这个问题必须的 5个条件:

  • 存在三层嵌套的布局标签;
  • 最外层元素设置了布局高度;
  • 中层元素的高度要大于最内层元素的高度;
  • 内层元素设置了 'float' 特性以及非 'overflow:visilbe' 特性值;
  • 对最外层布局动态修改高度时,修改的高度小于最内容器高度。

代码如下:

<h2>产生问题的布局</h2>
<input type="button" onclick="document.getElementById('A').style.height='29px'" value="祖先容器高度变小" />
<input type="button" onclick="document.getElementById('A').style.height='60px'" value="祖先容器高度恢复"  />
<input type="button" onclick="document.getElementById('B').style.position='relative'" value="修复此问题"  />
<div id="A" style="height:60px;background:gold;">
  <div style="height:31px;background:red;">
    <div style="overflow:auto;float:left; background:gray;width:500px; height:30px;" id="B" >
      内容文字<br/>
    </div>
    <span>环绕文字</span>
  </div>
</div>

高亮部分标注了产生问题的几个条件,此代码运行后,依次点击 "祖先容器高度变小"、"祖先容器高度恢复" 按键,使页面触发 Reflow 重绘布局,然后来看各浏览器中渲染情况。

  IE6 IE7 IE8 Firefox Opera Chrome Safari
按键点击前 IE6 IE7 IE8 Firefox Opera >Chrome Safari
按键点击 "祖先容器高度变小" 和 "祖先容器高度恢复" 后 IE6 IE7 IE8 Firefox Opera Chrome Safari
修复按键点击后 IE6 IE7 IE8 Firefox Opera >Chrome Safari

从表中对比可以看出,当最外层布局块高度变更到比最内层布局块小后,再还原为比最内层布局块高度大的时候:

  • IE6 IE7 IE8 Firefox Opera 中,整体布局无变化。
  • Chrome Safari (WebKit 渲染引擎) 中,在触发 Reflow 后没有将最内层布局块显示。从具体表现形式中可以看到,"环绕文字" 这个行内元素依然在原始位置没有变化,说明消失元素的布局依然存在,只是没有被浏览器 Repaint 。点击修复按键后,问题布局块被设置了 'position:relative' 特性值,他成为了相对定位块,使页面布局发生变化。由此触发 Reflow/Repaint 操作后,布局出现;此时再点击 "祖先容器高度变小"、"祖先容器高度恢复" 按键时,问题不能被重现,说明已被修复。

以上代码中仅说明了最内层子元素在 'overflow:auto' 和 'float:left' 特性值设置时的表现情况,下面看在 'overflow:hiddden' 和 'float:right' 特性值设置时的表现。

我们将代码稍作调整:

<h2>最内容器 'float:right' 'overflow:auto'</h2>
<input type="button" onclick="document.getElementById('C').style.height='29px'" value="祖先容器高度变小" />
<input type="button" onclick="document.getElementById('C').style.height='60px'" value="祖先容器高度恢复"  />
<input type="button" onclick="document.getElementById('D').style.position='relative'" value="修复此问题"  />
<div id="C" style="height:60px;background:gold;">
  <div style="height:31px;background:red;">
    <div style="overflow:auto;float:right; background:gray;height:30px;"  id="D"  >
      内容文字<br/>
    </div>
    <span>环绕文字</span>
  </div>
</div>
<h2>最内容器 'float' 'overflow:hidden'</h2>
<input type="button" onclick="document.getElementById('E').style.height='29px'" value="祖先容器高度变小" />
<input type="button" onclick="document.getElementById('E').style.height='60px'" value="祖先容器高度恢复"  />
<input type="button" onclick="document.getElementById('F').style.position='relative'" value="修复此问题"  />
<div id="E" style="height:60px;background:gold;">
  <div style="height:31px;background:red;">
    <div style="overflow:hidden;float:left; background:gray;height:30px;"  id="F"  >
      内容文字<br/>
    </div>
    <span>环绕文字</span>
  </div>
</div>

各览器中渲染情况:

'float:right' IE6 IE7 IE8 Firefox Opera Chrome Safari
按键点击前 IE6 IE7 IE8 Firefox Opera Chrome Safari
按键点击 "祖先容器高度变小" 和 "祖先容器高度恢复" 后 IE6 IE7 IE8 Firefox Opera Chrome Safari
修复按键点击后 IE6 IE7 IE8 Firefox Opera Chrome Safari

'overflow:hidden' IE6 IE7 IE8 Firefox Opera Chrome Safari
按键点击前 IE6 IE7 IE8 Firefox Opera >Chrome Safari
按键点击 "祖先容器高度变小" 和 "祖先容器高度恢复" 后 IE6 IE7 IE8 Firefox Opera Chrome Safari
修复按键点击后 IE6 IE7 IE8 Firefox Opera >Chrome Safari

在进一步修改验证之后,可发现 WebKit 渲染引擎的这个 Reflow/Repaint 问题与最内子元素同时设置 'float' 和 'overflow' 特性值不为 'visible' 有关。

根据这个特性可以继续验证出,如果去除 float' 和非 'overflow:visible' 特性值中任意一个,就会不出现此渲染问题。

或者在不修改特性值设定情况下,使最外层祖先容器高度设置变更大于等于最内容器计算高度,以及中层容器高度设置小于最内容器计算高度时,同样不会出现渲染问题。

具体验证代码不在文档内给出,读者可以运行下方的 测试用例 页面,自行验证。

由此可以得出结论:在本问题情况下,WebKit 渲染引擎浏览器执行 Reflow 计算后由于某种原因,执行 Repaint 重绘机制不完善,导致局部内容没有被重绘。

解决方案

应尽量避免出现类似的布局结构,如果无法避免,并出现了上文所描述的布局异常,根据实际情况可以使用以下方案解决:

  • 对出现问题的元素设置 'position:relative' 特性值可以避免这个现象。
  • 为具体出现问题的布局块加入 'opacity:.99' 特性值,迫使浏览器因计算透明度,从而避免由 Reflow 现象产生后不重绘局部布局的情况。
  • 也可以通过改变盒模型大小等手段迫使浏览器进行 Reflow 后的 RePrint 操作,比如将布局盒的高度或宽度修改,然后再使用 setTimeout(function...,0) 语句在当前脚本流执行完成以及 Reflow 完成时,将盒模型改回需要的大小。

参见

知识库

相关问题

测试环境

操作系统版本: Windows 7 Ultimate build 7600
浏览器版本: IE6
IE7
IE8
Firefox 3.6.10
Chrome 7.0.544.0 dev
Safari 5.0.2
Opera 10.62
测试页面: webkit_lost_layout.html
本文更新时间: 2010-10-12

关键字

webkit Chrome Safari float overflow hidden reflow layout repaint