I'm using Polymer and I noticed that the :target css selector doesn't work.
For example
<polymer-element name="my-element" noscript>
<template>
<style>
:target {
border: 2px solid red;
}
</style>
<div id="test">This is a :target test</div>
</template>
</polymer-element>
Click me
<my-element></my-element>
DEMO
Any suggestions how I can fix this ?
I must admit: I'm not very familar with shadow DOM and absolutely not familar with Polymer but I'd like to tell you my view on this because your intention looks somewhat strange to me and this is too long for a comment.
Short
You can't use the pseudo selector :target within a shadow host.
Long
Unfortunately I was not able to find clear evidences in these resources
http://www.w3.org/TR/shadow-dom/
http://dev.w3.org/csswg/css-scoping/
but some hints...
The goal of Web Components was to give us the ability to build individual and isolated components that can be used in a document without caring of their inner function or style.
If a component could directly reach the "outside" document or if the outside document could reach any shadow hosts element directly, this would completely break the intention of Web Components.
Imagine what would happen if you insert two instances of your <my-element>. Both contain the same ID, which one should be targeted?
Of course it's possible to reach the shadow document, or the containing document from within the shadow document, but only through ::shadow or :host respectively.
To me its logical that the browser can't select elements using a mere :target selector since the target is the matter of the document (it's URL is targeted to some ID) not of any shadow DOM. It's also not possible to reach a shadow tree node with document.getElementById() from within the container document.
The CSS scoping spec which also adresses the Shadow DOM concepts states:
Why is the shadow host so weird?
The shadow host lives outside the shadow tree, and its markup is in
control of the page author, not the component author.
It would not be very good if a component used a particular class name
internally in a shadow tree, and the page author using the component
accidentally also used the the same class name and put it on the host
element. Such a situation would result in accidental styling that is
impossible for the component author to predict, and confusing for the
page author to debug.
(3.1.1. Host Elements in a Shadow Tree)
I'd say this is another evidence: the shadow host (viewed from outside) itself will keep the active (focus) state while handling the focus inside its tree.
To maintain encapsulation, the value of the Document object's focus
API property activeElement must be adjusted. To prevent loss of
information when adjusting this value, each shadow root must also have
an activeElement property to store the value of the focused element in
the shadow tree.
(6.3 Active Element)
One possible solution to your problem
If your intention was to highlight only the div, when your shadow element is :targeted this might be the correct style within your shadow document:
<polymer-element name="my-element" constructor="" attributes="">
<template>
<style>
:host(:target) #inner {
color: #0c0;
}
</style>
<content>Hello World!</content>
<div id="inner">This is a :target test</div>
...
It will highlight the <div> with green text, when your shadow element <my-element id="outer"></my-element> is targeted by #outer.
If this was not your intention and you really wanted to be able to target #inner from outside, I'd say this is not possible (see the "longer" part ;).
I don't think it's a good idea to link to elements inside shadow dom, because you may have multiple instances of the outer element in same page so you'll get multiple elements with same id.
However when you request a url with #elementId the browser will only look in light dom for the according element.
If you still need to style shadow dom elements you could simulate :target selector:
<polymer-element name="my-element" constructor="" attributes="">
<template>
<style>
#inner[target] {
border: 2px solid red;
}
</style>
<content>Hello World!</content>
<div id="inner" target?="{{innerTargetted}}">This is a :target test</div>
</template>
<script>
Polymer('my-element', {
ready: function() {
$(window).on('hashchange', function() {
this.innerTargetted = window.location.hash == '#inner';
}.bind(this));
}
});
</script>
</polymer-element>
Demo.
Related
I am working on a web extension that adds a custom overlay element to any website. naturally I don't want the websites CSS and JavaScript to interfere with my custom element, nor do I want to influence the appearance of any websites in any way other than with my overlay.
So naturally I turn to Shadow DOM. I soon learn that I can't simply attach my shadow DOM to document.body since this will displace the whole page.
It turns out I need one regular element in the body to act as a shadow root:
const shadowHost = document.createElement('div');
document.body.appendChild(shadowHost);
const shadow = shadowHost.attachShadow({mode: 'closed'});
But how can I protect this shadow root from being influenced by the css or js of the website?
What have you tried?
shadowRoots are not styled by global CSS, unless they are inheritable styles
https://lamplightdev.com/blog/2019/03/26/why-is-my-web-component-inheriting-styles/
mode:"closed" has nothing to do with CSS, it just doesn't publish the shadowRoot in element.shadowRoot, making it impossible to access the shadow DOM from the outside.
When the mode of a shadow root is "closed", the shadow root’s implementation internals are inaccessible and unchangeable from JavaScript—in the same way the implementation internals of, for example, the <video> element are inaccessible and unchangeable from JavaScript.
document.body
.appendChild(document.createElement('div'))
.attachShadow({mode:'closed'})
.innerHTML = `<style>h1{color:green}</style><h1>Hello</h1><h2>Component</h2>`;
<style>
body {
/* inheritable styles do style shadowRoots */
font: 10px Arial;
color: red;
}
</style>
I'm building JavaScript widgets that are supposed to be added onto other people's websites.
I style my widgets by dynamically adding a CSS to the pages they're on.
For example,
My CSS code below applies to a DIV inside my widget:
.myWidget { background-color: red; }
But a CSS file outside my own on a remote page might have:
div { border: 5px solid green; }
The CSS above would also apply to my widgets. How can I disable all other CSS outside my own?
Thanks
You could be Using shadow DOM
Shadow DOM MDN Web Docs
An important aspect of web components is encapsulation — being able to keep the markup structure, style, and behavior hidden and separate from other code on the page so that different parts do not clash, and the code can be kept nice and clean. The Shadow DOM API is a key part of this, providing a way to attach a hidden separated DOM to an element.
You can use the all shorthand property and the unset keyword to set each property's value to its initial value.
.myWidget {
all:unset;
background-color: red;
}
div {
background-color:yellow;
}
<div class="myWidget">Hello World!</div>
I am building a widget for third-party websites, using shadow DOM to prevent their CSS from interfering with ours. I am using the ShadyDOM and ShadyCSS polyfills to make it work in Edge and IE, but it is not transforming the CSS for the shadow DOM as I would expect.
Example:
<!DOCTYPE html>
<html>
<head>
<title>Shadow DOM test</title>
</head>
<body>
<div id="container">container is here</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/2.3.0/webcomponents-bundle.js"></script>
<script>
const shadow = document.getElementById("container").attachShadow({ mode: "open" });
const style = document.createElement("style");
style.innerHTML = `
:host .stuff {
background: #ff00ff;
}
`;
shadow.appendChild(style);
const div = document.createElement("div");
div.classList.add("stuff");
div.innerHTML = "stuff inside shadow dom";
shadow.appendChild(div);
</script>
</body>
</html>
In Chrome (which supports shadow DOM natively), the stuff div has a pink background, as I would expect. But in Edge (which does not support shadow DOM natively), I see the "stuff inside shadow dom" text (meaning my script ran and the ShadyDOM functions worked), but I don't see the pink background.
Why is this happening? I am attaching a shadow root to a plain old div, instead of using custom elements as the example in the ShadyCSS README does, but does that matter? If so, how can I make this work? I am working on a big, existing app, and not wanting to make too many changes at once, so I would strongly prefer to use the standard HTML elements I am already using (divs, buttons, etc.) instead of coming up with my own elements or templates, although I would be willing to consider templates and/or custom elements if it can be done easily, without having to make a lot of big changes.
With ShadyCSS
:host CSS pseudo-element is not known in Edge.
To make it work, you should use ShadyCSS.prepareTemplate() that will replace :host by the name of the custom element and define the style as a global style that will apply to all the page.
Remember that there's no Shadow DOM in Edge: there's no boundaries/scope for CSS with a fake/polyfilled Shadow DOM.
In your case you could use ShadyCSS.prepareTemplate( yourTemplate, 'div' ) as in the example below:
ShadyCSS.prepareTemplate( tpl, 'div' )
container.attachShadow( { mode: "open" } )
.appendChild( tpl.content.cloneNode(true) )
<script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/2.3.0/webcomponents-bundle.js"></script>
<template id=tpl>
<style>
:host .stuff {
background: #ff00ff;
}
</style>
<div class=stuff>stuff inside shadow dom</div>
</template>
<div id=container>container is here</div>
Note: since the polyfill will replace :host by div and add it as a global style, you could observe some side effects if you have another HTML code part that matches div .stuff.
Without ShadyCSS
ShadyCSS was designed for Custom Elements, but not really for standard elements. However, you should get inspiration from the polyfill and create explicitely the styles properties for fake (polyfilled) Shadow DOM. In your case replace :host with div#containter:
container.attachShadow( { mode: "open" } )
.appendChild( tpl.content.cloneNode(true) )
<script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/2.3.0/webcomponents-bundle.js"></script>
<template id=tpl>
<style>
div#container .stuff {
background: #ff00ff;
}
:host .stuff {
background: #ff00ff;
}
</style>
<div class=stuff>stuff inside shadow dom</div>
</template>
<div id=container>container is here</div>
I need a selector for usage in css inside a shadow root, which selects all children (but not grand children) of the shadow root, no matter what tag they are and without giving them an ID.
In the example below, SPAN,A,P and DIV should get a red border, but SPAN IN DIV not.
<my-element>
#shadow-root
<span>SPAN</span>
<a>A</a>
<p>P</p>
<div>
DIV
<span>SPAN IN DIV</span>
</div>
<style>
:root>*{border:1px red solid;}
</style>
</my-element>
I hoped, the :root-Selector would do the job inside of a shadow dom, but thats not the case.
It would also be a possible solution if someone shows how to set an ID on the shadow root.
Update:
Tried using #shadow-root > * as selector:
seems not to work. Probably it is just google chrome developer tools visualizing the shadow root element like that.
Use this selector: :host > *
The :host selector is described in https://drafts.csswg.org/css-scoping/#host-selector
document.querySelector( 'my-element' )
.attachShadow( { mode: 'open' } )
.innerHTML = `
<span>SPAN</span>
<a>A</a>
<p>P</p>
<div>
DIV
<span>SPAN IN DIV</span>
</div>
<style>
:host>*{border:1px red solid;}
</style>`
<my-element>
</my-element>
:host may also hold a compound selector, which must be places in brackets. E.g. :host([foo=bar]) selects a host element which has attribute foo set to bar.
we can use the shadow() method from cypress. you can use the get('selector before the shadow-root') method then shadow() method and use the find('locator') till your control/elements and at last you invoke the actual method e.g. click() or type() or select('index') on that control/element. Also you can use {force:true} aswell.
cy.get("mc-select[name='taxTypeCodes']").shadow().find('div.mc-component-template ').find('div').find('label.mc-input__container').find('div.mc-input__field').find('select').select('IN3',{force:true})
For more details please refer-cypress-shadow-dom
I need to dynamically apply some styling to elements .child-1 and .child-2 by adding CSS classes.
Should I add them once to #parent or to each .child-? If I add it to #parent would existence of #large-container affect the performance?
<div id="parent">
<div class="child-1"></div>
<div class="child-2"></div>
<div id="large-container">
<!-- a bunch of content here - p tags, images... -->
</div>
</div>
(.child-1 and .child-2 are absolute positioned elements on top of #large-container)
$('#parent').addClass('myClass1 myClass2');
vs
$('.child-1, .child2').addClass('myClass1 myClass2');
Same with just CSS:
.myClass1 .child-1,
.myClass2 .child-2 {
color: red;
}
/* vs */
.myClass1.child-1,
.myClass2.child-2 {
color: blue;
}
myClass1 myClass2 only apply styles to #child-1 and 2, they don't add any styles to #large-container.
Thank you for advice!
although i think my answer is impossible to verify from a profiler (are there any css/html profiling tools out there in terms of rendering the page etc?) I'll state it based on my experience:
$('#parent').addClass('myClass1 myClass2');
is faster than
$('#child-1, #child2').addClass('myClass1 myClass2');
simply because you are traversing the dom tree once rather than twice ie
$('#child-1, #child2').addClass('myClass1 myClass2'); is the same as
$('#child-1).addClass('myClass1 myClass2');
$('#child-1).addClass('myClass1 myClass2');
to theoretically prove that last point imagine your html code looked something like this:
<div id="parent">
<div id="child-1"></div>
... lots and lots of html nodes
<div id="child-2"></div>
</div>
then looking for #child-1 is a completely separate operation than looking for #child-2.. and when it comes to css/html optimisation.. one of the most expensive operations is the DOM tree traversal.
in the case of $('#parent').addClass('myClass1 myClass2'); you are traversing the DOM tree once (ie finding where #parent is then using css cascading to apply to the elements within the narrowed down #parent DOM subtree
to address the concern that #tMagwell raised about repainting #large-container here is another optimized way of applying css:
// store the child-1 node in a variable.. ie whenever you
// refer to it in the future.. you won't have to traverse the entire DOM again
var child1element = $('#child-1');
$('#child-1).addClass('myClass1 myClass2');
// referring to child1element costs you nothing big, it's already stored in a variable
child1element.siblings().addClass('myClass1 myClass2');
this code works of course assuming that there are only child-1 and child-2.. if you got child-3, child-4.. child-n and only want to apply it to child-n.. then you can use
child1element.siblings()[n] // where n is the index of the child you want to target, since siblings() returns an array
hope this helps!
update:
to address this specific point you raised in the comments:
Does the presence of #large-container slows down something when I add classes to #parent?
the answer is yes. let me give you a scenario where it definitely does:
css:
#parent .class1 .class2
{
font-size:10pt;
}
html:
<div id="parent">
<div id="child-1"></div>
<div id="child-2"></div>
<div id="large-container">
<!-- images etc -->
<p>hello world!<p>
<!-- many more p tags that has a lot of text and stuff -->
</div>
</div>
so in this example.. the font-size:10pt placed under #parent .class1 .class2 will definitely impact the contents of #large-container.. and the operation costs something.. i have no way to quantify how expensive that is (it would depend on the browser rendering engine etc).. but suffice it to say that there is some cost x that is higher than if you didn't just add class1 and class2 to the parent div.