补白

在内联元素中,分为替换元素和非替换元素(不了解的同学可以百度一下),非替换元素是不可以设置尺寸的,而替换元素作为特殊的内联元素,由于其自身拥有尺寸属性,所以其的尺寸是可以进行再次设置的。

此文适合有一定CSS使用基础的同学

如果

如果我想实现一个如下图的布局,这是我在做自己博客时遇到的问题:

其左侧三个字为大小1000*1000像素的图片,其拥有属性display:block;height:30%;,更所当然,这三个字撑开了它的父元素的宽度,且其宽度为图片目前的宽度。这样则可以实现左侧侧边栏的宽度是自适应的。

---这是布局想法。

问题

这样的布局可以完美实现,但是在实际使用过程中,我发现了一个特殊的问题,当对窗口进行缩放时,出现了很特殊的问题,现在来贴上代码和它的两个缩放动画来演示一下:

<style>
div{
height: 100%;
float: left;
background: yellow;
}
img{
display: block;
height: 50%;
}
</style> <div>
<img src="......">
</div>

窗口缩小时:

看得出来img改变了高度,宽度也随着改变(设置为block才会发生宽度跟随改变),但是它的父div的宽度并没有发生改变。

再来看看当窗口放大时:

与窗口缩小相似,img的尺寸虽然等比放大了,但是它的父div的宽度并没有发生改变,以至于超出了父div

这是为什么呢?且一起来跟我分析分析

分析

我们来在Timeline中看一下在浏览器resize的时候,发生了什么事情~

并且图中的Recalculate Style的summary中显示影响的元素数量为1

注意在图中下面的Paint操作,下面将Paint的操作主要分为了三部分:

  1. 绘制body
  2. 绘制黄色div
  3. 绘制图片

注意绘制黄色div的尺寸为:(342 - 8),(339 - 8)

而绘制图片的尺寸为: (174 - 8),(174 - 8)

这其中8为body的初始margin,div和img的坐标原点为(8,8)

在绘制的时候,黄色div的宽度并不和图片一样,而且是保持着resize之前的尺寸,这就让我们疑惑了,为什么是这样呢,让我们从文章的开头,从替换元素来说起

Rference from webkit

A replaced element is an element whose rendering is unspecified by CSS. How the contents of the object render is left up to the element itself.

一个替换元素的渲染是和CSS无关的,而是由该元素的自身内容来决定渲染的。

所以这就造成了图片的特殊性,在我们没有对其进行尺寸的样式设定时,图片的大小由自身的渲染规则来确定。而我们对其的高度进行了设置,这则把控制权交给了Layout,那我们再来看看Layout是如何进行工作的:

Rference from webkit

void layout()
{
ASSERT(needsLayout()); // Determine the width and horizontal margins of this object.
... for (RenderObject* child = firstChild(); child; child = child->nextSibling()) {
// Determine if the child needs to get a relayout despite the dirty bit not being set.
... // Place the child.
... // Lay out the child
child->layoutIfNeeded(); ...
}
// Now the intrinsic height of the object is known because the children are placed
// Determine the final height
... setNeedsLayout(false);
}

这个函数是一个递归函数,我们可以看到在对一个元素进行layout时,首先确定了其宽度及水平margin,而再来确定子元素的layout,当子元素位置确定后,再根据其被撑开的高度来作为最后的高度。这样的工作机制保证了页面的竖向排列布局,这种布局也符合我们人类的阅读习惯,这也是在布局中的行布局(元素都以行为基础展示,例如block元素默认宽度为100%,高度由样式或内部元素决定)。

这下则可以解释清楚为什么外部的div没有获得内部img的宽度,即在对内部child进行layout时,已经将div的宽度设置好了,所以这样来说对于外部div来说,内部元素的宽度是无法得到的。其实我们上面看到的在resize后触发的Recalculate Style影响元素个数为1,这个元素其实就是body,更改主body容器的大小后,后面的计算都交给layout去处理。

resize触发的layout是从根容器来进行递归layout的,所以这样我们只能解决子基于父容器去排列的情况,如:p元素中的文字,其中文字的排列是基于父元素p的宽度的,假设resize后p元素宽度变小,我们根据上面的layout函数来说,则先基于p元素的容器元素来设置p的宽度,再根据p元素的宽度进行其中的文字排列,如果文字被挤成了多行,在遍历完成后,再根据子(文字)的高度来决定p元素的高度。

我们再来一个演示,那就是基于宽度的百分比!

<style>
div{
background:yellow;
}
img{
width:30%;
}
</style>
<div>
<img src="......">
</div>

下面是演示效果:

完美!可以看出来效果很完美,这则是基于正常layout函数的过程来进行的布局,也是最常用的。

继续分析

喜欢动脑筋的同学可以读到这一块问题就来了,那么为什么在height:30%的布局下,初次加载没有resize时div为什么可以得到内部的宽度呢???在回答这个问题之前,我们先来看firefox下的表现:

Oh!My God! div竟然没有被撑开,而且宽度为图片的原始宽度,我们先不管这个,来让我们来看一看在Chrome初次加载时发生了什么:

我们可以看到,图片流被一部分一部分的接收,而在接收一定大小的数据后,则会触发Layout,这个Layout则是由img来触发的,它会沿着容器链一路向上进行标记normalChildNeedsLayout或者posChildNeedsLayout位,并接着递归触发layout,而在图像的编码头信息里会包含它的尺寸大小,它会根据这个尺寸并结合img上的style生成计算出img需要占用的尺寸大小,则在后面的图片加载过程中,不会再触发layout,只是去将图片流paint进已经设置好的区域中。

而firefox的黄色div宽度为图片的默认宽度250px,我们可以看出来在Gecko引擎中的layout是没有对img来应用style的,而是直接使用了图像里的编码头信息尺寸,看来webkit还是稍稍地聪明一点,但是他们两个都有一个共同的地方,即对float元素进行重新的宽度计算,这个过程是发生在对其子元素遍历layout结束后来进行的,但是为什么在resize的过程中没有触发对img的宽度重新计算,当黄色div的宽度在初次被初始化后,如果其拥有确定数值并且基础样式为auto时,layout时不再对其宽度进行再次的更改。

解药

这一情况在不同的排版引擎下表现是不一样的,因为其并不是标准的阅读方式,所以也没有统一的标准去规范它,例如在webkit下,我们可以使用js来再次触发img的layout(更改overflow或float等很多值),来使引擎进行再次layout,而这时可以再次对黄色div进行宽度设定,可以推测出该过程在对div进行设置needslayout时先洗冲掉了其的尺寸设定,这样则可以像初始的时候一样获取img的宽度,如下代码:

<!-- 基于上面的代码添加 -->
<button id="btn">add overflow</button>
<script>
var img = document.getElementsByTagName('img')[0]; document.getElementById("btn").onclick = function(){
if(img.style.cssText){
img.style.cssText = "";
}else{
img.style.cssText = "overflow:hidden;"
}
}
</script>

而这一方法在firefox的Gecko中是无法做到的,本来写此文是想在探索这个问题的最优解法,但是到最后才发现这个问题没有最优的解法,都是很麻烦才能去解决。如我的使用方法是,既然你外部div无法探知到内部基于高度百分比的图片变化,那我就监听resize直接用js来给你丫个宽度(可以参考zhiyishou.com)…………虽然很暴力的解决了,但是还是怎么觉得不开心!

这个问题其实主要原因是img内联元素的特殊性,当它的高度改变时,其宽度也会发生改变,如果我们在这个例子中把img换成一个100px*100px的div,则不会发生这么多排版引擎没有预料到的事情。

结语

浏览器对整套的排版全是以行排列,本文分析了浏览器的主要排版过程,希望对你的对浏览器排版有帮助。

对了,如果你有更优的解决方案,记得告诉我!

Finish.

img与特殊布局下对浏览器渲染的剖析的更多相关文章

  1. 【Web动画】CSS3 3D 行星运转 && 浏览器渲染原理

    承接上一篇:[CSS3进阶]酷炫的3D旋转透视 . 最近入坑 Web 动画,所以把自己的学习过程记录一下分享给大家. CSS3 3D 行星运转 demo 页面请戳:Demo.(建议使用Chrome打开 ...

  2. 160826、浏览器渲染页面过程描述,DOM编程技巧以及重排和重绘

    一.浏览器渲染页过程描述   1.浏览器解析html源码,然后创建一个DOM树. 在DOM树中,每一个HTML标签都有一个对应的节点(元素节点),并且每一个文本也都有一个对应的节点(文本节点). DO ...

  3. 浏览器渲染页面过程描述,DOM编程技巧以及重排和重绘。

    一.浏览器渲染页过程描述 1.浏览器解析html源码,然后创建一个DOM树. 在DOM树中,每一个HTML标签都有一个对应的节点(元素节点),并且每一个文本也都有一个对应的节点(文本节点). DOM树 ...

  4. 读书笔记(一)—— 浅析浏览器渲染过程和html中的文件加载

    在构建页面时,我们会在html中载入一个或多个css和js文件.或许大家都已经习惯了"最佳实践"中,css文件应该放在<head>标签中引入,而js文件则是放在< ...

  5. Web标准的简单理解 不同内核浏览器的差异以及浏览器渲染简介(转)

    Web标准是一系列标准的集合.这些标准大概分三方面:结构.表现和行为.结构化主要有HTML, XHTML和XML,表现主要有CSS,行为标准主要包括对象模型,如 W3C DOM.ECMAScript等 ...

  6. 【翻译】浏览器渲染Rendering那些事:repaint、reflow/relayout、restyle

    原文链接:http://www.phpied.com/rendering-repaint-reflowrelayout-restyle/ 有没有被标题中的5个“R”吓到?今天,我们来讨论一下浏览器的渲 ...

  7. 从敲入 URL 到浏览器渲染完成、对HTTP协议的理解

    1. 大致过程 当你这样子回答的时候: 用户输入 url 地址,浏览器查询 DNS 查找对应的请求 IP 地址 建立 TCP 连接 浏览器向服务器发送 http 请求,如果服务器段返回以 301 之类 ...

  8. 浏览器渲染HTML页面步骤

    渲染步骤:浏览器渲染页面时,表示网站资源已经请求成功(要了解查看:浏览器向服务器请求资源过程) 解析HTML以构建dom树--->构建render树--->布局render树---> ...

  9. (前端常考面试题)从敲入 URL 到浏览器渲染完成,到底发生了什么 ?

    前言 小汪最近在看[WebKit 技术内幕]一书,说实话,这本书写的太官方了,不通俗易懂. 但是看完书,对浏览器内核的 WebKit 有了进一步的了解,所以从浏览器内核出发,写这篇文章以记录学到的知识 ...

随机推荐

  1. windows系统同时连接多个openvpn账户

    windows系统同时连接多个openvpn账户. 前提 客户端系统:Windows7 64位 OpenVPN版本:OpenVPN 2.3.10 Windows 64位 一.安装 1.安装目录 D:\ ...

  2. HttpSolrServer 实例管理参考,来自org.eclipse.smila.solr

    http://dev.eclipse.org/svnroot/rt/org.eclipse.smila/trunk/core/org.eclipse.smila.solr/code/为什么要对实例管理 ...

  3. Postman+Postman interceptor的安装和使用-解决把chrome浏览器登录状态同步到postman进行有依赖的接口测试 Postman 使用方法详解

    Postman+Postman interceptor的安装和使用-解决把chrome浏览器登录状态同步到postman进行有依赖的接口测试   问题引入:做接口测试时,有依赖关系的接口往往不好测试( ...

  4. sql2008拒绝了对对象 (数据库 ,架构'dbo')的SELECT权限

    连接sql2008的时候,出现了这种一直报权限错误:错误截图如下: 所见效果描述:在windows身份验证的 情况下登陆进去数据库的表都是可以打开的,当换到sa或者别的账号登陆进去的时候这个时候我们点 ...

  5. centos7下mysqldump+crontab自动备份数据库

    1.创建文件夹(存放备份数据) mkdir /bak mkdir /bak/mysqldata 2.编写脚本 vi /usr/sbin/bakmysql.sh 脚本内容如下 DATE=`date +% ...

  6. servlet响应解析

    response对象可以设置一些响应信息 1)设置状态码 response.setStatus(int) 2)设置响应头信息.定时刷新或者间隔 n 秒后跳转 response.setHeader(&q ...

  7. Jquery使容器自适应浏览器窗口

    一.几个关键点 1:当文档大小改变时可以通过哪个事件来触发? resize([Data], fn) 可传入data供函数fn处理. 示例: $(window).resize(function(){ a ...

  8. vue 发送ajax请求

    一. 简介 1.vue本身不支持发送AJAX请求,需要使用vue-resource(vue1.0版本).axios(vue2.0版本)等插件实现 2.axios是一个基于Promise的HTTP请求客 ...

  9. mysql 主主复制(双主复制)binlog-do-db

    [root@DB ~]# grep "binlog-do-db" /etc/my.cnf binlog-do-db = test [root@DB-S ~]# grep " ...

  10. svn lock 锁定文件

    [root@NGINX-APACHE-SVN pro]# svn lock zh_CN.UTF-8 'zh_CN.UTF-8' locked by user 'svnroot'. [root@NGIN ...