CSSOM中定義的那些尺寸

前言

CSSOM全稱CSS對象模型,涉及兩部分內容,第一部分和操作樣式表相關,第二部分和元素尺寸相關,本文介紹第二部分內容。

Window接口的擴展

  • innerWidth
  • innerHeight
  • outerWidth
  • outerHeight
  • pageXOffset
  • pageYOffset
  • scrollX
  • scrollY

Element接口的擴展

  • clientTop
  • clientLeft
  • clientWidth
  • clientHeight
  • scrollTop
  • scrollLeft
  • scrollWidth
  • scrollHeight
  • getBoundingClientRect()

HTMLElement接口的擴展

  • offsetTop
  • offsetLeft
  • offsetWidth
  • offsetHeight
  • offsetParent

測試環境

  • os:mac10.10
  • 瀏覽器版本
    • chrome:38.0.2125.122
    • safari:8.0
    • Firefox:32.0.2

已有文獻

一些概念

盒模型

正確理解盒模型對掌握CSSOM中各個屬性有幫助,看似簡單,很長一段時間我對這張圖的理解卻并不完全正確。


盒模型

注意觀察每一條邊的范圍,舉個例子:
夾在Left margin edge和Left padding edge之間的是什么?

Viewport

用戶代理UA,可以理解為瀏覽器,提供給用戶的窗口或者是屏幕上的可視區域,用戶可以通過它查看文檔。

我自己的理解:viewport是用戶可以看到的瀏覽器用于展示內容的區域,該區域會隨著瀏覽器resize而改變大小, 一旦瀏覽器窗口大小固定,該值就不會變化,除非引起瀏覽器窗口大小的變化。這篇文章講了如何動態調整瀏覽器窗口的大小

與viewport相關聯的兩類屬性:

window.innerWidth/Height
window.pageXOffset/window.pageYOffset
document.documentElement.clientWidth/clientHeight
document.documentElement.offsetLeft/document.documentElement.offsetTop

不清楚w3c基于何種原因考慮,非要搞兩套幾乎接近的API,這里有PPK的抱怨

下面的資料也值得看看:
坑爹的viewport
Get the browser viewport dimensions with JavaScript

包含塊

擁有class=a的元素的寬度是多少?100%是相對于誰?元素自己嗎?

.a{
width:100%;
}

元素位置和大小的計算是相對于一個特定的長方形,這個長方形叫做包含塊,w3c對包含塊的定義如下:

  • 根元素所在的包含快叫初始化包含塊,對于continuous media它擁有viewport的尺寸和坐標,對于paged media就是頁面區,關于continuous media[連續媒體]和paged media[分頁媒體],官方文獻在這里,直白的講,continuous和paged media是兩種媒體組,什么意思呢?對于我們每天接觸到得信息種類,早有人替我們進行歸類了,例如:投影機代表的媒體類型就是投影,歸入分頁媒體組,而平常使用的電腦屏幕 ,是歸于連續媒體組。

  • 對于其他元素,如果元素的position屬性的值是relative或者static,包含快就是最近的塊級祖先容器的內容區域

  • 如果元素的position屬性值是fixed,對于continuous media就是viewport,對于paged media就是頁面區域。

  • 如果元素的position屬性值是absolute,包含塊是最近的postion屬性值為absolute,relative,fixed的祖先元素,

    • 如果祖先是inline元素,包含塊是圍繞第一個和最后一個inline box產生的padding box的區域
    • 否則,包含塊是祖先元素的padding edge
    • 如果沒有這樣的祖先元素,包含快是初始化包含快

另外補充一個問題:元素的包含快和元素的offsetParent屬性之間是什么關系呢?

Specified, computed, and actual values

CSS2.1 規范,一旦瀏覽器解析了一個文檔并且創建了文檔樹,瀏覽器必須為文檔樹中的每一個元素指定其每一個 CSS屬性,屬性值最終確定需要經過四個步驟:

  1. 用戶給屬性指定的值-specified value

    • 如果樣式層疊的結果是一個值,使用該值
    • 否則,如果屬性可被繼承,并且元素不是根元素,則使用父元素的computed value
    • 其他情況使用屬性的初始化值,每個屬性的初始化值在屬性定義中已經明確給出,例如width的初始化值是auto
  2. 解析為可以用于繼承的值-computed value
    樣式層疊階段,specified value被解析為computed value,例如:uri被轉化為絕對地址,em,ex單位被轉化為px像素,屬性的計算過程不需要瀏覽器去渲染文檔; 屬性值為inherit computed value的計算參考inheritance; 當屬性還沒有被應用到元素上的時,屬性的computed value是可以獲取到的.

  3. 如果必要,將2中的值轉化為絕對計量單位-used value
    computed value的處理應該盡可能不用格式化文檔,然而有一些值,只能在文檔渲染后才能得到,例如:如果元素的寬度被設置為百分比的形式,那該元素的寬度直到其包含塊的寬度確定后才可以確定,used value就是將還沒有解析的有依賴的屬性的computed value解析為絕對單位的值。

  4. 依照宿主環境的限制確定最終的值-actual value
    used value只是按照原則計算得到的用于渲染的值,有些值可能并不會被瀏覽器真正拿去渲染頁面,例如給border設置一個小數值,瀏覽器并不會講小數值應用到元素的border屬性上。

這里補充一下js中獲取元素css屬性的方式:

  1. 獲取元素內聯樣式,這種方式下,只能獲取元素通過內聯方式設置的樣式,對于style方式定義的內嵌樣式,以及外部樣式文件定義的樣式是無法獲取的。

elem.style.width='120px';
elemWidth = elem.style.width;

  1. 獲取內嵌樣式

document.styleSheets[0].cssRules[0].style.marginLeft
document.styleSheets[0].insertRule("body{margin-left:20px}")

  1. 有沒有一種方式可以獲取通過任意方式定義的樣式呢?
  • w3c標準,

window.getComputedStyle

  • IE方式

elem.currentStyle

獲取元素最終的樣式屬性是一件蠻復雜的事情,它涉及到很多細節上的處理,比如段落P設置字體font-size:100%,如果得到P字體的絕對大小呢?

這里有另外一個問題,那就是通過getComputedStyle接口獲取的值,屬于我們介紹的那這種值呢?是computed value,used value,還是actual value呢?

推薦看看jQuery中這幾個函數

  • jQuery.style
  • jQuery.css
  • getStyles
  • curCSS

值得看看的文章:

window的擴展接口

首先這六個屬性是只讀的,你無法對其進行賦值

innerWidth/innerHeight

W3C對innerWidth/innerHeight的定義是:

window.innerWidth = viewport Width + scrollbar's Width

看看jquery中如何獲取window的寬度高度呢?

$(window).width()
$(window).height()

具體如何實現的呢?

if ( jQuery.isWindow( elem ) ) {
return elem.document.documentElement[ "client" + name ];
}

jquery使用clientWidth而不是用window.innerWidth,這個和兼容性有關,window.innerWidth不被IE8支持,而clientWidth有著更好的兼容性

outerWidth/outerHeight

outerWidth/outerHeight返回用戶看到的整個瀏覽器的寬和高,對開發者來說沒什么用

pageXOffset(scrollX)/pageYOffset(scrollY)

頁面滾動的水平/垂直距離,w3c的定義:
pageXOffset返回scrollX屬性的值,scrollX屬性返回相對于初始化包含塊起點的viewport的X坐標

兼容性:pageXOffset/pageYOffset不兼容IE8等低版本瀏覽器,jquery中使用document.documentElement['scrollLeft/scrollTop']兼容:

scrollLeft/scrollTop

Element接口的擴展

clientWidth/clientHeight

w3c對clientWidth的定義:

  1. 如果元素沒有布局盒子,或者盒子是inline方式,返回0;
  2. 如果元素是根元素,并且不在怪異模式下,或者元素是body元素,并且出于怪異模式,返回viewport width,不應不包括scrollbar的寬度;
  3. 返回padding edge width - scrollbar,忽略應用到祖先元素上的變形

clientLeft/clientTop

  1. 如果元素沒有關聯的css layout,或者元素是inline box,返回0;
  2. 返回對應border屬性的寬度加上位于padding edge和border edge之間的滾動條寬度,忽略任何應用在該元素及其祖先元素上的變形。

這里有個小知識點,滾動條出現的位置是:padding edge和border edge之間,滾動條占據的寬度最終

scrollLeft/scrollTop

w3c算法:

  • 如果是根元素,并且文檔處于怪異模式,返回0終止算法;
  • 如果元素是根元素返回scrollX
  • 如果是body元素,文檔處于怪異模式,元素沒有scrolling box,返回scrollX
  • 如果元素沒有布局盒子,返回0終止算法
  • 返回元素滾動區域的x坐標,對齊點(相對于)是元素的左padding edge。

scrollWidth/scrollHeight

w3c算法:scrollWidth

  • 讓viewport的寬度等于viewport的寬度,不包含滾動條的寬度,如果沒有viewport,則為0
  • 如果元素是根元素,并且文檔沒有處于怪異模式,返回max(viewport滾動區域的寬度,viewport的寬度)
  • 如果元素是body元素,文檔處于怪異模式,并且元素沒有與之關聯的滾動盒子,返回max(viewport滾動區域的寬度,viewport的寬度)
  • 如果元素沒有關聯的布局盒子,返回0終止。
  • 返回元素滾動區域的寬度

滾動盒子:
元素和視口(viewport)有一個關聯的滾動盒子,如果元素有滾動機制,或者元素溢出其內容區域,overflow-x 或者 overflow-y 屬性hidden。:CSSBOX

元素的滾動區域(scrolling area ):
滾動區域涉及到溢出方向,對于滾動盒子,最多有兩個溢出方向,下面以右溢出和下溢出的情況給出元素滾動區域的包含的范圍。

對于viewport

  • 上邊界:初始化包含塊的top edge
  • 右邊界:初始化包含塊的right edge,viewport中所有后代元素的right margin edge,二者取最右。
  • 下邊界:初始化包含塊的bottom edge,viewport中所有后代元素的bottom margin edge,二者取最下。
  • 左邊界:初始化包含塊的left edge

對于element

  • 上邊界:元素的top padding edge
  • 右邊界:元素的right padding edge,元素的所有后代元素的right margin edge,不包括那些將元素作為其包含快的盒子,二者取最右。
  • 下邊界:元素的bottom padding edge,元素的所有后代元素的bottom margin edge,不包括那些將元素作為其包含快的盒子,二者取最下。
  • 左邊界:元素的left padding edge

getBoundingClientRect()

返回元素相對于viewport的left、right、top、bottom值。注意這里right和bottom于元素定位內容中的區別。
viewport 又是什么呢?viewport是頁面的可視區域,結合前面介紹的innerWidth/innerHeight是不是就比較容易在腦海中形成viewport的位置圖像了呢!
就是下面這個圖,另外這篇文章推薦讀讀

圖片來自http://levi.cg.am/archives/3099

Element.getBoundingClientRect()返回值中width/height值指的是什么?
測試發現chrome,Firefox,safari返回的是元素border edge區域的寬度和高度,left,top值返回的是元素border edge至viewport的距離

接下來解決一個問題:getBoundingClientRect()返回的是元素相對于viewport的坐標,如何獲得元素相對于文檔的坐標?

ppk曾經抱怨過getBoundingClientRect這個接口沒什么用,John Resig發文說使用getBoundingClientRect是現有兼容性最好的用于獲取元素相對于文檔的坐標的方式。

jquery中offset的定義,主要看函數的返回值:

offset

是否想過為什么是這樣?加頁面的垂直滾動偏移win.pageYOffset好理解,為什么減去html元素的clientTop?

html元素的clientTop是什么?就是html元素的top border width。

回想一下offsetLeft如何定義的 ->(元素left border edge的x坐標減去元素offsetParent的left padding edge),而且jQuery官方API文檔也說了,offset返回的是元素相對于文檔的偏移。

HTMLElement接口擴展

offsetWidth/offsetHeight

w3c算法:offsetWidth

  • 如果元素沒有形成布局盒子,返回0,終止算法。
  • 返回元素border edge的寬度。

offsetLeft/offsetTop

w3c算法:offsetLeft

  • 如果是body元素,或者沒有與之關聯的css layout box存在,返回0,終止算法;

  • 如果元素的offsetParent為null,返回元素left border edge相對于初始化包含塊的x軸坐標

  • 否則,返回元素left border edge的x坐標減去元素offsetParent的left padding edge的x坐標

offsetParent

w3c定義:

如果下面幾條任一條為true,終止,并且返回null

  • 元素沒有關聯的CSS layout box
  • 元素是根元素
  • 元素是body元素
  • 元素position的computed value是fixed

至少下面有一個條件為真,找到則終止算法

  • 祖先元素的position屬性的computed value不是static
  • 祖先元素是body
  • 元素的position屬性值的computed value是static,并且元素的祖先元素是td,th,table元素
  • 返回 null

好奇Jquery中元素的offsetParent方法與W3C關于offsetParent的定義存在較大出入,不曉得jquery是基于什么原因考慮的,測試文件在這個位置

好了,就說到這里了, 關于這篇文章涉及到得測試文件我在github上建了一個項目,地址在這里,可用的測試用例還比較少,歡迎大家fork補充,謝謝!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容