基于Shadow DOM根元素的字体大小CSS值

22

问题简述:

我正在寻找一种在影子DOM中设置基于字体大小的值的方法(类似于将某些属性设置为em值),以使元素相对于影子宿主进行缩放,无论影子DOM中父元素的字体大小如何。与rem值类似的某些东西,但以影子作为根,而不是HTML根。

这样做的原因是为了能够在一个地方缩放内容(例如使用影子DOM插入的小部件)。如果基于字体大小的属性可以通过这种方式进行缩放,则整个小部件就可以在每个上下文中缩放,而无需设置大量CSS值以使其适合于每个位置。

问题详述:

我正在寻找一种根据DOM中给定元素调整文本(字体大小)大小的方法,以便根据此包装元素缩放内容的不同部分(适用于小部件等)。这不仅适用于实际文本,还经常基于字体大小来调整许多CSS值,因此元素的视觉布局随着文本的大小而缩放(例如填充,边框半径和阴影)。如果所有内容都基于em值,这可能会有点混乱。如果元素内存在多级字体大小继承,并且我想扩大第一级而不更改第二级,则需要在DOM中放大第一级的em,同时减少任何第二级元素(基于该第一级计算)以使子元素的文本保持相同大小。在许多情况下,这是不太理想的。

Root EM(rem)上的一个好解决方案是基于东西,因此更改DOM的一个级别不会影响子元素。但是,如果我想在一个地方放大/缩小包裹元素内所有文本的大小(而不影响页面上的其他元素),则需要放大/缩小包裹元素内所有字体大小的REM值。

我最近开始研究带有Shadow DOM和模板的Web组件。在模板标记中,CSS被封装,这很棒,因为可以单独为该框/小部件/组件设计其样式,而无需考虑其余设计,不需要继承的值等等。最后,一种制作独立组件以构建网页的好方法。但是,如果我可以为模板本身设置一种模板根字体大小就好了。因此,如果我将模板内的某个元素设置为字体大小2rem(或任何其他类似单位),则该元素的字体大小将是该模板的根字体大小的2倍,而不管该模板在哪个主机元素中使用,也不管页面根(html元素)的字体大小如何。

我尝试在模板中使用REM值时,它仍然基于页面根(HTML标记)的字体大小。我还尝试使用vw值,但这也基于整个视口,而不仅仅是封装区域(影子根)。

我查看了许多关于Shadow DOM的文章,但我无法找到任何解决此问题的方法。有什么建议吗?

发生了什么(简化的例子):

<html style="font-size: 16px">
    <body style="font-size: 12px">
        I am 12px large
        
        <!-- em, based on the body in this case -->
        <div style="font-size: 1.5em">
            I am 18px large
            
            <div style="font-size: 1.5em">
                I am 27px large
            </div>
        </div>
        
        <!-- rem, based on the styles of the html element -->
        <div style="font-size: 1.5rem">
            I am 24px large
            
            <div style="font-size: 1.5rem">
                I am 24px large
            </div>
        </div>
    </body>
</html>

我想要的是:

<html style="font-size: 16px">
    <body style="font-size: 12px">
        I am 12px large

        <div style="font-size: 1.5em">
            I am 18px large
        </div>
        <div style="font-size: 1.5rem">
            I am 24px large
        </div>

        <template> <!-- Simulates that it is inserted into the DOM by using shadow elements -->
            <style>
                :root{ /* Or something like that */
                    font-size: 20px; /* This is the simulated "template root fontsize" */
                }
            </style>
            
            <div style="font-size: 1.5trem"> <!-- Based on the template root fontsize -->
                I am 30px large
                
                <div style="font-size: 2trem">
                    I am 40px large
                </div>
            </div>

        </template>
    </body>
</html>

这将为组件内所有相对字体大小提供一个控制位置(如上例中的 :root 部分),然后可以将其与 px 或其他常规 CSS 值等非相对字体大小结合使用。


我也想要这个。你真的需要能够根据容器的大小来确定所有组件的尺寸。我发布了一个类似的问题 - Lonnie Best
3个回答

1

在查看Web组件文档后,您想要做的示例与Web组件完全不同。请参阅以下Web组件示例。

在文档的头部,您将具有

<link rel="import" href="web_component_name.html"></link>

在HTML中,您会在body标签中看到这样的内容:

<my-web-component>Bob</my-web-component>

您的网络组件将有自己的文件。
<html>
  <template id="nameTagTemplate">
    <style>
    .outer {
      border: 2px solid brown;
      border-radius: 1em;
      background: red;
      font-size: 20pt;
      width: 12em;
      height: 7em;
      text-align: center;
    }
    .boilerplate {
      color: white;
      font-family: sans-serif;
      padding: 0.5em;
    }
    .name {
      color: black;
      background: white;
      font-family: "Marker Felt", cursive;
      font-size: 45pt;
      padding-top: 0.2em;
    }
    </style>
    <div class="outer">
      <div class="boilerplate">
        Hi! My name is
      </div>
      <div class="name">
        <content></content>
      </div>
    </div>
  </template>
  <script>
    var importDoc = document.currentScript.ownerDocument;
    var nameBadgePrototype = Object.create(HTMLElement.prototype);
    nameBadgePrototype.createdCallback = function() {
      var shadow = this.createShadowRoot();
      var template = importDoc.querySelector('#nameTagTemplate');
      shadow.appendChild(template.content.cloneNode(true));
    };
    document.registerElement("my-web-component", {
      prototype: nameBadgePrototype
    });
  </script>
</html>

你所尝试做的事情和你真正想做的事情不一致。在你回复了几次之后,我发现你想要做Web组件。这与你所尝试做的示例不同。此外,该代码仅在您启用Chrome Canary网络浏览器中的正确标志时才起作用。它不会默认工作,需要用户启用正确的beta设置才能使其正常工作。即使在内部网络中,我也不建议使用Web组件,因为它仍然处于测试阶段,您将遇到许多维护问题,特别是对于许多内部用户。用户经常找到重置浏览器设置的方法。用户总是会破坏新的、闪亮的东西。

1
我的示例不是经过测试的代码,它只是(如注)一个伪示例,展示了我想要实现的目标。我已经对Web组件代码进行了实际测试(包括Shadow DOM、Templates和Custom Elements),在Chrome浏览器中完美地工作(非测试版)。所有主要浏览器都存在Polyfills。正如之前所指出的那样,我知道WC的支持范围还不广泛,但这对此次询问来说并不相关。关键是找到一种方法,以后可以基于阴影根的字体大小值来设置em值,就像可以在文档根上使用rem一样。 - henit
我同意WC目前还不适合生产环境。然而,它的支持范围比你说的要广一些。Shadow dom在Chrome 25+中得到支持(从35开始无需webkit前缀),v37是当前最新的非beta版本。 Custom Elements在Chrome 33+中得到支持。HTML Templates在Chrome 26+和Firefox 22+中得到支持(FF 31是最新版本)。Polymer项目发布了javascript-polyfills,使这些功能在所有主流浏览器的最新两个版本中都能够使用。我自己还没有测试过这些polyfill。 - henit
我必须测试一下,因为我所读的所有内容都表明它在Chrome中仍然处于beta阶段,并且尚未在Firefox中实现。如果您有关于当前状态的来源并且显示不同,请在此网站上发布它们,因为这与WC相关。 - Patrick W. McMahon
我从caniuse.com获取了支持不同技术的版本:http://caniuse.com/#feat=shadowdom http://caniuse.com/#feat=custom-elements http://caniuse.com/#feat=template以及来自Polymer项目的Web组件polyfill信息:http://www.polymer-project.org/resources/compatibility.html,以及有关Polymer平台的一些常规信息:http://www.polymer-project.org/docs/start/platform.html - henit

0

:host选择器样式化承载影子根的元素。这与使用外部css/styles样式化宿主元素具有相同的效果(例如,对于下面的示例,<div id="rootelm" style="font-size:20px"></div>)。如果您使用:host而不是您的伪:root选择器,它应该按照您的期望工作。以下是一个示例:

<!DOCTYPE HTML>
<html style="font-size: 16px">
    <body style="font-size: 12px">
        I am 12px large

        <div style="font-size: 1.5em">
            I am 18px large
        </div>
        <div style="font-size: 1.5rem">
            I am 24px large
        </div>

        <div id="rootelm"></div>

        <template id="testtemplate">
            <style>
                :host{ /* Our new selector */
                    font-size: 20px; /* This is the "template root fontsize" */
                }
            </style>
            <div style="font-size: 1em">
                I am 20px large
                <!-- Absolute size for comparison -->
                <div style="font-size: 20px">I am 20px large as well</div>
                <div style="font-size: 2em">
                    I am 40px large
                    <!-- Absolute size for comparison -->
                    <div style="font-size: 40px">I am 40px large as well</div>
                </div>
            </div>
        </template>

        <script>
            var shadow = document.querySelector('#rootelm').createShadowRoot();
            var template = document.querySelector('#testtemplate');
            shadow.innerHTML = template.innerHTML;
        </script>
    </body>
</html>

plunker

编辑: 在 plunker 中更新了 JS 以使字体大小相对于承载 Shadow Root 的元素。脚本如下:

    function updateFontSizes() {
        //Get root element. If you had more than one, you could use querySelectorAll(), and loop over the resulting array
        var rootElm = document.querySelector("#rootelm");
        /*Get styled elements. If you were using external styles instead of inline,
          you'd have to use getComputedStyle or another method of getting CSS */
        var styledElms = rootElm.shadowRoot.querySelectorAll('[style]');
        //Get the root font size (the font size applied to the element containing the template)
        var rootFontSize = window.getComputedStyle(rootElm, null).getPropertyValue("font-size");
        //Format root fontsize. If you are using different units, change the string in IndexOF
        rootFontSize = parseFloat(rootFontSize.substring(0, rootFontSize.indexOf('px')).trim());
        for (var i = 0; i < styledElms.length; i++) {
            //Get relative font size index of units
            var unitIndex = styledElms[i].style.fontSize.indexOf('rem');
            //Get custom attribute that we will fill later
            var oldFS = styledElms[i].getAttribute("oldfs");
            //if font-size contains the relative units
            if (unitIndex > -1) {
                //Store relative font size in custom attribute
                styledElms[i].setAttribute("oldfs",styledElms[i].style.fontSize);
                //Set fontsize to relative font size * computed root font size
                styledElms[i].style.fontSize = parseFloat(styledElms[i].style.fontSize.substring(0, unitIndex).trim()) * rootFontSize + "px";
            }
            //If font size doesn't contain the relative units, and we stored the old relative font size
            else if (oldFS !== null) {
                //Set the fontsize to old relative font size * computed root font size
                styledElms[i].style.fontSize = parseFloat(oldFS.substring(0, oldFS.indexOf('rem')).trim()) * rootFontSize + "px"
            }
        }
    }

    //Media query listeners
    //See https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Testing_media_queries for more info
    var mql = window.matchMedia("(max-width: 500px)");
    mql.addListener(handleMediaQuery);
    handleMediaQuery(mql);

    function handleMediaQuery(mql) {
        updateFontSizes();
    }

1
这并不能解决问题。我想要根据阴影根设置基于值的字体大小,而不考虑父元素。然后,我可以通过媒体查询来控制模板根,相对于它来缩放内容,而不是在所有子元素上都使用媒体查询。em 值是基于父元素的,rem 值是基于 html 根元素的。在我的示例中,html 字体大小为 16,模板根为 20。我希望能够将一个元素设置为模板根字体大小的 1.5 倍(例如,在我的示例中,“我是30像素大”),并且该元素的子元素设置为模板根的 2 倍(40 像素),而不考虑父元素(即 30 像素)。 - henit
@henit 我不相信只用 CSS 就能做到这一点,但是可以通过 JavaScript 实现。我已经更新了答案和 plunker,使用一个脚本将 rem 字体大小更改为相对于阴影根。弹出 plunker 并调整窗口大小以查看媒体查询变化。 - quw

-2
问题在于你将一个DOM对象称为“影子根”,而实际上它只是另一个DOM元素,是根元素的一个分支。在HTML5中,标签模板没有特殊的含义。并不存在所谓的“影子根”。有多种方式可以将文档注入到另一个文档中,比如使用iframe、object标签、元素标签等。我不建议使用任何一种方法,因为这会破坏文档正确解析的能力,同时还会带来一系列其他未来可能出现的问题。
你最好的选择是在CSS中创建一个DOM元素,具有你想要的基本属性,并使用CSS使该元素的子项根据你的CSS进行更改。你可以在CSS中创建一个DOM元素模板,并给它一个基本样式。
CSS文件
template{
    font-size: 20px;
}

HTML文件

<template>
   <div style="font-size: 1.5trem">
       I am 30px large
       <div style="font-size: 2trem">
           I am 40px large
       </div>
   </div>
</template>

这将把任何名为template的DOM元素设置为20像素的字体大小,所有子DOM元素都将根据父元素进行更改。另外,请尽量不要在元素上使用内联样式,CSS选择器可以为您完成很多工作。


2
似乎你可能不熟悉HTML5技术中的Web Components,它是一组包括Shadow DOM、Templates和其他标准的技术。这些技术的目的正是为了避免通过嵌入、iframe等方式插入内容。以下页面解释了Web Components的概念:http://css-tricks.com/modular-future-web-components/ - henit
Shadow DOM 只在 Chrome、Opera、Android 浏览器和 Chrome for Android 中有效。因此,在 IE、FireFox、Safari、IOS Safari 和 Opera Mini 中无法使用。当您知道如何在元素上设置 CSS 以及属性如何遍历 DOM 树时,"Shadow DOM" 的用途非常有限。我的示例设置了字体大小,因此模板 DOM 元素的所有子元素都将从模板继承完全符合您在问题中想要的输出。此外,Shadow DOM 的状态是 "正在考虑中",并不是一成不变的。 - Patrick W. McMahon
根据这个问题,Web组件是不必要的,而且过于复杂了。简单的CSS解决方案可以解决这个问题,而不需要使用一个不完全支持的函数。 - Patrick W. McMahon
1
我不同意。我曾经多次在混乱的CSS代码中工作,不得不花费数小时清理,因为通常情况下CSS会随着时间的推移变得非常混乱。多年来,CSS封装是我一直期待的东西。唯一缺少的(在我看来)是影子根元素。我知道WC尚未得到广泛支持,但它可用于内部系统,并希望在未来适用于所有网站。 - henit
我发了一个新答案,展示给你如何使用 Web 组件。请查看并注意,代码确实有效,但我仍然不建议在现场环境(甚至是内联网)中使用 beta 代码。很多坏事情可能会发生,而且许多事情可能会发生变化。将 Web 组件用作准备公共使用的测试,而不是为客户提供服务。 - Patrick W. McMahon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接