22
votes

Short description of issue:

I am looking for a way to set font size based values (similar to setting some property to an em-value) in an element inside a shadow dom relative to the shadow host, regardless of font size of the parent element in the shadow dom. Something similar to how rem-values work, but with the shadow as root instead of the html root.

The reason for this is to be able to scale content like a widget (that is inserted with shadow dom) in one place. If properties that is based on font size can be scaled this way, a whole widget can be scaled in every context without having to set a lot of css values to make it fit at every location.

Long description of issue:

I am looking for a way to size text (font-size) based on a given element in the DOM, to be able to scale different parts of content inside that element based on this wrapping element (good for widgets and such). This is not only for the actual text, there is often good to base a lot of css values on font-size, so the elements visual layout scale with the size of the text (like padding, border-radius and shadows). If everything is based on em-values, this can get a little messy. If there is multiple levels of inheritance of font-sizes inside the element, and I want to enlarge the first level without changing the second, I would need to enlarge the em of the first level in the dom, while reducing on any second level element (that is calculated based on that first level) to make text of sub-elements remain the same size. This is less than optimal in many cases.

A nice solution is basing stuff on Root EM (rem), so changing one level of the dom does not affect the sub-elements. However, if I want to enlarge/reduce the size of all text inside this wrapping elements (in one place) without affecting the other elements on the same page, I would need to enlarge/reduce the rem-values of all font-sizes for elements inside that wrapping element.

I recently started looking into Web Components with Shadow DOM and templates. In template-tags, css is encapsulated which is great because the design of that box/widget/component can be styled on its own without having to think of the rest of the design, unwanted inherited values and such. Finally a good way to make independent components to build up a webpage. But there would be great if I could set a kind of template root font-size for the template itself. So if I set some element inside the template to be font-size 2rem (or any other similar unit), then the font-size of that element would be 2x the root font size of the template, regardless of the font size in the host element where that template is used, and regardless of the font-size of the root of the page (the html element).

When I tested with rem-values inside a template, it was still based on the page root (html tag) font-size. I have also tested vw-values but this is also based on the whole viewport, and not just the encapsulated area (the shadow root).

I have looked at lots of articles about Shadow DOM, but I have not been able to find any solution on this issue. Any suggestions?

What happens (simplified examples):

<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>

What I want:

<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>

This would give one place (the :root part of the example above) to control all relative font-sizes inside that component, and this can then be combined with non-relative font-sizes like px or other normal css values.

3
I too want this. You really need to be able to base all sizing off the size of the container that houses your component. I posted a similar question.Lonnie Best

3 Answers

1
votes

after reviewing the Web Component documents. Your example of what you want to do looks nothing like a web component. see the following Web Component example.

in the header of your document you would have

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

in the body you would have

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

your web component would then have its own file.

<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>

Your example of what you're attempting to do and what you really want do not match. After a few replies from you I see you want to do Web Components. This is not what you where showing in your example of what you are attempting to do. Also this code only works if you have enabled the right flags in your Chrome Canary web browser. It will not work by default and needs the user to enable the correct beta settings for it to work. Even in an intranet I would not recommend using web components yet as it's still very much beta and you will have a lot of problems maintaining this with many internal users. Users find ways to reset the settings on the browser all the time. Users break everything new and shiny.

0
votes

The :host selector styles the element that hosts the shadow root. This has the same effect as styling the host element with the outer css/styles (e.g. <div id="rootelm" style="font-size:20px"></div> for the example below). If you use :host instead of your fake :root selector, it should work as you expect. Here is an example:

<!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

EDIT: Updated plunker with JS to make font sizes relative to the element that hosts the shadow root. Script is reproduced below.

    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();
    }
-2
votes

The problem is your calling a DOM object a "shadow root" what you have is another DOM element that's a branch of root. the tag template has no special meaning in HTML5. There is no such thing as a "shadow root". There is multiple ways of injecting a document into another document. using iframes, object tags, element tags. I do not recommend doing any of them as it breaks the ability of the document being pars properly along with a long list of other future problems you will have.

Your best option is to create a DOM element in CSS that has the base attributes you want to start with and using css to make sub items of that element change based on your css. You can create a DOM element template in css and give it a base style.

CSS file

template{
    font-size: 20px;
}

html file

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

this will set any DOM element named template to the font-size 20px and all sub dom elements will change based on the parent element. also try not to use inline style on elements, css selectors can do a lot of work for you.