-
-
Save NightScript370/806066cc36b871ed349decac70861c54 to your computer and use it in GitHub Desktop.
convert CSS units to pixels
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// imported from https://github.com/heygrady/Units | |
let preCalculated = false; | |
let computedValueBug = false; | |
const defaultView = document.defaultView; | |
const getComputedStyle = defaultView && defaultView.getComputedStyle; | |
const runit = /^(-?[\d+\.\-]+)([a-z]+|%)$/i; | |
const convert:Record<string, number|undefined> = { | |
mm2px: 1/25.4, | |
cm2px: 1/2.54, | |
pt2px: 1/72, | |
pc2px: 1/6, | |
in2px: undefined, | |
mozmm2px: undefined, | |
}; | |
// convert a value to pixels | |
// use width as the default property, or specify your own | |
export type Primitive = string | number | boolean | bigint | symbol | undefined | null; | |
export type Builtin = Primitive | Function | Date | Error | RegExp; | |
export type DeepWritable<T> = T extends Builtin | |
? T | |
: T extends Map<infer K, infer V> | |
? Map<DeepWritable<K>, DeepWritable<V>> | |
: T extends ReadonlyMap<infer K, infer V> | |
? Map<DeepWritable<K>, DeepWritable<V>> | |
: T extends WeakMap<infer K, infer V> | |
? WeakMap<DeepWritable<K>, DeepWritable<V>> | |
: T extends Set<infer U> | |
? Set<DeepWritable<U>> | |
: T extends ReadonlySet<infer U> | |
? Set<DeepWritable<U>> | |
: T extends WeakSet<infer U> | |
? WeakSet<DeepWritable<U>> | |
: T extends Promise<infer U> | |
? Promise<DeepWritable<U>> | |
: T extends {} | |
? { -readonly [K in keyof T]: DeepWritable<T[K]> } | |
: T; | |
export default function toPx(elem:HTMLElement, value:string, prop:keyof DeepWritable<CSSStyleDeclaration> = 'width', force=false) { | |
if (!preCalculated) { | |
preCalculated = true; | |
preCalculate(); | |
} | |
const rem = /r?em/i; | |
const unit = (value.match(runit) || [])[2]; | |
let conversion = unit === 'px' ? 1 : convert[`${unit}2px` as keyof typeof convert]; | |
let result:number; | |
if (conversion || rem.test(unit) && !force) { | |
// calculate known conversions immediately | |
// find the correct element for absolute units or rem or fontSize + em or em | |
elem = conversion | |
? elem | |
: unit === 'rem' | |
? document.documentElement | |
: prop === 'fontSize' | |
? elem.parentElement || elem | |
: elem; | |
// use the pre-calculated | |
// conversion or fontSize of the element for rem and em | |
conversion = conversion || parseFloat(curCSS(elem, 'fontSize').toString()); | |
// multiply the value by the conversion | |
result = parseFloat(value) * conversion; | |
} else { | |
// begin "the awesome hack by Dean Edwards" | |
// @see http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 | |
// remember the current style | |
const style:any = elem.style; | |
const inlineValue = style[prop]; | |
// set the style on the target element | |
try { | |
style[prop] = value; | |
} catch (_e) { | |
// IE 8 and below throw an exception when setting unsupported units | |
return 0; | |
} | |
// read the computed value | |
// if style is nothing we probably set an unsupported unit | |
result = !style[prop] ? 0 : parseFloat(curCSS(elem, prop).toString()); | |
// reset the style back to what it was or blank it out | |
style[prop] = inlineValue !== undefined ? inlineValue : null; | |
} | |
// return a number | |
return result; | |
} | |
function preCalculate():void { | |
// create a test element | |
const testElem = document.createElement('test'); | |
const docElement = document.documentElement; | |
// add the test element to the dom | |
docElement.appendChild(testElem); | |
// test for the WebKit getComputedStyle bug | |
// @see http://bugs.jquery.com/ticket/10639 | |
if (getComputedStyle) { | |
// add a percentage margin and measure it | |
testElem.style.marginTop = '1%'; | |
computedValueBug = getComputedStyle(testElem).marginTop === '1%'; | |
} | |
// pre-calculate absolute unit conversions | |
Object.keys(convert).reverse().forEach((conversion) => convert[conversion] = convert[conversion as keyof typeof convert] | |
? convert[conversion as keyof typeof convert]! * convert.in2px! | |
: toPx(testElem, `_${conversion as keyof typeof convert}`) | |
); | |
// remove the test element from the DOM and delete it | |
docElement.removeChild(testElem); | |
} | |
// return the computed value of a CSS property | |
function curCSS(elem:HTMLElement, argProp:keyof DeepWritable<CSSStyleDeclaration>):number { | |
const prop = typeof argProp == 'number' ? argProp.toString() : argProp as string | |
const pixel = elem.style[`pixel${prop.charAt(0).toUpperCase()}${prop.slice(1)}` as keyof CSSStyleDeclaration]; | |
let value; | |
if (getComputedStyle) { | |
// FireFox, Chrome/Safari, Opera and IE9+ | |
value = getComputedStyle(elem)[prop as keyof CSSStyleDeclaration]; | |
} else if (pixel) { | |
// IE and Opera support pixel shortcuts for | |
// top, bottom, left, right, height, width | |
// WebKit supports pixel shortcuts only when an absolute unit is used | |
value = pixel + 'px'; | |
} else if (prop === 'fontSize') { | |
// correct IE issues with font-size | |
// @see http://bugs.jquery.com/ticket/760 | |
value = toPx(elem, '1em', 'left', true) + 'px'; | |
} else { | |
// IE 8 and below return the specified style | |
value = (elem as any).currentStyle[prop]; | |
} | |
// check the unit | |
const unit = (value.match(runit)||[])[2]; | |
if (unit === '%' && computedValueBug) { | |
// WebKit won't convert percentages for | |
// top, bottom, left, right, margin and text-indent | |
if (/^top|bottom/.test(prop)) { | |
// Top and bottom require measuring the innerHeight of the parent. | |
const parent = elem.parentElement || elem; | |
const innerHeight = [ | |
'borderBottom', 'borderTop', 'paddingBottom', 'paddingTop', | |
].reduce( | |
(height, prop) => height - parseFloat(curCSS(parent, prop as 'top' | 'bottom').toString()), | |
parent.offsetHeight | |
); | |
value = parseFloat(value) / 100 * innerHeight + 'px'; | |
} else { | |
// This fixes margin, left, right and text-indent | |
// @see https://bugs.webkit.org/show_bug.cgi?id=29084 | |
// @see http://bugs.jquery.com/ticket/10639 | |
value = toPx(elem, value); | |
} | |
} else if ( | |
(value === 'auto' || (unit && unit !== 'px')) | |
&& getComputedStyle | |
) { | |
// WebKit and Opera will return auto in some cases | |
// Firefox will pass back an unaltered value when | |
// it can't be set, like top on a static element | |
value = 0; | |
} else if (unit && unit !== 'px' && !getComputedStyle) { | |
// IE 8 and below won't convert units for us | |
// try to convert using a prop that will return pixels | |
// this will be accurate for everything | |
// (except font-size and some percentages) | |
value = toPx(elem, value) + 'px'; | |
} | |
return value; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment