【问题标题】:Converting em to px in Javascript (and getting default font size)在 Javascript 中将 em 转换为 px(并获取默认字体大小)
【发布时间】:2012-05-14 20:45:50
【问题描述】:

在某些情况下,获取em 测量值的确切像素宽度可能很有用。例如,假设您有一个具有以 em 为单位测量的 CSS 属性(如边框或填充)的元素,并且您需要获取边框或填充的确切像素宽度。有一个涉及此主题的现有问题:

How can i get default font size in pixels by using JavaScript or JQuery?

这个问题是关于获取 默认字体大小 - 这是将相对的 em 值转换为精确的 px 值所必需的。

This answer 很好地解释了如何获取元素的默认字体大小:

由于 ems 测量宽度,您始终可以计算出精确的像素字体 通过创建一个 1000 ems 长的 div 并将它的 client-Width 属性乘以 1000。我似乎记得 em 被截断为 最接近的千分之一,所以你需要 1000 ems 来避免错误 截断像素结果。

因此,以这个答案为指导,我编写了以下函数来获取默认字体大小:

function getDefaultFontSize(parentElement)
{
    parentElement = parentElement || document.body;
    var div = document.createElement('div');
    div.style.width = "1000em";
    parentElement.appendChild(div);
    var pixels = div.offsetWidth / 1000;
    parentElement.removeChild(div);
    return pixels;
}

获得默认字体大小后,您可以将任何 em 宽度转换为 px 宽度,只需将 em 乘以元素的默认字体大小并将结果四舍五入:

Math.round(ems * getDefaultFontSize(element.parentNode))

问题:getDefaultFontSize 理想情况下应该是一个简单、无副作用的函数,它返回默认字体大小。但它确实有一个不幸的副作用:它修改了 DOM!它附加一个孩子,然后删除孩子。为了获得有效的offsetWidth 属性,必须附加孩子。如果您不将子 div 附加到 DOM,则 offsetWidth 属性将保持为 0,因为该元素永远不会被渲染。即使我们立即删除子元素,此函数仍然会产生意想不到的副作用,例如触发正在侦听父元素的事件(如 Internet Explorer 的 onpropertychange 或 W3C 的 DOMSubtreeModified 事件)。

问题:有没有办法编写一个真正无副作用的getDefaultFontSize() 函数,不会意外触发事件处理程序或导致其他意外副作用?

【问题讨论】:

  • 并非如此,这就是 DOM 的工作方式。但是,您可以将创建的元素的类设置为诸如 font-size-check 之类的东西,然后在触发的任何事件中,检查元素是否具有该类以确定是否忽略它。
  • 遍历 DOM 直到找到一个宽度在 ems 中指定的节点,然后用它来进行不太准确的测量?唯一的副作用是处理时间。也不保证能正常工作。

标签: javascript css events dom


【解决方案1】:

以下 JS 函数将任何 CSS 长度单位值转换为另一个。
干杯! ?

// Usage examples: convertCSSLengthUnit('26.556016597510375mm'); convertCSSLengthUnit('26.556016597510375mm', 'px'); convertCSSLengthUnit('100px', 'mm');
function convertCSSLengthUnit(fromUnitValue, toUnit){
  let value = fromUnitValue.match(/[0-9]+\.*[0-9]*/g)[0];
  let unit = fromUnitValue.match(/[a-zA-Z]+/g)[0];

  let frag = document.createRange().createContextualFragment(`
    <div style='all: initial; pointer-events: none; display: block; position: absolute; border: none; padding: 0; margin: 0; background: rgba(0,0,0,0); color: color: rgba(0,0,0,0); width: 1${unit}; height: 1px;'></div>
  `);
  document.body.appendChild(frag);
  let measureElement = document.body.children[document.body.children.length-1];
  let toUnitValuePixel = measureElement.getBoundingClientRect().width * value; // X
  measureElement.remove();

  if(toUnit){
    let frag = document.createRange().createContextualFragment(`
      <div style='all: initial; pointer-events: none; display: block; position: absolute; border: none; padding: 0; margin: 0; background: rgba(0,0,0,0); color: color: rgba(0,0,0,0); width: 1${toUnit}; height: 1px;'></div>
    `);
    document.body.appendChild(frag);
    let measureElement = document.body.children[document.body.children.length-1];
    let valueUnit = measureElement.getBoundingClientRect().width; // Y
    measureElement.remove();

    // Given: Aem and Bmm with B=1. We know: Aem = Xpx and Bmm = Ypx. Therefore: 1px = Bmm/Y Result: Aem = Xpx = X * 1px = X * Bmm/Y <=> Aem = X * 1mm/Y (with B=1) <=> Aem = X/Ymm
    return (toUnitValuePixel / valueUnit) + toUnit;
  }
  return toUnitValuePixel + 'px';
}

【讨论】:

    【解决方案2】:

    我需要 em 大小,这就是我着陆的地方。我受到@10011101111 的解决方案的启发,编写了这个通用解决方案。请注意,这可能不一定没有 OP 要求的副作用,但可以对其进行调整以使副作用最小。

    此解决方案适用于那些与我有相同问题的人。

    要获得全像素结果:

    function getSize(size = '1em', parent = document.body) {
        let l = document.createElement('div')
        l.style.visibility = 'hidden'
        l.style.boxSize = 'content-box'
        l.style.position = 'absolute'
        l.style.maxHeight = 'none'
        l.style.height = size
        parent.appendChild(l)
        size = l.clientHeight
        l.remove()
        return size
    }
    

    要获得更精确的结果:

    function getSizePrecise(size = '1em', parent = document.body) {
        let l = document.createElement('div'), i = 1, s, t
        l.style.visibility = 'hidden'
        l.style.boxSize = 'content-box'
        l.style.position = 'absolute'
        l.style.maxHeight = 'none'
        l.style.height = size
        parent.appendChild(l)
        t = l.clientHeight
        do {
            s = t
            i *= 10
            l.style.height = 'calc(' + i + '*' + size + ')'
            t = l.clientHeight
        } while(t !== s * 10)
        l.remove()
        return t / i
    }
    

    请注意,您可以传入任何尺寸的任何单位。例如:

    getSizePrecise('1ex')
    getSizePrecise('1.111ex')
    

    您也可以通过删除calc() 函数并直接将除单位以外的大小值乘以JavaScript 中的乘数来适应CSS

    如果值溢出,这可能会返回 0。

    在尝试计算溢出限制时,我在 Firefox 88.0 上达到了 17895696 或 17895697 的像素值。所以,我将上面的函数更新如下:

    function getSizePrecise(size = '1em', parent = document.body) {
        let l = document.createElement('div'), i = 1, s, t
        l.style.visibility = 'hidden'
        l.style.boxSize = 'content-box'
        l.style.position = 'absolute'
        l.style.maxHeight = 'none'
        l.style.height = size
        parent.appendChild(l)
        t = l.clientHeight
        do {
            if (t > 1789569.6)
                break
            s = t
            i *= 10
            l.style.height = 'calc(' + i + '*' + size + ')'
            t = l.clientHeight
        } while(t !== s * 10)
        l.remove()
        return t / i
    }
    

    .6 严格来说不是必需的(因为 t 可能是一个整数),但我将它留在那里以供参考。

    【讨论】:

      【解决方案3】:

      我有一个更好的答案。我的代码将存储 1em 的长度(在 JavaScript 变量 em 中以 CSS 像素 px 为单位:

      1. 将此div 放置在 HTML 代码中的任何位置

        <div id="div" style="height:0;width:0;outline:none;border:none;padding:none;margin:none;box-sizing:content-box;"></div>
        
      2. 将此函数放入您的 JavaScript 文件中

        var em;
        function getValue(id){
            var div = document.getElementById(id);
            div.style.height = '1em';
            return ( em = div.offsetHeight );
        }
        

      现在,每当您在参数中使用该测试 div 的 id 调用此函数 'getValue' 时,您将拥有一个变量名称 em,其中包含 1 em 的值(以 px 为单位)。

      【讨论】:

        【解决方案4】:

        以下解决方案使用二分搜索和 window.matchMedia 来找出窗口的 em 宽度。然后我们将窗口 em 宽度除以窗口像素宽度得到最终结果。 不涉及 DOM,无副作用! 在性能方面,我的 comp 大约需要 8 次迭代,大约 0.2 毫秒。

        const singleEmInPixels = getSingleEmInPixels();
        console.log(`1em = ${singleEmInPixels}px`);
        
        /**
         * returns the amount of pixels for a single em
         */
        function getSingleEmInPixels() {
            let low = 0;
            let high = 200;
            let emWidth = Math.round((high - low) / 2) + low;
            const time = performance.now();
            let iters = 0;
            const maxIters = 10;
            while (high - low > 1) {
                const match = window.matchMedia(`(min-width: ${emWidth}em)`).matches;
                iters += 1;
                if (match) {
                    low = emWidth;
                } else {
                    high = emWidth;
                }
                emWidth = Math.round((high - low) / 2) + low;
                if (iters > maxIters) {
                    console.warn(`max iterations reached ${iters}`);
                    break;
                }
            }
            const singleEmPx = Math.ceil(window.innerWidth / emWidth);
            console.log(`window em width = ${emWidth}, time elapsed =  ${(performance.now() - time)}ms`);
            return singleEmPx;
        }
        

        【讨论】:

        • 我可能遗漏了一些东西,但我认为上述函数的问题可能是无法设置运行它的元素。 1em 从上下文切换到上下文。如果是rem,你就是g2g。
        【解决方案5】:

        编辑:不,没有。

        在不影响 DOM 的情况下获取给定元素的渲染字体大小:

        parseFloat(getComputedStyle(parentElement).fontSize);
        

        这是基于对this question 的回答。


        编辑:

        在 IE 中,您必须使用 parentElement.currentStyle["fontSize"],但这不能保证将大小转换为 px。这样就结束了。

        此外,这个 sn-p 不会给你元素的默认字体大小,而是它的 实际 字体大小,如果它实际上有一个相关的类和样式,这很重要用它。换句话说,如果元素的字体大小为2em,您将获得2 ems 中的像素数。除非内联指定字体大小,否则您将无法获得正确的转换率。

        【讨论】:

        • 它不会改变 DOM,但会强制浏览器进行重新计算/布局操作,所以不是“免费”操作。另外,使用 parseFloat() 而不是 Number()!
        • @ThomasMcCabe:为什么不使用Number()
        • 好吧,如果你使用parseFloat,你就不需要像parseFloat(getComputedStyle(parentElement, "").fontSize)那样的正则表达式的开销。如果您尝试对Number 执行相同操作,您将得到NaN。所以基本上,我是说你无缘无故地增加了.match 的开销。此外,在这种情况下,NumberparseFloat 稍慢。
        • “无法在 'Window' 上执行 'getComputedStyle':参数 1 不是 'Element' 类型”。如果有人在使用 jQuery 对象作为“parentElement”时遇到此错误:您只需将其替换为 JS 元素(即 HTML DOM 对象)。你可以这样做:$('#parentElement')[0].
        • @EnginYapici 使用 $jQueryElememt.get(0) 作为 getComputedStyle() 的第一个参数
        【解决方案6】:

        如果您需要快速而肮脏的东西(并且基于主体的基本字体大小,而不是元素),我会选择:

        Number(getComputedStyle(document.body,null).fontSize.replace(/[^\d]/g, ''))
        
         Number(  // Casts numeric strings to number
           getComputedStyle(  // takes element and returns CSSStyleDeclaration object
             document.body,null) // use document.body to get first "styled" element
                 .fontSize  // get fontSize property
                  .replace(/[^\d]/g, '')  // simple regex that will strip out non-numbers
         ) // returns number
        

        【讨论】:

        • 我会添加\。到正则表达式来解释小数(然后 parseFloat() 或者什么不取决于你必须做什么),但这似乎有效。
        猜你喜欢
        • 2019-07-21
        • 2017-05-14
        • 2021-02-02
        • 1970-01-01
        • 1970-01-01
        • 2013-01-13
        • 1970-01-01
        • 2013-04-24
        • 1970-01-01
        相关资源
        最近更新 更多