SVG shadow-root is closed - css

I have tried to animate an svg-sprite with CSS.
I’ve created a sprite and injected it from gulp:
gulp.task('svgstore', function () {
var svgs = gulp
.src('app/svg/*.svg')
.pipe(svgmin(function (file) {
return {
plugins: [{
cleanupIDs: {
minify: true
}
}]
}
}))
.pipe(svgstore({ inlineSvg: true }));
function fileContents (filePath, file) {
return file.contents.toString();
}
return gulp
.src('app/*.html')
.pipe(inject(svgs, { transform: fileContents }))
.pipe(gulp.dest('app/'))
});
…and inserted images from the sprite to HTML:
<svg class="icon-ufo" >
<use xlink:href="img/sprite.svg#ufo" aria-hidden="true"></use>
</svg>
And it works well, but the following image shows the shadow DOM is closed.
How I can to animate some styles of this SVG without JavaScript? But if JavaScript is the only way, how to do it better?

The DOM of the referenced element is not part of the DOM of the referencing HTML page. It has isolated style sheets.
But the shadow element inherits styles from the referencing <use> element. This means that as long as the referenced element does not set the styles itself in the sprite or in a style sheet associated with the sprite, you can change (and animate) every inheritable style property on the icon by styling the <use> element.

You could use "currentColor" property in your fill attribute to styling:
and styles for "icon-ufo" class will be like
.icon-ufo {
color: green;
}
.icon-ufo:hover {
color: red;
}

Related

Lit-element - :host selector is not triggering render on Safari

In a project I'm working on, I have this LitElement component, which is absolute-positioned and determines its left or top locations according to its reactive properties.
I've encountered a problem in Safari only, including iOS Safari/Chrome/Firefox. The element has updated its shadow styles, but in the view it does not move at all. I realized its a render issue when I found that when the cursor hovers the element, or exits the browser view, the element pops to the expected location.
I managed to reproduce the problem with a simpler code:
my-elem.ts:
import { LitElement, html, property, customElement, css } from 'lit-element';
#customElement('my-elem')
export class MyElem extends LitElement {
#property({ type: Number, reflect: true })
left: number = 30
static get styles() {
return css`
:host { position: absolute; }
div { position: absolute; }
`;
}
render() {
return html`
<style>
:host { left: ${this.left}px; }
</style>
<div> Hello </div>
`;
}
}
index.html:
<html lang="en">
<head>
<script src="my-elem.ts"></script>
</head>
<body>
<my-elem id="my-elem" left="50"></my-elem>
<button id="move-btn">move</button>
<script>
const elem = document.getElementById('ma-elem');
const moveBtn = document.getElementById('move-btn');
moveBtn.onclick = function() {
elem.left += 30;
}
</script>
</body>
</html>
I found that this happens only on :host selector. If the shadowed styling updates the div's left, it renders without any problems.
I wish to avoid forcing the browser to layout/paint, due to performance issues.
I think the root of your problem is that you are trying to use an expression inside a style element in your render template.
The LitElement guide mentions that using them that way has limitations and can cause performance issues. I think your problem is just facing one of them so you should remove that style element.
As an alternative, since you want to affect the host element, you can actually do this in an easier way if you just don't use a property but style the host directly.
So when using my-elem the code would look like:
<my-elem style="left: 30px;"></my-elem>
This is because styles applied to :host in shadow DOM have less priority than those applied to it using classes or the style attribute by its parent.
Alternatively, if you really want to keep the property, you can create property accessors for the left property and set the style to the host from there like this:
export class MyElem extends LitElement {
// other code
set left(value) {
const oldValue = this.left;
this._left = value;
this.style.setProperty('left', `${value}px`)
this.requestUpdate('left', oldValue);
}
get left() {
return this._left;
}
}

Vuejs apply loop in css to put hover

In VueJS, I have elements that have hover property in my object.
So, I want to put a foreach in style, but it is not possible.
I want to do that kind of thing :
<style>
#foreach (element in elements) {
if (element.has_backgroundhover) {
'#'+element.id:hover {
background : element.background_hover;
}
}
}
</style>
Notice that each element has a background color different (it is stored in his oibject property)
Thank you
The #mouseenter and #mouseleave event listeners would allow for css classes to be applied to each element.
For example, toggle a .hovered class that has the background color defined.
Something like this?
The HTML:
<div id="app">
<div
v-for="element of elements"
#mouseenter="element.hover=true"
#mouseleave="element.hover=false"
:style="{
background: element.hover? element.background_hover : element.background
}"
>{{element.name}}</div>
</div>
And the JS:
new Vue({
el: "#app",
data: {
elements:[
{
name:"element1",
background:"#f8f",
background_hover:"#a4a",
hover:false
},
{
name:"element2",
background:"#ff8",
background_hover:"#aa4",
hover:false
},
]
},
})
This is not using the CSS, rather using events as suggested by #DigitalDrifter. I think the point is that reactive css is not a good idea, and not supported in vue. Instead you need to have the HTML element properties dependent on your vue data object. A fiddle for this is: https://jsfiddle.net/edzaokum/

How do I use Tippy in shadow DOM with scoped CSS?

I want to use Tippy.js in my shadow DOM.
Since scripts can access my shadow DOM but styles can't, I tried to inline the Tippy CSS within the shadow DOM and link Popper's and Tippy's JS outside. Here is a demo of it not working.
I need the CSS to be scoped so I have the following constraint:
<style>
:host {
all: initial; /* 1st rule so subsequent properties are reset. */
display: block;
contain: content; /* Boom. CSS containment FTW. */
/* any other CSS, so this is where I inlined the Tippy's */
}
</style>
For people getting here from google and wandering what's the current process for putting tippy into shadow dom (e.g. for using with browser extension). The following works for me:
const shadowRoot = initShadowRoot()
function initShadowRoot() {
const shadowContainer = document.createElement("div")
const shadowRoot = shadowContainer.attachShadow({mode: "open"})
let style = document.createElement('style')
style.innerText = shadowCss // inline or use a bundler to give you a string representation
shadowRoot.appendChild(style)
document.body.appendChild(shadowContainer)
return shadowRoot
}
//---
//elsewhere when initializing tippy:
tippy(elment, {
// ...
// in shadow dom to avoid affecting the page styles
appendTo: () => shadowRoot,
//...
})
Get the target element, create a style tag to hold the inline CSS and append the style tag as a child.
const anchor = document.querySelector('#blog-header')
const style = document.createElement('style')
style.type = 'text/css'
const stylesheet = // inline Tippy.js CSS here or fetch it from somewhere else
style.appendChild(document.createTextNode(stylesheet))
anchor.content.prepend(style)

Responsive Props in Vue Component

I have a prop called src in a Vue Component that binds to a :style like this:
<template>
<section :class="color" class="hero" :style="{ backgroundImage: src && 'url(' + src + ')' }">
<slot></slot>
</section>
</template>
<script>
export default {
props: ['src', 'color']
}
</script>
What I would like to do is to create a list of responsive props that get used depending on the device or screen size of the site visitor.
For instance, I imagine a list of props like src-sm, src-md, src-lg, etc. The user would enter different image urls for different device sizes and the style attr would use the appropriate url depending on the screen/size.
Is this possible in VueJS. If so, any idea how?
Thanks.
Unfortuently what you are trying to do is not trivial. This is because inline style tags can not accept media queries.
The spec declares:
The value of the style attribute must match the syntax of the contents of a CSS declaration block
Solution 1:
This solution is the simplest, perhaps not entirely what you are looking for.
It works by including img elements, and showing an hiding them via CSS.
<template>
<div>
<img class="image--sm" :src="src.sm" />
<img class="image--md" :src="src.md" />
<img class="image--lg" :src="src.lg" />
</div>
</template>
<script>
export default {
props: {
src: Object
}
}
</script>
<style>
.image--md,
.image--lg {
display: none;
}
#media (min-width: 400px) {
.image--sm {
display: none;
}
.image--md {
display: block;
}
}
#media (min-width: 600px) {
.image--md {
display: none;
}
.image--lg {
display: block;
}
}
</style>
Example: https://jsfiddle.net/h3c5og08/1/
Solution 2:
Image tags may not be the desired effect you are trying to achieve. This solution creates a style tag in the head and injecting the css content to change the background images.
You can not have style tags in Vue template. It will throw an error like:
Templates should only be responsible for mapping the state to the UI. Avoid placing tags with side-effects in your templates, such as , as they will not be parsed.
As the error describes vue is designed the map state the UI. Using style tags in the template is prohibited because you can cause leaks to the outer world.
Although you can not declaratively styles in a template, we can use a bit of JS in the mounted hook of the component to add targetted and dynamic styles.
First we will need to constrain dynamic styles to this element. We can use the internal id of the created component this._uid, attaching to scope the css. (Note this is internal API so can be subject to change)
<template>
<div class="image" :data-style-scope="_uid">
</div>
</template>
The next part is to generate the style in a computed property, to later inject into a style block. You can expand on this computed property, to conditionaly assign properties ect. Note: keep the properties to the dynamic values only.
css () {
const selector = `.image[data-style-scope="${this._uid}"]`
const img = val => `${selector} { background-image: url("${val}"); }`
const sm = img(this.sm)
const md = img(this.md)
const lg = img(this.lg)
return `
${sm}
#media (min-width: 200px) { ${md} }
#media (min-width: 300px) { ${lg} }
`
}
This generated string from the css computed property is what we will now use when creating the style tag at mount. At mount we create a style node and append to the head. Assigning the nodes to the vm for references.
Using the references in the vm we can watch changes to the computed updating the style node.
Remember to clean up before destorying the component, removing the style node.
{
data () {
return {
// Reference data properties
style: null,
styleRef: null
}
},
mounted () {
// Create style node
let style = document.createElement('style')
style.type = "text/css"
style.appendChild(document.createTextNode(''))
// Assign references on vm
this.styleRef = style
this.style = style.childNodes[0]
// Assign css the the style node
this.style.textContent = this.css
// Append to the head
document.head.appendChild(style)
},
beforeDestroy () {
// Remove the style node from the head
this.style.parentElement.removeChild(this.style)
},
computed: {
css () {
// ...
}
},
watch: {
css (value) {
// On css value change update style content
this.style.textContent = this.css
}
}
}
Working Example: https://jsfiddle.net/bLkc51Lz/4/
You could also try the module described here: https://alligator.io/vuejs/vue-responsive-components/ which is called vue-responsive-components
It lets the component change its CSS depending on its own width (not on the entire browser's width)

styling svgs that are inline of css

.player
{
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10'><line x1='1' y1='1' x2='9' y2='9' stroke='red'/><line x1='1' y1='9' x2='9' y2='1' stroke='red'/></svg>");
width: 100%;
height: 100%;
display: inline-block;
}
.one
{
fill: black;
}
.two
{
stroke: white;
}
Elements with the player class will have a background image that is an svg. If there are elements with both the player and the one or two class, is it possible to change the style of the svg if it's inlined like so? If not, how would I do a simple color change by having classes? Ideally, I wouldn't want to introduce any more markup in the html and prevent http accesses for such a small svg file.
As Douglas wrote, you can style only IMG elements with inline "src".
I use this technique:
Use common image with the path to your SVG:
<img src="my.svg" alt="">
Transform the "src" to inline style using JavaScript (jQuery):
$('img').each(function(){
var $img = jQuery(this);
var imgID = $img.attr('id');
var imgClass = $img.attr('class');
var imgURL = $img.attr('src');
jQuery.get(imgURL, function(data) {
// Get the SVG tag, ignore the rest
var $svg = jQuery(data).find('svg');
// Add replaced image's ID to the new SVG
if(typeof imgID !== 'undefined') {
$svg = $svg.attr('id', imgID);
}
// Add replaced image's classes to the new SVG
if(typeof imgClass !== 'undefined') {
$svg = $svg.attr('class', imgClass+' replaced-svg');
}
// Remove any invalid XML tags as per http://validator.w3.org
$svg = $svg.removeAttr('xmlns:a');
// Replace image with new SVG
$img.replaceWith($svg);
}, 'xml');
});
Style your SVG using CSS:
path {
fill: #f00;
}
Done.
In order to modify your SVG with CSS, you have to use inline SVG, you can't modify it with a class if you use it as a background-image to an element.
If you want to make your authoring look cleaned up you can use a server side script to push the SVG's markup to your page (such as PHP's file_get_contents()).
Since there's no way around this, just compress your SVG as much as possible (I like this tool, there's some other good ones on the source I attached) and then write your svg as cleanly as possible, it shouldn't be that bloated looking if it's a small file.
Source: https://css-tricks.com/using-svg/
Sorry I couldn't be the bearer of better news.

Resources