getUsedValue 0.1

前不久写了一个小脚本,用来获取页面中CSS样式的 used value 。

[b]什么是Used Value?[/b]

简单来说就是样式表应用到页面元素的最终结果值。比方说一个p元素,可能有多份样式表的多个样式规则,都关系到p元素的最终样式,但是最终一个特定p元素的实际css属性,比如它的width,当然只能有一个值。这个值就叫做used value。更精确的定义,请看[url=http://www.w3.org/TR/CSS21/cascade.html#used-value]CSS 2.1规范的6.1.3节[/url]。

大多数同志可能知道computed value,因为符合标准的浏览器上有window.getComputedStyle()方法。

实际上,按照我对规范的理解,这个getComputedStyle方法返回的其实应该是used value,而不是computed value。这两者的差别是,computed value是无需layout时所能得到的值。比如,经过cascade计算后,实际应用于p的width为10em,假设p的fontSize是16px,则它的width的computed value就是160px,这是可以确定计算出来的。但是不是所有computed value都能这样确定下来,有些值要到layout时才能确定。比如p的width为50%,假定p是body的子元素,则这个50%是相对于body的宽度的。如果body没有设过明确的宽度,那实际上就是窗口的客户区宽度(clientWidth)。所以p的width是多少,只有到了layout的步骤才能算出来。另一个例子是table,本身td的宽度可以设定为table宽度的百分比,而非fixed的table本身的宽度又是要根据所有td(如内容多少、字体大小、换行设置等)来确定的,这个layout计算过程相当复杂。

所以,将computed value加上所有相关依赖,最后得到的结果,就叫做used value。

那么为什么DOM方法叫做getComputedStyle,而不是getUsedStyle呢?这是因为DOM level 2 CSS规范制定时(2000年)还没有CSS2.1。DOM是按照更早的CSS2规范来的,在CSS2中还没有提出computed value和used value的区分。严格的说,CSS2中定义的computed value,既不是CSS2.1的computed value,也不是CSS2.1的used value,而是某种有点含糊的两者混合体。CSS2.1之所以加上这个区分,可能是为了澄清有关inherit的值如何计算的问题。

抛开规范的问题,获得used value对于ajax开发者来说有时是很有用的。比如典型的垂直居中问题,通常的做法,你要获得实际的元素高度和容器高度的used value。就这个特定问题,通常利用clientHeight、offsetHeight(它们不是CSS property)等来计算。不过如果要满足一些其他要求,你可能还需要获得padding、border、margin、outline的used value。getUsedValue还具有更广泛的用途,例如获取某个元素里font的实际大小、或者某个元素的实际颜色之类的信息。另外,浏览器的功能有多强大,bug就有多烦人。有时候你难免要为特定问题打patch,而getUsedValue是打patch的居家必备武器。

我写的这个getUsedValue的API如下:
getUsedValue(element, cssPropertyName, cssUnit)


前两个参数比较好理解,最后一个参数是指定css长度单位(如果你要取的是一个css长度值的话)。如果不指定这个值(就传前两个参数),则返回的是一个字符串,例如"12px",如果你指定了cssUnit为“px”,则返回的是一个数字,即12。这对于要进行运算来说比较方便,省得你自己去parseFloat和parseInt。

来看一个简单的使用例子:

function verticalAlign(e, ratio) {
ratio = ratio || 0.382
var p = e.offsetParent || e.parentNode
var s = e.style
var minTop = getUsedValue(e, 'marginTop', 'px')
s.position = 'relative'
s.top = Math.max(minTop, (p.clientHeight - e.offsetHeight) * ratio - minTop) + 'px'
}


这个verticalAlign,用来将一个元素(由第一个参数e指定)在其父元素内垂直定位,默认会放在中间偏上一些(符合黄金分割的比例)。具体实现主要是通过clientHeight和offsetHeight来的,本来可以不用getUsedValue。但是我们有个额外的要求,希望在垂直定位时,仍然保留marginTop的用途,即元素上方至少要保留由元素的marginTop指定的空间。所以我们要得到marginTop的值,作为垂直定位时top的下限。
var minTop = getUsedValue(e, 'marginTop', 'px')
这句就是了。它表示获得e元素的marginTop以px单位记的数值。

下面是这个getUsedValue函数的源代码。代码以LGPL方式发布。


// getUsedValue() version 0.1
// Copyright 2009 hax<johnhax@gmail.com>

// You can use and distribute these codes under LGPL v3 license.

var CSSValueUnitTypes = [
'unknown', 'number', '%',
'em', 'ex', 'px',
'cm', 'mm', 'in', 'pt', 'pc',
'deg', 'rad', 'grad',
'ms', 's', 'hz', 'khz',
'dimension', 'string', 'uri', 'indent',
'attr', 'counter', 'rect', 'rgbcolor'
]
function isLengthProperty(prop) {
return '\
top right bottom left \
marginLeft marginRight marginTop marginBottom outlineWidth \
borderTopWidth borderRightWidth borderBottomWidth borderLeftWidth \
paddingTop paddingRight paddingBottom paddingLeft \
maxHeight maxWidth minHeight minWidth height width \
fontSize lineHeight textIndent letterSpacing wordSpacing \
borderSpacing backgroundPosition clip \
'.indexOf(prop) != -1
}
function isLengthUnit(unit) {
return ('% em ex px cm mm in pt pc'.indexOf(unit) != -1)
}
function isAbsoluteLengthUnit(unit) {
return ('cm mm in pt pc'.indexOf(unit) != -1)
}
function convertAbsoluteLength(n, u, u2) {
if (u == u2) return n
var t = { in_cm:2.54, in_pt:72, cm_mm:10, pc_pt:12 }
t.in_mm = t.in_cm * t.cm_mm //25.4
t.in_pc = t.in_pt / t.pc_pt //6
t.cm_pt = t.in_pt / t.in_cm
t.cm_pc = t.in_pc / t.in_cm
t.mm_pt = t.in_pt / t.in_mm
t.mm_pc = t.in_pc / t.in_mm
var r
if (r = t[u + '_' + u2]) return n * r
if (r = t[u2 + '_' + u]) return n / r
throw Error('Can not convert: ' + u + ' -> ' + u2)
}
function cssPropertyName(cssAttributeName) {
return cssAttributeName.replace(/([A-Z])/g, '-$1').toLowerCase()
}

function getDPI() {
if (!getDPI._1in) {
getDPI._1in = document.createElement('div')
var s = getDPI._1in.style
s.margin = s.borderWidth = s.padding = '0'
s.maxWidth = s.minWidth = s.width = '1in'
}
return getDPI._1in.style.pixelWidth
}
function calcPixelLength(elt, cssLength) {
var s
if (!calcPixelLength._temp) {
calcPixelLength._temp = document.createElement('div')
s = calcPixelLength._temp.style
s.margin = s.borderWidth = s.padding = '0'
s.display = 'none'
}
s = calcPixelLength._temp.style
s.maxWidth = s.minWidth = s.width = cssLength
elt.appendChild(calcPixelLength._temp)
var r = s.pixelWidth
elt.removeChild(calcPixelLength._temp)
return r
}
function calcFontSize(size) {
var s
if (!calcFontSize._temp) {
calcFontSize._temp = document.createElement('div')
s = calcFontSize._temp.style
s.margin = s.borderWidth = s.padding = '0'
s.maxWidth = s.minWidth = s.width = '1em'
}
s = calcFontSize._temp.style
s.fontSize = size
return s.pixelWidth
}

function getUsedValue(elt, prop, unit) {

if (typeof getComputedStyle == 'function') {
if (unit == null) {
return getComputedStyle(elt, null)[prop]
} else {
var unitType = CSSValueUnitTypes.indexOf(unit)
var value = getComputedStyle(elt, null).
getPropertyCSSValue(cssPropertyName(prop))
switch (unitType) {
default: return value.getFloatValue(unitType)
}
}
}

if (elt.currentStyle) {

var v = elt.currentStyle[prop]

if (!v) return v

if (isLengthUnit(unit) || isLengthProperty(prop)) {

if (prop == 'fontSize') {
if ('xx-small x-small small medium large x-large xx-large'.
indexOf(v) != -1) {
var n = calcFontSize(v)
if (unit == null) return n + 'px'
if (unit == 'px') return n
if (isAbsoluteLengthUnit(unit))
return convertAbsoluteLength(n / getDPI(), 'in', unit)
throw Error('px -> ' + unit + ' convertion has not implemented yet')
}
if ('smaller larger'.indexOf(v) != -1)
throw Error('Calculation of font relative size has not implemented yet')
}

var n, u
var a = /^([+-]?([0-9]+|[0-9]*[.][0-9]+))(em|ex|px|in|cm|mm|pt|pc|%)?/.exec(v)
if (a == null) throw Error('Unknown length format: ' + v)
n = parseFloat(a[1]), u = a[3]

if (u == unit) return n

if (u == 'px') {
if (unit == null) return v
if (isAbsoluteLengthUnit(unit))
return convertAbsoluteLength(n / getDPI(), 'in', unit)
throw Error('px -> ' + unit + ' convertion has not implemented yet')
}
if (isAbsoluteLengthUnit(u)) {
if (isAbsoluteLengthUnit(unit))
return convertAbsoluteLength(n, u, unit)
n = convertAbsoluteLength(n, u, 'in') * getDPI()
if (unit == 'px') return n
if (unit == null) return n + 'px'
throw Error(u + ' -> ' + unit + ' convertion has not implemented yet')
}
var pixelProp = {
width:'pixelWidth',height:'pixelHeight',
top:'pixelTop',bottom:'pixelBottom',
left:'pixelLeft',right:'pixelRight'
}[prop]
if (pixelProp) {
n = elt.currentStyle[pixelProp]
if (unit == 'px') return n
if (unit == null) return n + 'px'
throw Error('px -> ' + unit + ' convertion has not implemented yet')
}
if (u == '%') {
throw Error('Percentage value calculation has not implemented yet')
}
if (u == 'ex') {
throw Error('ex value calculation has not implemented yet')
}
if (u == 'em') {
if (prop == 'fontSize') {
n = calcPixelLength(elt.parentNode, v)
} else {
n *= getUsedValue(elt, 'fontSize', 'px')
}
if (unit == 'px') return n
if (unit == null) return n + 'px'
throw Error('em -> ' + unit + ' convertion has not implemented yet')
}
throw Error('Unknown unit type: ' + u)

} else {

return v

}
}
}


注意,目前还只是0.1版,没有经过充分的测试。

已知的问题包括:

1. 标准浏览器下,依赖getComputedStyle,而getComputedStyle函数,由于之前讲过的标准制定上的不同步,导致实际各浏览器实现可能有差异(可参考[url]http://www.nabble.com/getComputedStyle-results-td16390247.html[/url])。
2. getComputedStyle不支持px以外的相对单位,即使specified value确实是em/ex,也只能得到绝对值,无法倒算回去。
3. IE下是根据currentStyle来进一步计算的。currentStyle介于specified value和computed value之间(当然实际上都不是,因为IE的CSS模型根本完全不合标准,所以只是就概念上说部分属性相当于specified value,部分属性相当于computed value)。目前只针对css长度进行了进一步计算,而且不支持百分比和ex计算。em只在currentStyle返回原值是em时才可用。
4. 复杂情况,如table,如visibility:hidden、display:none之类的,都尚未有时间测试。

下一步的计划是:

1. 做更多实例的测试
2. 做更多浏览器的测试(目前其实只测了IE6和FF3)
3. 完全支持em和%长度的计算(ex不会支持,因为基本没人用,且无法准确测量ex,ex依赖于字体,bug之多无法计数)
4. 支持各种格式的颜色值的计算

欢迎大家测试和反馈bug。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值