前言
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
已有文獻
- 最權威的官方文檔,不過是英文的,中文版發現有一些章節刪減。
- ppk大神的兼容表格
- Firefox開發者中心
- 其他個人BLOG也有精品,例如:CSSOM視圖模式相關整理 , CSSOM與getOffset函數
一些概念
盒模型
正確理解盒模型對掌握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屬性,屬性值最終確定需要經過四個步驟:
-
用戶給屬性指定的值-specified value
- 如果樣式層疊的結果是一個值,使用該值
- 否則,如果屬性可被繼承,并且元素不是根元素,則使用父元素的computed value
- 其他情況使用屬性的初始化值,每個屬性的初始化值在屬性定義中已經明確給出,例如width的初始化值是auto
解析為可以用于繼承的值-computed value
樣式層疊階段,specified value被解析為computed value,例如:uri被轉化為絕對地址,em,ex單位被轉化為px像素,屬性的計算過程不需要瀏覽器去渲染文檔; 屬性值為inherit computed value的計算參考inheritance; 當屬性還沒有被應用到元素上的時,屬性的computed value是可以獲取到的.如果必要,將2中的值轉化為絕對計量單位-used value
computed value的處理應該盡可能不用格式化文檔,然而有一些值,只能在文檔渲染后才能得到,例如:如果元素的寬度被設置為百分比的形式,那該元素的寬度直到其包含塊的寬度確定后才可以確定,used value就是將還沒有解析的有依賴的屬性的computed value解析為絕對單位的值。依照宿主環境的限制確定最終的值-actual value
used value只是按照原則計算得到的用于渲染的值,有些值可能并不會被瀏覽器真正拿去渲染頁面,例如給border設置一個小數值,瀏覽器并不會講小數值應用到元素的border屬性上。
這里補充一下js中獲取元素css屬性的方式:
- 獲取元素內聯樣式,這種方式下,只能獲取元素通過內聯方式設置的樣式,對于style方式定義的內嵌樣式,以及外部樣式文件定義的樣式是無法獲取的。
elem.style.width='120px';
elemWidth = elem.style.width;
- 獲取內嵌樣式
document.styleSheets[0].cssRules[0].style.marginLeft
document.styleSheets[0].insertRule("body{margin-left:20px}")
- 有沒有一種方式可以獲取通過任意方式定義的樣式呢?
- 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
值得看看的文章:
- css-cascade-3官方文檔
- 設置 CSS 的特性值
- PPK Get Styles
- 關于HTML Object中三個Style實例的區別
- Computed vs Cascaded Style
- IE 取得css屬性的絕對像素值
- JQuery中使用DE的絕對像素值hack的一處疑問
- runtimeStyle object
- 盡管源碼版本較低,但是還是值得參考一下:jQuery源碼-CSS樣式相關部分
- jquery css() 方法的分析 于bug .
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']兼容:
Element接口的擴展
clientWidth/clientHeight
w3c對clientWidth的定義:
- 如果元素沒有布局盒子,或者盒子是inline方式,返回0;
- 如果元素是根元素,并且不在怪異模式下,或者元素是body元素,并且出于怪異模式,返回viewport width,不應不包括scrollbar的寬度;
- 返回padding edge width - scrollbar,忽略應用到祖先元素上的變形
clientLeft/clientTop
- 如果元素沒有關聯的css layout,或者元素是inline box,返回0;
- 返回對應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的位置圖像了呢!
就是下面這個圖,另外這篇文章推薦讀讀
Element.getBoundingClientRect()返回值中width/height值指的是什么?
測試發現chrome,Firefox,safari返回的是元素border edge區域的寬度和高度,left,top值返回的是元素border edge至viewport的距離
接下來解決一個問題:getBoundingClientRect()返回的是元素相對于viewport的坐標,如何獲得元素相對于文檔的坐標?
ppk曾經抱怨過getBoundingClientRect這個接口沒什么用,John Resig發文說使用getBoundingClientRect是現有兼容性最好的用于獲取元素相對于文檔的坐標的方式。
jquery中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補充,謝謝!