樣式模塊分爲兩大塊,精確獲取樣式值與設置樣式,精確是用於修飾符獲取的。因爲樣式分佈爲外部樣式,內部樣式與行內樣式,再加個impotant對選擇器的權重的干擾,咱們實際很難看到元素是應用了那塊的樣式。所以,樣式模塊,80%的比重在於獲取這一塊,像offset,滾動條也歸入這一塊。javascript
大致上,咱們在標準瀏覽器是使用getComputedStyle,ie6-8使用currentStyle來獲取元素的精確樣式。不過,getComputedStyle並不掛在元素上,而是window的一個API,它返回一個對象,能夠選擇使用getPropertyValue方法傳入連字符風格的樣式名取得其值 ,或者屬性法+駝峯風格的樣式名去取值。但考慮到currentStyle也是使用屬性法+駝峯風格,咱們就統一使用後者。css
var getStyle = function (el, name) { if (el.style) { name = name.replace(/\-(\w)/g,function(all,letter) { return letter.toUpperCase(); }); if (window.getComputedStyle) { //getComputedStyle的第二個僞類是用於對付僞類的,如滾動條,placeholder, //但ie9不支持,由於咱們只管元素節點,上面的el.style過濾掉了 return el.ownerDocument.getComputedStyle(el, null)[name] } else { return el.currentStyle[ name ] } } }
設置樣式則更是沒有難度,直接el.style[name]=value搞定。html
但框架要考慮的東西更多,如兼容性,易用性,擴展性java
1.樣式名要同時支持連字符風格(css的標準風格),與駝峯風格(DOM的標準風格)node
2.樣式名要進行必要的處理,如float樣式與css3帶私有前綴的樣式css3
3.若是框架是仿jQuery風格,要考慮set all get fristgit
4.設置樣式時,對長度寬度能夠考慮直接處理數值,好比由框架智能補上px單位。github
5.設置樣式時,對於長寬度能夠考慮傳入相對值,如"-=20"web
6.對於個別的樣式特殊處理,如IE下的z-index,opacity,user-select,background-position,top,leftchrome
7.基於setStyle,getStyle的擴展,height,width,offset等方法。
在學習過程當中,對css模塊與css_fix模塊進行展開。涵蓋的內容至關於jQuery的css,offset,demensions模塊,也至關於EXT4的Element.style Element.scroll, Element.position模塊。
https://github.com/jiayi2/mass-Framework/blob/master/css.js
https://github.com/jiayi2/mass-Framework/blob/master/css_fix.js
其中,css_fix用於兼容舊版的IE,涉及的API有css,cssName,cssNumber,cssHooks,height,width,innerHeight,innerWidth,outerWidth,offset,position,scrollTop,scrollLeft,show,hide,toggle,offsetParent,scrollParent。
放在$上爲靜態方法,放在$.fn上的爲原型方法。獨立於這二者的爲私有方法或對象。
一.樣式名的修正
不是全部的樣式名都是用正則簡單的處理一下就行,這裏存在三個陷阱,float對於的javascript屬性存在兼容性問題。css3帶來的私有前綴,ie的私有前綴不和流等問題。
私有前綴是css3的實現和標準滯後所帶來的問題,其實私有前綴-ms-在ie8時代就存在了,-khtml-就更早了。如今私有前綴以下:
瀏覽器 | ie | firefox | chrome | safari | opera | Konqueror |
前綴 | -ms- | -moz- | -webkit- | -webkit- | -o- | -khtml- |
2013年,google嫌webkit內核態臃腫,決定本身單幹,取名blink,並在chrome28起使用此內核,爲了減輕用戶負擔,仍是使用webkit作前綴,目前,2013年,google嫌webkit內核態臃腫,決定本身單幹,取名blink,並在chrome28起使用此內核,爲了減輕用戶負擔,仍是使用webkit作前綴,目前,使用-webkit-前綴的有Opera,Safari,Chrome三家。
上述的這些前綴加上樣式名再駝峯化就是真正可用的樣式名,好比-ms-transform -> MsTransform, -webkit-transform ->WebKitTransform, -o-transform -> Otransform 。
但這些實驗性的樣式會早晚退出歷史舞臺,它們會卸載掉前綴重新亮相,好比ff17下就能夠直接使用transfrom。但光是這樣不夠,還有第三個問題:IE下的-ms-trabsform的javascript屬性名爲msTransform,所以,搞定這個正則就特別複雜,咱們要動用一個函數經過偵測手段獲取它。在mass,它叫cssName,在jQuery,它叫vendorPropName。因爲特徵偵測是DOM操做,消耗很大,所以獲取後就應該緩存起來,避免重複檢測,這個對象在mass稱爲cssMap.
var prefixes = ['', '-webkit-', '-moz-', '-ms-', '-o-']; var cssMap = { "float": $.support.cssFloat ? 'cssFloat' : 'styleFloat', background : "backgroundColor" }; function cssName(name, host, camelCase) { if (cssMap[name]) { return cssMap[name]; } host = host || document.documentElement for (var i = 0, n = prefixes.length; i < n; i++) { camelCase = $.String.camelCase(prefixes[i] + name); if (camelCase in host) { return (cssMap[name] = camelCase) } } return null; }
prefixes的順序設置得至關有技巧,「」表示沒有私有前綴, 此樣式已經標準化,全部在最前。-webkit- , -o-依次排開。
經過上面的函數,咱們只需傳入一個參數,就能夠獲得真正可用的樣式名了。
一:個別樣式的特殊處理
讓咱們來展現cssHooks的價值所在,它是專門用於對付那些有兼容性的問題,不按常規出牌的奇葩樣式。cssHooks爲一個普通的對象。每一個屬性名都以xxx+":set"或xxx+":get"命名,值爲處理函數。
1。opacity。
.opacity{opacity:.5}
opacity會讓背景與內容變得透明,想內容不透明,就要用rgba與hsla。不過它們是一種值的格式,並非樣式。
ie依賴濾鏡DXImageTransform.Microsoft.Alpha,不過IE提供了一個簡短的
.opacity{
fliter:alpha(opacity=40)
}
在ie6 7須要注意,爲了使得透明設置生效,元素必須是「有佈局」。一個元素能夠經過一些css屬性來使其被佈局,有如width和position.關於微軟專有的hasLayout屬性詳情,以及如何觸發它,能夠看下面的連接。
http://www.blueidea.com/tech/site/2006/3698.asp
2.background-position
舊版ie中,ie只支持backgrounPositionX與backgroundPositionY,不支持backgroundPosition。實現很簡單,分別提取backgrounPositionX與backgroundPositionY,而後拼合他們。
adapter["backgroundPosition:get"] = function(node, name, value) { var style = node.currentStyle; return style.backgroundPositionX + " " + style.backgroundPositionY };
3.z-index
z-index是一個並不難理解的屬性,但它由於錯誤的假設使一些開發人員陷入混亂。混亂髮生的緣由是由於z-index只能工做在被明肯定義了absolute,fixed,relative這三個定位屬性元素中,它會讓元素沿着z軸進行排序(z軸的起點爲父節點所在的層,終點爲屏幕)。
z-index在下拉菜單,tooltip,燈箱效果中,相冊與拖動中常常被使用。爲了讓目標控件排在最前,咱們須要得知他們的z-index,而後有目的的改z-index,而後有目的的改z-index或重排元素(將目標元素移除dom樹,再插入父元素最後的一個元素以後)。
想獲取z-index,這裏應對一個特殊狀況,目標元素沒有被定位,須要往上回溯到其祖先定位元素。若是找到,就返回祖先的z-index.若是最後也沒找到,就返回0.
adapter["zIndex:get"] = function (node) { while (node.nodeType !== 9) { //即便元素定爲了,但若是z-index的值設置爲 "aaa"這樣的無效值。瀏覽器都會返回auto //若是沒有指定z-index值,ie會返回0。其它返回auto var position = getter(node, "position") || "static"; if (position !== "static") { // <div style="z-index:-10;" ><div style="z-index:0;"></div></div> var value = parseInt(getter(node, "zIndex"),10); if (!isNaN(value) && value !== 0) { return value; } } node = node.parentNode; } return 0 }
4.元素的隱顯
元素的隱藏與顯示在頁面上實現由不少辦法,這裏只說明下display。display爲none時,它再也不佔有物理空間,附近的元素就順勢挪過去,好比手風琴效果,下拉效果都依賴於此。
$.fn.show = function() { return this.each(function() { this.style.display = ""; }) } $.fn.hide = function() { return this.each(function() { this.style.display = "none"; }) } $.fn.toggle = function() { return this.each(function() { this.style.display = isHidden(this) ? "" : "none"; }) } $.fn.isHidden = function(node) { return node.sourceIndex === 0 || getter(node, "display") === "none" || !$.contains(node.ownerDocument, node); }
而後咱們建立一個方法,把$.fn.show, $.fn.hide, $.fn.toggle功能所有交給它作。
function toggleDisplay(nodes, show) { var elem, values = [], status = [], index = 0, length = nodes.length; //因爲傳入的元素們可能存在包含關係,所以分開兩個循環來處理,度土匪循環用於取得當前值或默認值 for (; index < length; index++) { elem = nodes[index]; if (!elem.style) { continue; } values[index] = $._data(elem, "olddisplay"); status[index] = $.isHidden(elem); if (!values[index]) { values[index] = status[index] ? $.parseDispaly(elem.nodeName) : getter(elem,"display"); $._data(elem,"olddisplay",values[index]); } } //第二個循環用於樣式設置,-1爲toggle,1爲show,0爲hide for (index = 0, index < length; index++) { elem = nodes[index]; if (elem.style) { continue; } show = show === -1 ? !status[index] : "none"; } return nodes }
最後,咱們只要稍微在外面一層就能實現與jQuery功能同樣的show,hide,toggle,這樣作還有一個好處,就是接口與實現分離,實現隱藏於內部。保持既有功能,不受太多制約,安心優化與升級。
$.fn.show = function() { return toggleDisplay (this, 1); }; $.fn.hide = function() { return toggleDisplay (this, 0); } //state爲true時,強制所有顯示,爲false時,強制所有隱藏 $.fn.toggle = function() { return toggleDisplay (this, typeof state === "boolean" ? state : -1) }
5.元素的座標(基於getBoundingClientRect方法)
元素的座標指其top與left值。node.style恰逢有這兩個屬性,但它只有被定位了才生效。不然在其餘瀏覽器下都返回auto.它的offsetTop,offsetLeft也是有效的。它是相對offsetParent的距離。咱們一級一級的向上累加,就能獲得相對頁面的座標,亦有人稱之爲元素的絕對座標。
function offset (node) { var left = node.offsetLeft, top = node.offsetTop; do { left += node.offsetLeft; top += node.offsetTop; } while (node = node.offsetParent); return { left: left, top: top } }
此外,相對於可視的座標也很實用,好比讓彈出窗口居中對齊。之前實現這個計算量很是巨大,自從IE的getBoundingClientRect方法被挖掘出來之後,簡直是小菜一碟、如今列入w3c標準,無兼容性之憂。此方法能獲取頁面中某個元素的border-box的左、上、右、下的相對瀏覽器視窗的位置。
它返回一個Object對象,該對象確定有4個屬性,top,left,right,bottom,標準的瀏覽器還多出width和height這兩個屬性,這裏的top,left和css的理解類似,width,height就是元素自身的高,可是right,bottom和css的理解有所不同。以下:(-top - left,+bottom +right等自行測試)
https://msdn.microsoft.com/en-us/library/hh781509(v=vs.85).aspx
上面是微軟MSDN的文章,用於演示CSSOM的各類屬性.上面頁面中擁有一個相對定位的紅色元素。藍色元素是紅色元素的父節點,它用於演示各類盒子,如:content-box,padding-box,border-box,margin-box以及offset(這是屬於藍色元素的,換言之,它是藍色元素的offsetParent) viewport是個黑色虛線框,爲html標籤。藍色元素還有滾動條,方便咱們觀察clientTop與scrollTop, clientHeight與scrollHeight的差別。
getBoundingClientRect中的top與bottom也在此圖中顯示出來,掌握此圖對咱們構建此模塊很是有用。
getBoundingClientRect目前市場主流瀏覽器都支持。
所以,咱們能夠利用它求出相對於頁面的距離。將它相對於可視區域距離加上滾動條的距離便可!
var left = this.getBoundingClientRect().left + document.documentElement.scrollLeft; var top = this.getBoundingClientRect().top + document.documentElement.scrollTop;
更多參考:https://developer.mozilla.org/en-US/docs/Web/API/Element
https://msdn.microsoft.com/en-us/library/ms536433.aspx
斷定一個元素的,首先咱們斷定它是否在DOM樹上,不在直接返回(0,0)。不然取得元素在可視區域的距離加上滾動條的距離而後減去瀏覽器的邊框。所以在計算可視區距離與滾動距離時都已經包含瀏覽器邊框。
$.fn.offset = function(options) { if ( argunments.length ) {//設置匹配元素的offset return (!options || (isFinite(options.top) && !isFinite(options.left))) ? this : this.each(function(){ setOffset(this, options); }) } //取得第一個元素的相對頁面的座標 var node = this[0], doc = node && node.ownerDocument, pos = { left:0, top:0 }; if (!doc) { return pos } //咱們經過getBoundingClientRect計算元素相對於client的rect //https://msdn.microsoft.com/en-us/library/ms536433.aspx上面有更多api var box = node.getBoundingClientRect(), win = getWinow(doc), root = doc.document, clientTop = root.clentTop || 0, clientLeft = root.clentLeft || 0, scrollTop = win.pageYOffset || root.scrollTop, scrollLeft = win.pageXOffset || root.scrollLeft; //將滾動距離加到left,top上面 //ie一些版本會爲HTML自動添加2px的border,須要去掉。https://msdn.microsoft.com/en-us/library/ms533564.aspx pos.top = box.top + scrollTop - clientTop, pos.left = box.left + scrollLeft - clientLeft; return pos; }
關於offsetParent,請自行參考jQ.
從盒子模型來看,相對於offsetParent的距離,是指此元素的margin-box左上角到offsetParent的content-box的左上角。因爲offsetParent,getBoundClientRect首先提出來的,所以盒子都以border-box爲計算單元。咱們須要減去offsetParent的左邊框與元素的左邊界的寬。如圖
所以,x軸的距離計算公式以下:
X = node[clientLeft] - offsetParent[client_left] - offsetParent[boderLeftWidth] - node[marginLeftWidth]
整個實現方法以下:
$.fn.position = function() { //取得元素相對於其offsetParent的座標 var offset, offsetParent, node = this[0], parentOffset = { //默認的offsetParent相對於視窗距離 top:0, left:0 } if (!node || node.nodeType !== 1) { return } //fixed元素是相對於window if (getter(node,"position") === "fixed") { //fixed offset = node.getBoundingClientRect(); } else { offset = this.offset();//獲得元素相對視窗距離。由於咱們只有top和left offsetParent = this.offsetParent(); if (offsetParent[0].tagName !== "HTML") { parentOffset = offsetParent.offset();//獲得它的offsetParent相對視窗的距離 } parentOffset.top += parseFloat(getter(offsetParent[0],"borderTopWidth")) || 0; parentOffset.left += parseFloat(getter(offsetParent[0],"boderLeftWidth")) || 0; } return { top: offset.top - parentOffset.top - (parseFloat(getter(node,"marginTop")) || 0), left :offset.left - parentOffset.left - (parseFloat(getter(node,"marginLeftWidth")) || 0) } }
咱們再回頭看看offset方法.offset也是一個set all get first的方法。想把一個元素挪到某個位置有不少實現方式,好比移動滾動條,修改margin,修改left,top。offset的寫法就是最後一種。不過可能存在祖先定爲,所以不借助框架。這個工做對通常人而言有點吃力,而正是框架的價值所在。
function setOffset(node, options) { if (node && node.nodeType == 1) { var position = $.css(node, "position"); //強制定位 if (position === "static") { node.style.position = "relative"; } var curElem = $(node), curOffset = curElem.offset(), curCSSTop = $.css(node, "top"), curCSSLeft = $.css(node, "left"), calculatePosition = (position === "absolute" || position === "fixed") && [curCSSTop, curCSSLeft].indexOf("auto") > -1, props = {}, curPosition = {}, curTop, curLeft; if (calculatePosition) { curPosition = curElem.position(); curTop = curPosition.top; curLeft = curPosition.left; } else { //若是是相對定位,只要用當前top,left作基數 curTop = parseFloat(curCSSTop) || 0; curCSSLeft = parseFloat(curCSSLeft) || 0; } if (options.top != null) { props.top = (options.top - curOffset.top) + curTop; } if (options.left != null) { props.left = (options.left - curOffset.left) + curLeft; } curElem.css(props) } }
offset方法完整了。
6.元素的滾動條座標
元素的滾動條座標這是瀏覽器一組很是重要的屬性,所以,光瀏覽器自己就提供了多個方法來修改它們,好比掛在window下的scroll,scrollTop,scrollBy方法,掛在元素下的scrollLeft,scrollTop,scrollIntoView。
jQuery在css模塊就提供了scrollLeft,scrollTop,來修改或讀取元素或窗口的滾動座標,在animation模塊,更是容許他以平滑的方式來挪動它們。EXT更是用一整塊的模塊來知足用戶對滾動的各類需求。
修改top, left挪動元素又一個壞處,就是可能遮在某些元素之上,而修改scrollTop,scrollLeft不會。
這裏咱們仿照jQuery那樣,把這兩個方法名取名爲scrollTop、scrollLeft,對於通常元素節點,讀寫都沒有什麼困難(由於元素上就有這兩個屬性了。咱們只須要集中精力對付外邊的滾動條,也稱爲瀏覽器的滾動條。位於頂層的可視元素之上。)設置時,咱們用到了window中的scrollTop方法,裏面傳入你想要滾動到的座標。讀取時,咱們常說使用pageXOffset, pageYOffset這組屬性,標準瀏覽器網景時代就支持了。IE則直接取html元素的scrollLeft,scrollTop屬性。
"scrollLeft_pageXOffset, scrollTop_pageYOffset".replace($.rmapper, function(_, method, prop) { $.fn[method] = function(val) { var node, win, top = method === "scrollTop"; if (val === void 0) { node = this[0]; if (!node) { return null; } win = getWinow(node);//獲取第一個元素的scrollTop/scrollLeft return win ? (prop in win) ? win[prop] : win.document.documentElement[method] : node[method]; } return this.each(function(){//設置匹配元素的scrollTop/scrollLeft win = getWinow(this); if (win) { win.scrollTo(!top ? val : $(win).scrollLeft(), top ? val : $(win).scrollTop()); } else { this[method] = val; } }) } })