Skip to content

Instantly share code, notes, and snippets.

@NightScript370
Forked from wKich/toPx.js
Last active August 24, 2022 02:57
Show Gist options
  • Save NightScript370/806066cc36b871ed349decac70861c54 to your computer and use it in GitHub Desktop.
Save NightScript370/806066cc36b871ed349decac70861c54 to your computer and use it in GitHub Desktop.
convert CSS units to pixels
// 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