chaojidan

导航

第十九课:盒子模型

css的盒子模型是一个前端工程师必须知道的知识点。一个元素,它的盒子模型是:margin(边界)+border(边框)+padding(填充)+content(内容)。其中除了content,其他三个都有上下左右4个方向,比如:margin-left,padding-left,border-left。一个元素它还有背景颜色和背景图片。当你设置一个元素的背景颜色时,它的背景颜色会延伸到border(你可以通过把border设置为transparent看到)。背景图片会在背景颜色之上,也就是覆盖背景颜色,也会延伸到border。border在背景颜色和背景图像之上,覆盖它们。

当前存在两种盒模型,IE在怪异模式下的盒模型是:width = content + padding +border,我们称之为border-box。而标准浏览器的盒模型:width = content,我们称之为content-box。从设计与计算的角度,尤其是百分比设置宽高时,IE的盒子模型更为合理。因此W3C后来搞了一个box-sizing的CSS3属性,这个属性可以改变元素的盒模型,它接收三个值,content-box(W3C的盒模型),border-box(IE的盒模型),padding-box(width = content+padding)。

 我们通过jQuery来取元素的width时,使用的是content-box,也就是content的宽。一般我们可以使用window.getComputedStyle精确取到元素的宽,但如果元素display为none,或者元素的祖先display为none,又或者元素脱离了DOM树,此方法是获取不到的,旧版本IE的ele.currentStyle.width也差不多。当元素不在DOM树中时,它的offsetWidth为0。opera的可能小于0.但是我们不能把这个offsetWidth<=0作为元素隐藏(display:none)的充分条件,因为用户可能设置元素的width:0(并且padding和border也为0),这样元素的offsetWidth也为0,但是它不是隐藏的。其实offsetWidth = width + padding + border(不管哪种盒模型)。

窗口的宽度:

如果你只需要支持标准浏览器和IE以及以上版本,可以使用window.innerWidth来获取窗口的宽度。手机端就可以使用这种方法。那如果要兼容IE6-IE8,我们可以使用document.documentElement.clientWidth,这个属性用于取得元素的可视区的尺寸,不包括滚动条和被隐藏的部分。document.documentElement是html元素,它是包含整个浏览器窗口的可视元素。远古时代的浏览器把body当做html元素,html元素时隐藏的,所以你会看到一些代码document.documentElement.clientWidth || document.body.clientWidth,但是远古时代的浏览器就不用支持了,因此要获得窗口的宽,兼容模式就是:windowWidth = document.documentElement.clientWidth。如果在手机端,就这样使用:windowWidth  = window.innerWidth。

页面的宽度:

页面的宽度,我们称为文档的宽度,一旦出现滚动条,我们就要考虑加上被隐藏的部分。

因为兼容性问题,我们只有一种方法来获取pageWidth = Math.max(document.documentElement.scrollWidth,document.documentElement.offsetWidth,document.documentElement.clientWidth,document.body.scrollWidth,document.body.offsetWidth);取他们中的最大值,作为页面的宽度,这个方法兼容所有浏览器。

判断浏览器是在标准模式下还是在怪异模式下,通过document.compatMode === "BackCompat"(怪异模式,border-box),"CSS1Compat"(标准模式,content-box)

元素的显隐:

这里只讲display。我们对元素进行display:none隐藏,display:block,显示。但是像thead,tbody,tr等具有特定默认display值的元素,它们一旦设置了display:block,表格马上奔溃。

display的值:block(块元素),none,inline(内联元素),inherit(继承),run-in(填充),table(表格)等。

要想取得元素的默认display属性,我们需要在iframe沙箱中去取。

iframe沙箱的意思是:

iFrame因安全问题而臭名昭著,这主要是因为iFrame常常被用于嵌入第三方内容,而后者则可能会执行某些恶意操作。沙箱通过限制被嵌入内容所允许的操作而提升iFrame的安全性,这种方式将沙箱内容与父页面进行了分离,因此限制了被嵌入内容的权限,不过这并不意味着用户不再需要检测数据以防止潜在的攻击。

沙箱化本地对象的概念,即在全局名称空间外部安全增强的本地 JavaScript 。JavaScript 最强大、但也可能最危险的特性之一是使用自定义方法和属性增强全局对象的能力。

JavaScript 支持通过 prototype 属性直接访问 ArrayStringFufunction、甚至 Object 本身这类全局对象。如果您想添加一个 contains 方法到本地 Array,只需要在 Array.prototype 上定义一个contains 函数。本地 JavaScript 对象上的原型增强是一个有吸引力和影响力的想法。但是在实践中,在修改本地全局对象时出现了很严重的问题。幸运的是,沙箱化本地对象 — 以将它们从全局空间中隔离出来的方法增强本地对象 — 提供了一个安全的选择。沙箱化本地对象的概念可以追溯到 2006 年 Dean Edwards 发表的两篇文章 “使用 <iframe> 沙箱化 JavaScript” 和 “如何将 JavaScript 数组对象划分子类”。他提供了一个巧妙的增加本地对象问题的解决方案,并建议使用一个 iframe 来复制和增强 Array,而不影响全局空间。

var iframe = document.createElement("iframe");

iframe.style.display = "none";

document.body.appendChild(iframe);

frames[frames.length - 1].document.write(   "<script>parent.Array2 = Array;<\/script>");

尽管它并不是一个完美的解决方案,Edwards 的方法激发大家对这个问题的探讨。随着一个新的 JavaScript 的框架 FuseJS 的问世,John-David Dalton 决定使用沙箱化本地对象概念作为他新项目的核心。目的是创建一个稳定的、跨浏览器的沙箱系统作为框架的基础,将沙箱概念扩展到 Array(之前尝试的焦点)之外的其他本地对象。在 2010 年 9 月,他将这个核心代码分离出来作为一个独立的开源项目,称为 Fusebox,并发布了一系列截屏视图来介绍该项目。

3 个独立的技术被用来创建这个沙箱,实现目标:

  • ActiveXObject('htmlfile') 用于 Internet Explorer。
  • Object['__proto__'] 用于 Gecko 和 WebKit 浏览器。
  • Iframes 用于 Opera。

这个截屏视图系列深入介绍了这个沙箱环境创建的技术细节。本文只关注结果Fusebox 的实例化使您可以根据需求操作Fusebox.Array,而不触及全局 Array

var fb = Fusebox();

fb.Array.prototype.contains = function(q){    

  for ( var i = 0, len = this.length; i < len; i++ ) {

     if ( this[i] === q ) { return true; }

  }

   return false;

}

console.log(fb.Array(1,2,3,4,5).contains(4));   >>>true

console.log(typeof Array.prototype.contains);    >>>false

Fusebox.Array 表现得像一个真正数组。

元素的坐标:

元素的坐标就是指其top和left值。元素的style样式具有left,top的属性。但是必须定位了(fixed,relative,absolute),元素的left和top的样式才会有作用,不然就跟没设置是一样的。

元素节点还有一个offsetTop和offsetLeft属性,它们是相对于offsetParent距离,是只读属性,node.offsetLeft,元素没定位,也可以取到。

因此我们可以通过此属性获得此元素的页面的坐标:

function offset(node){

  var left = node.offsetLeft,

    top = node.offsetTop;

  while(node = node.offsetParent){

    left+= node.offsetLeft;

    top+= node.offsetTop;

  } 

  return {left:left,top:top}

}

此外,相对于可视区的坐标也很实用,比如,让弹出框居中对齐。我们可以使用getBoundingClientRect方法,此方法已经是W3C的标准了。此方法返回页面中某个元素(border-box,也就是border是界线)的左,上,右,下分别相对浏览器视窗的位置。它返回一个对象,该对象有4个属性,left,top,bottom,right,标准浏览器下可能会多出width和height这两个属性,这里的width = content+padding+border。这里的left和top跟css的理解一样,但是bottom和right跟css不一样,right是指元素的右边界线与窗口最左边的距离(不是窗口最右边),bottom是指元素下边界线与窗口最上面的距离(不是窗口最下面)。我们通过它也可以很方便的求出元素相对于页面的距离:

var left = this.getBoundingClientRect().left  +  document.documentElement.scrollLeft;

var top = this.getBoundingClientRect().top +  document.documentElement.scrollTop;

将它相对于窗口的距离+滚动距离=页面的距离。但是这里计算了两次浏览器的边框(以上两个操作都会算上浏览器边框),需要减去document.documentElement.clientLeft。

(IE的一些版本会自动为HTML元素,也就是document.documentElement添加2px的border,需要去掉)

offsetParent的定义,在W3C上有详细的说明,但是实际的浏览器没有按照它规定的实现。这里我们按照jQuery实现的方法来定义。jQuery认为元素的offsetParent的position必须为relative或absolute,否则继续往上寻找,没有就返回html。另外jQuery认为position:fixed的元素也有offsetParent,就是当前可视区(一般为window)。

元素的滚动条的坐标

读取元素的滚动坐标时,标准浏览器使用window对象的pageXOffset,pageYOffset这组属性。IE直接使用元素的scrollLeft和scrollTop属性。

设置时,我们只要用window对象的scrollTo方法,里面传入滚动的坐标就行了。

 

 

 

加油!

posted on 2014-12-12 16:34  chaojidan  阅读(886)  评论(0编辑  收藏  举报