JS 12.2 样式

12.2 样式

DOM2级样式模块围绕外联样式,style内嵌,style属性这3中应用样式的机制提供了一套API;

能力检测;

var supportsDOM2CSS = document.implementation.hasFeature("CSS", "2.0");
var supportsDOM2CSS2 = document.implementation.hasFeature("CSS2", "2.0");

访问元素的样式

在标准模式下,所有度量值都必须指定一个度量单位;

在混杂模式下,可以将style.width 设置为”20”,浏览器会假设它是”20px”;

但在标准模式下,将style.width 设置为”20”会导致被忽略——因为没有度量单位;

在实践中,最好始终都指定度量单位;

如果没有为元素设置style特性,那么style对象中可能会包含一些默认的值,

但这些值并不能准确地反映该元素的样式信息;

DOM样式属性和方法

style对象的属性和方法;

1.cssText:如前所述,通过它能够访问到style特性中的CSS代码;

2.length:应用给元素的CSS属性的数量;

3.parentRule:表示CSS信息的CSSRule对象;本节后面将讨论CSSRule类型;

4.getPropertyCSSValue(propertyName):返回包含给定属性值的CSSValue对象;

5.getPropertyPriority(propertyName):如果给定的属性使用了!important设置,则返回”important”;否则,返回空字符串;

6.getPropertyValue(propertyName):返回给定属性的字符串值;

7.item(index):返回给定位置的CSS属性的名称;

8.removeProperty(propertyName):从样式中删除给定属性;

9.setProperty(propertyName,value,priority):将给定属性设置为相应的值,并加上优先权标志(“important”或者一个空字符串);

var prop, value, i, len;
for (i=0, len=myDiv.style.length; i < len; i++){
    prop = myDiv.style[i]; //或者 myDiv.style.item(i)
	value = myDiv.style.getPropertyValue(prop);
	alert(prop + " : " + value);
}

计算的样式

getComputedStyle()接受两个参数:要取得计算样式的元素和一个伪元素字符串(例如”:after”);

如果不需要伪元素信息,第二个参数可以是null;

返回一个CSSStyleDeclaration对象(与style属性的类型相同),其中包含当前元素的所有计算的样式;

所有的意思就是,style只包含自身样式,不包含其他样式表层叠而来并影响到当前元素的样式信息,

而这个方法可以得到所有的;

<!DOCTYPE html>
<html>
<head>
<title>Computed Styles Example</title>
<style type="text/css">
#myDiv {
background-color: blue;
width: 100px;
height: 200px;
}
</style>
</head>
<body>
<div id="myDiv" style="background-color: red; border: 1px solid black"></div>
</body>
</html>

// 获取style
var myDiv = document.getElementById("myDiv");
var computedStyle = document.defaultView.getComputedStyle(myDiv, null);
alert(computedStyle.backgroundColor); // "red"
alert(computedStyle.width); // "100px"
alert(computedStyle.height); // "200px"
alert(computedStyle.border); // 在某些浏览器中是"1px solid black"

浏览器对综合属性的解释不同,

在设置border时, 实际上是设置了四个边的边框宽度,颜色,样式属性;

border-left-width,border-top-color,border-bottom-style,等等;

因此,即使computedStyle.border不会在所有浏览器中都返回值,

但computedStyle.borderLeftWidth会返回值;

浏览器表示值的方式有所区别,

例如,Firefox和Safari会将所有颜色转换成RGB格式(例如红色是rgb(255,0,0));

因此,在使用getComputedStyle()方法时,最好多在几种浏览器中测试一下;

IE不支持此方法,但是有一个类似的概念,currentStyle属性;

var myDiv = document.getElementById("myDiv");
var computedStyle = myDiv.currentStyle;
alert(computedStyle.backgroundColor); //"red"
alert(computedStyle.width); //"100px"
alert(computedStyle.height); //"200px"
alert(computedStyle.border); //undefined

IE也没有返回border样式,因为这是一个综合属性;

所有的计算样式都是只读的;

计算后的样式也包含属于浏览器内部样式表的样式信息,

因此任何具有默认值的CSS属性都会表现在计算后的样式中;

例如,所有浏览器中的visibility属性都有一个默认值,但这个值会因实现而异;

在默认情况下,有的浏览器将visibility属性设置为”visible”,

而有的浏览器则将其设置为”inherit”;

换句话说,不能指望某个CSS属性的默认值在不同浏览器中是相同的;

如果你需要元素具有某个特定的默认值,应该手工在样式表中指定该值;

操作样式表

CSSStyleSheet类型表示的是样式表,包括通过<link>元素包含的样式表和在<style>元素中定义的样式表;

CSSStyleSheet继承自StyleSheet,继承的属性有:

disabled,href,media,ownerNode,parentStyleSheet,title,type;

除了disabled属性,其他属性都是只读的;

CSSStyleSheet还支持下面属性和方法;

cssRules,ownerRule,deleteRule(index),insertRule(rule, index);

DOM规定了一个包含CSSStyleSheet对象的属性,名叫sheet;

function getStyleSheet(element){
return element.sheet || element.styleSheet;
}
//取得第一个<link/>元素引入的样式表
var link = document.getElementsByTagName("link")[0];
var sheet = getStylesheet(link);

是的,IE支持的是styleSheet;

getStyleSheet()返回的样式表对象与document.styleSheets集合中的样式表对象相同;

CSS规则

CSSRule对象表示样式表中的每一条规则;

包含属性:cssText,parentRule,parentStyleSheet,selectorText,style,type;

div.box {
	background-color: blue;
	width: 100px;
	height: 200px;
}

var sheet = document.styleSheets[0];
var rules = sheet.cssRules || sheet.rules; //取得规则列表
var rule = rules[0]; //取得第一条规则
alert(rule.selectorText); //"div.box"
alert(rule.style.cssText); //完整的CSS 代码
alert(rule.style.backgroundColor); //"blue"
alert(rule.style.width); //"100px"
alert(rule.style.height); //"200px"

// 可以修改
rule.style.backgroundColor = "red"

以这种方式修改规则会影响页面中适用于该规则的所有元素;

创建规则

insertRule()方法;

sheet.insertRule("body { background-color: silver }", 0); //DOM方法

IE8及更早版本中addRule();

有关这个方法的规定中说,最多可以使用addRule()添加4095条样式规则;

超出这个上限的调用将会导致错误;

兼容时刻;

function insertRule(sheet, selectorText, cssText, position){
    if (sheet.insertRule){
    	sheet.insertRule(selectorText + "{" + cssText + "}", position);
    } else if (sheet.addRule){
    	sheet.addRule(selectorText, cssText, position);
    }
}

删除规则

deleteRule(),IE中removeRule();

function deleteRule(sheet, index){
    if (sheet.deleteRule){
        sheet.deleteRule(index);
    } else if (sheet.removeRule){
        sheet.removeRule(index);
    }
}

与添加规则相似,删除规则也不是实际Web开发中常见的做法;

考虑到删除规则可能会影响CSS层叠的效果,因此请大家慎重使用;

元素大小

本节介绍的属性和方法并不属于”DOM2 级样式”规范,但却与HTML元素的样式息息相关;

DOM中没有规定如何确定页面中元素的大小;

IE为此率先引入了一些属性,以便开发人员使用;

目前,所有主要的浏览器都已经支持这些属性;

偏移量

offsetHeight,offsetWidth,offsetLeft,offsetTop;

其中,offsetLeft和offsetTop属性与包含元素有关,

包含元素的引用保存在offsetParent属性中;

offsetParent属性不一定与parentNode的值相等;

例如,<td>元素的offsetParent是作为其祖先元素的<table>元素,

因为<table>是在DOM层次中距<td>最近的一个具有大小的元素;

要想知道某个元素在页面上的偏移量,

将这个元素的offsetLeft和offsetTop与其offsetParent的相同属性相加,

如此循环直至根元素,就可以得到一个基本准确的值;

function getElementLeft(element) {
    var actualLeft = element.offsetLeft;
    var current = element.offsetParent;
    while (current !== null) {
        actualLeft += current.offsetLeft;
        current = current.offsetParent;
    }
    return actualLeft;
}

function getElementTop(element) {
    var actualTop = element.offsetTop;
    var current = element.offsetParent;
    while (current !== null) {
        actualTop += current.offsetTop;
        current = current.offsetParent;
    }
    return actualTop;
}

所有这些偏移量属性都是只读的,而且每次访问它们都需要重新计算;

因此,应该尽量避免重复访问这些属性;

客户区大小

元素的客户区大小(client dimension),指的是元素内容及其内边距所占据的空间大小;

clientWidth,clientHeight;

客户区大小就是元素内部的空间大小,因此滚动条占用的空间不计算在内;

function getViewport() {
    if (document.compatMode == "BackCompat") {
        return {
        // IE7之前的版本
            width: document.body.clientWidth,
            height: document.body.clientHeight
        };
    } else {
        return {
            width: document.documentElement.clientWidth,
            height: document.documentElement.clientHeight
        };
    }
}

与偏移量相似,客户区大小也是只读的,也是每次访问都要重新计算的;

滚动大小

滚动大小(scroll dimension),指的是包含滚动内容的元素的大小;

有些元素(例如<html>元素),即使没有执行任何代码也能自动地添加滚动条;

但另外一些元素,则需要通过CSS的overflow属性进行设置才能滚动;

scrollHeight,scrollWidth,scrollLeft,scrollTop;

确定元素大小

getBoundingClientRect();

这个方法返回会一个矩形对象,包含4个属性:left,top,right和bottom;

function getBoundingClientRect(element) {
    if (typeof arguments.callee.offset != "number") {
        var scrollTop = document.documentElement.scrollTop;
        var temp = document.createElement("div");
        temp.style.cssText = "position:absolute;left:0;top:0;";
        document.body.appendChild(temp);
        arguments.callee.offset = -temp.getBoundingClientRect().top - scrollTop;
        document.body.removeChild(temp);
        temp = null;
    }
    var rect = element.getBoundingClientRect();
    var offset = arguments.callee.offset;
    return {
        left: rect.left + offset,
        right: rect.right + offset,
        top: rect.top + offset,
        bottom: rect.bottom + offset
    };
}

是的,为了IE;

IE8及更早版本认为文档的左上角坐标是(2,2),而其他浏览器包括IE9则将传统的(0,0)作为起点坐标;

这个函数使用了它自身的属性来确定是否要对坐标进行调整;

第一步是检测属性是否有定义,如果没有就定义一个;

最终的offset会被设置为新元素上坐标的负值,

实际上就是在IE中设置为2,在Firefox和Opera中设置为0;

为此,需要创建一个临时的元素,将其位置设置在(0,0),然后再调用其getBoundingClientRect();

而之所以要减去视口的scrollTop,是为了防止调用这个函数时窗口被滚动了;

这样编写代码,就无需每次调用这个函数都执行两次getBoundingClientRect()了;

接下来,再在传入的元素上调用这个方法并基于新的计算公式创建一个对象;

对于不支持getBoundingClientRect()方法的浏览器,可以;

function getBoundingClientRect(element) {
    var scrollTop = document.documentElement.scrollTop;
    var scrollLeft = document.documentElement.scrollLeft;
    if (element.getBoundingClientRect) {
        if (typeof arguments.callee.offset != "number") {
            var temp = document.createElement("div");
            temp.style.cssText = "position:absolute;left:0;top:0;";
            document.body.appendChild(temp);
            arguments.callee.offset = -temp.getBoundingClientRect().top - scrollTop;
            document.body.removeChild(temp);
            temp = null;
        }
        var rect = element.getBoundingClientRect();
        var offset = arguments.callee.offset;
        return {
            left: rect.left + offset,
            right: rect.right + offset,
            top: rect.top + offset,
            bottom: rect.bottom + offset
        };
    } else {
        var actualLeft = getElementLeft(element);
        var actualTop = getElementTop(element);
        return {
            left: actualLeft - scrollLeft,
            right: actualLeft + element.offsetWidth - scrollLeft,
            top: actualTop - scrollTop,
            bottom: actualTop + element.offsetHeight - scrollTop
        }
    }
}

由于这里使用了arguments.callee,所以这个方法不能在严格模式下使用;