Can a SVGStyleElement be disabled like an HTMLStyleElement can? - svg

Short version:
Unlike the HTMLStyleElement the SVGStyleElement has no disabled property (according to the MDN docs)
Question: can a whole SVGStyleElement be disabled/toggled?
Longer playground version:
I can inject HTML in a DIV and the HTMLStyleElement remains a HTMLStyleElement and respects the disabled state.
(there is a FOUC because unload=this.disabled=true kicks in after parsing)
The disabled Toggle Button works as it should.
Injected in an SVG the HTMLStyleElement becomes a SVGStyleElement
The onload is ignored (the exact behaviour I want for my Web Component)
FireFox shows the state undefined on first load because there is no disabled property
but!! the disabled Toggle button works in Chromium, not in FireFox (and Safari??)
Question: Which Browsers are doing what right?
<template id=TEMPLATE>
<style onload="this.disabled=true">
body { background: red; }
circle { fill:green; }
</style>
<circle cx="50%" cy="50%" r="45%"></circle>
</template>
<div id=DIV></div>
<svg id=SVG width="40" height="40"></svg>
<script>
let STYLE;// global
function inject(id) {
DIV.innerHTML=``;SVG.innerHTML=``; // wipe all
INNER.innerHTML=``;DISABLEDSTATE.innerHTML=``;
id.innerHTML = TEMPLATE.innerHTML; // set innerHTML in DIV or SVG
INNER.append(TEMPLATE.innerHTML); // show template HTML as text
setTimeout(() => { // wait till DOM is fully updated
STYLE = id.querySelector("style"); // get style tag in DIV or SVG
DISABLEDSTATE.append(STYLE.constructor.name,' - disabled state: ',STYLE.disabled);
})
}
function toggle(){
STYLE.disabled = !STYLE.disabled;
DISABLEDSTATE.innerHTML =``;
DISABLEDSTATE.append(STYLE.constructor.name,' - disabled state: ',STYLE.disabled);
}
</script>
<hr>
<button onclick="inject(DIV)">inject Template in DIV</button>
<button onclick="toggle()">toggle STYLE disabled state</button>
<button onclick="inject(SVG)">inject Template in SVG</button>
<hr><b>Injected HTML:</b><div id=INNER></div>
<b>Style disabled state:</b><div id=DISABLEDSTATE></div>

Both elements support the media property, so you can use the media="not all" hack found here: https://stackoverflow.com/a/59572781/1869660
Edit: Looks like SVGStyleElement.media is read-only in Chrome, so you need to start with a normal (enabled) style sheet and use both techniques:
<template id=TEMPLATE>
<style>
body { background: red; }
circle { fill:green; }
</style>
<circle cx="50%" cy="50%" r="45%"></circle>
</template>
function toggle() {
if ((STYLE.media === 'all') || !STYLE.media) {
STYLE.media = 'not all'; //Firefox
STYLE.disabled = true; //Chrome
}
else {
STYLE.media = 'all'; //Firefox
STYLE.disabled = false; //Chrome
}
DISABLEDSTATE.innerHTML = ``;
DISABLEDSTATE.append(STYLE.constructor.name, ' state: ', STYLE.media);
}
https://jsfiddle.net/mvb9fkay/

Chrome has implemented disabled on SVGStyleElement for some time now.
The SVG 2 specification says this about the style element
SVG 2 ‘style’ element shall be aligned with the HTML5 ‘style’ element.
In recognition of that the SVGStyleElement has recently had a disabled property added to it.
Additionally I've landed a patch to Firefox that implements disabled on SVGStyleElements. That should be available from Firefox 104 but you can already try it out in nightlies if you wish.

Related

Why does svg not get requested with app load

I've got a live site https://courageous-kulfi-58c13b.netlify.app/. Whenever a checkbox is clicked, it should turn green with a tick svg on it. However, on the first app load, clicking a checkbox will show a noticeable delay between the background turning green and the tick svg actually showing. NextJS only requests for the svg once I click on the checkbox. The svg is used in my css as a background image in a ::before pseudo element. How can I make next request for the svg in initial render so there isn't this delay?
const Checkbox = (props: Props) => {
const [checked, setChecked] = useState(false);
const handleClick = () => {
setChecked((prevChecked) => !prevChecked);
};
return (
<>
<input
type="checkbox"
onClick={handleClick}
className={cn({
"before:bg-tick": checked,
})}
></input>
</>
);
};
tailwind.config.js
module.exports = {
extend: {
backgroundImage: {
tick: "url(/Tick.svg)",
},
}
}
You can inline the svg with something like the following. Then there will be no delay in downloading the svg.
.tick:before {
content: url('data:image/svg+xml; utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 342.357 342.357" style="enable-background:new 0 0 342.357 342.357" xml:space="preserve"><path d="M290.04 33.286 118.861 204.427l-66.541-66.52L0 190.226l118.862 118.845L342.357 85.606z"/></svg>');
}
Note I have run the svg through svgo to optimize it.
Example here: https://play.tailwindcss.com/TlRvKKs53I?file=css
Preload image
The other option would be to preload the image in the html head.
<link rel="preload" as="image" href="/Tick.svg">
The old school hacky way was to include an image tag on the page somewhere, but use css to make it not visible, but the browser would still load it.

Touch events are wrong for transformed SVG elements

In the process of experimenting with scaling/panning an inline SVG image by applying a matrix transform I have discovered a rather peculiar thing. After loading the image I am attaching a touchstart event listener to some of the elements in the SVG image and the event fires right away when the object is touched. However, after applying a transform
document.getElementById('mysvg').setAttribute('transform''matrix(a b c d e)')
which has the effect of scaling and/or translating the entire SVG image touching the same object no longer triggered the expected touch event. After some experiment I found that the event could still be triggered by the touch location on screen had no bearing to the actual new placement of the object on the screen. I then proceeded to first removeEventListener followed by addEventListener for the object after issuing the matrix transform and lo & behold the touch event handling was back to normal.
Quite apart from the fact that I would like to avoid the rather expensive operations of removing & then reassigning the same event listener after each pan/zoom I would like to understand just why this is happening. It is like the browser is locating the pixel location of the object at the addEventListener stage and then holds on to that somewhere in its memory blissfully ignorant of any object displacements that might have occurred later.
Can anyone here tell me what is going on here and how I can go about retaining the utility of the touch event after pan & zoom in a more efficient manner?
I've set up a similar issue:
There is a <circle> element, with a transform attribute inside an <svg>.
The 'touchstart' event fires only at the first tap on the <circle>. After that it doesn't trigger the 'touchstart' event anymore.
I have found a strange workaround: Add a 'touchstart' eventListener to the <svg> element with a noop handler:
document.querySelector('svg').addEventListener('touchstart', () => {});
After this the <circle> triggers the 'touchstart' events perfectly.
You can test it with the folllowing snipet:
let debugLines = [];
let lines = 0;
function writeDebug(...t) {
let d = document.getElementById('debug');
debugLines.unshift(`${lines++}: ${t.join(' ')}`);
debugLines.splice(5);
d.innerHTML = debugLines.join('<br />');
}
document.querySelectorAll('circle')[0].addEventListener('touchstart', writeDebug);
/* remove comment from the line below to test workaround */
// document.querySelector('svg').addEventListener('touchstart', () => {});
<!doctype html>
<html>
<head>
<style>
svg { background: #f0f0f0; width: 200px; float: left; }
</style>
</head>
<body>
<div id="wrap">
<svg viewBox="-50, -50, 100, 100" class="b-circular-slider-svg">
<circle cx="0" cy="0" r="8"
stroke="#ccc" fill="#fafafa"
transform="translate(0, -10)"></circle>
</svg>
</div>
<strong>debug:</strong>
<div id="debug"></div>
</body>
</html>

reactjs unrecognized tag attributes

TLDR:
Is there a way to force-append an attribute to a react tag?
.
The full story:
I'm using reactjs and i've run into a problem with SVG and foreignObjects.
I wanted to center text in an SVG image so i figured the easiest approach would be to use a div in a foreign object.
It works fine on chrome, but in firefox the text isn't displayed.
On closer inspection, it appears that my
requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"
requiredExtensions="http://www.w3.org/1999/xhtml"
xmlns="http://www.w3.org/2000/svg"
Isn't coming through to the browser.
I've read the reactjs docs which suggested putting the prefix
data-
in, but the prefix stays in the browser.
I also tried to set the required features using the style={...}, but then this was inside the style string and not inserted as a tag attribute.
React Code:
import React, {Component,PropTypes} from 'react';
export default class myComponent extends Component {
render() {
return (<svg width = {this.props.width}
height = {this.props.height}>
<foreignObject x = {0} y = {0}
width = {this.props.width}>
<div> <p> {this.props.title} </p> </div > </foreignObject>
</svg>)
}
PARTIAL ANSWER:
I haven't been able to get text in a foreignObject to work with react in firefox, but i did work out how to set 'arbitary' tag attributes.
For each of the react components i assigned refs, i.e.:
<div style={divStyle} ref="d2">
<p style={{wordWrap: 'normal', textAlign: 'left', margin: 'auto', position: 'relative'}} key="p2">
{Math.round(this.props.kW)} W
</p>
</div>
Then in componentdidmount:
componentDidMount() {
this.refs.svg1.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
this.refs.svg1.setAttribute('version', '1.2');
this.refs.fo1.setAttribute('requiredExtensions', 'http://example.com/SVGExtensions/EmbeddedXHTML');
this.refs.d1.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
this.refs.fo2.setAttribute('requiredExtensions', 'http://example.com/SVGExtensions/EmbeddedXHTML');
this.refs.d2.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
this.refs.fo3.setAttribute('requiredExtensions', 'http://example.com/SVGExtensions/EmbeddedXHTML');
this.refs.d3.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
this.refs.fo4.setAttribute('requiredExtensions', 'http://example.com/SVGExtensions/EmbeddedXHTML');
this.refs.d4.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
this.refs.fo5.setAttribute('requiredExtensions', 'http://example.com/SVGExtensions/EmbeddedXHTML');
this.refs.d5.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
this.refs.fo6.setAttribute('requiredExtensions', 'http://example.com/SVGExtensions/EmbeddedXHTML');
this.refs.d6.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
}
This actually broke the foreign object in BOTH chrome and firefox!!
I think the issue is that i'm going by
https://www.w3.org/TR/SVG/extend.html
and they have:
<body xmlns="http://www.w3.org/1999/xhtml">
<p>Here is a paragraph that requires word wrap</p>
</body>
But since you can't use the <body> tag inside a react component, this doesn't get rendered.
I will manually edit the code in firefox and see if the inclusion of <body> in the foreign object fixes this issue.
I think the only option I have is to layer a second SVG component over the top of the first then use setInnerHTMLDangerously to write all of the foreign objects inside. Such a filthy hack!!

SVG + Angular2 code sample does not work with interpolation

My goal is to convert code from Angular 1.3 to Angular 2 (with SVG in both cases).
I tried the following simple test code, which works in case #1 that does not involve interpolation, but does not work in case #2 (which uses interpolation), and AFAICS the only difference in the generated SVG code is the inclusion of an extra attribute in the element: class="ng-binding"
Is there a way to suppress the preceding class attribute, or is there another solution?
Btw I wasn't able to get the formatting quite right (my apologies).
Contents of HTML Web page:
<html>
<head>
<title>SVG and Angular2</title>
<script src="quickstart/dist/es6-shim.js"></script>
</head>
<body>
<!-- The app component created in svg1.es6 -->
<my-svg></my-svg>
<script>
// Rewrite the paths to load the files
System.paths = {
'angular2/*':'/quickstart/angular2/*.js', // Angular
'rtts_assert/*': '/quickstart/rtts_assert/*.js', // Runtime assertions
'svg': 'svg1.es6' // The my-svg component
};
System.import('svg');
</script>
</body>
</html>
Contents of the JS file:
import {Component, Template, bootstrap} from 'angular2/angular2';
#Component({
selector: 'my-svg'
})
#Template({
//case 1 works:
inline: '<svg><ellipse cx="100" cy="100" rx="80" ry="50" fill="red"></ellipse></svg>'
//case 2 does not work:
//inline: "<svg>{{graphics}}</svg>"
})
class MyAppComponent {
constructor() {
this.graphics = this.getGraphics();
}
getGraphics() {
// return an array of SVG elements (eventually...)
var ell1 =
'<ellipse cx="100" cy="100" rx="80" ry="50" fill="red"></ellipse>';
return ell1;
}
}
bootstrap(MyAppComponent);
SVG elements do not use the same namespace as HTML elements. When you insert SVG elements into the DOM, they need to be inserted with the correct SVG namespace.
Case 1 works because you are inserting the whole SVG, including the <svg> tags, into the HTML. The browser will automatically use the right namespace because it sees the <svg> tag and knows what to do.
Case 2 doesn't work because you are just inserting an <ellipse> tag and the browser doesn't realise it is supposed be created with the svg namespace.
If you inspect both SVGs with the browser's DOM inspector, and look at the <ellipse> tag's namespace property, you should see the difference.
You can use outerHtml of an HTML element like:
#Component({
selector: 'my-app',
template: `
<!--
<svg xmlns='http://www.w3.org/2000/svg'><ellipse cx="100" cy="100" rx="80" ry="50" fill="red"></ellipse></svg>
-->
<span [outerHTML]="graphics"></span>`
})
export class App {
constructor() {
this.graphics = this.getGraphics();
}
getGraphics() {
// return an array of SVG elements (eventually...)
var ell1 =
'<svg><ellipse cx="100" cy="100" rx="80" ry="50" fill="red"></ellipse></svg>';
return ell1;
}
}
note that the added string has to contain the <svg>...</svg>
See also How can I add a SVG graphic dynamically using javascript or jquery?

One SVG file, many SVG gradients inside

I’m making a set of buttons which use dynamic gradients. I’ve taken care of Firefox 3.6+ and WebKit by using their proprietary CSS extensions and all I need to do is support Opera, iOS and IE9 by using background-image: url("gradient.svg").
This is relatively easy, I made an SVG file, linked it and got it working. However, I’m making a set so I need at least 6 gradients. When I normally do it in images, I create a sprite for fast HTTP access. I’m not sure how to achieve this in SVG – can I use one file and access different parts of its XML by using #identifiers, like XBL does?
My current SVG:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="select-gradient" x1="0" x2="0" y1="0" y2="1">
<stop offset="0%" stop-color="rgb(231,244,248)"/>
<stop offset="100%" stop-color="rgb(207,233,241)"/>
</linearGradient>
<style type="text/css">
rect {
fill: url(#select-gradient);
}
</style>
</defs>
<rect x="0" y="0" rx="6" ry="6" height="100%" width="100%"/>
</svg>
And then I have CSS:
.button-1 {
background-image: url("gradient-1.svg");
}
.button-2 {
background-image: url("gradient-2.svg");
}
I want to do something like this:
.button-1 {
background-image: url("gradient.svg#gradient1");
}
.button-2 {
background-image: url("gradient.svg#gradient2");
}
Is it even possible? Can you help me out? I really don’t wanna push 6 XML files when I can do it with one.
If you just want gradients for button backgrounds, most of this can be acheived in css. For the remaining browsers, ie6 + can user ms filters:
http://msdn.microsoft.com/en-us/library/ms532847.aspx
iOS uses webkit to render, so you can use -webkit vendor prefix. Unfortunately you will still need svg for opera, but this may make it easier (or just use a normal image sprite for opera's 1% of users)
in theory - according to SVG documentation #Params it is possible. You could use 2 params for setting up both colors, you could create multiple rects with different gradients, height set to 0 and then make only one 100% (like ?gradient2=100%)
What you could do is load your SVG file that contains all of the definitions first, and then load your other SVG files.
Using Firefox, jQuery SVG , and a minor shot of framework...
in your XHTML:
<div id="common_svg_defs"><!--ieb--></div>
<div id="first_thing"><!--ieb--></div>
<div id="second_thing"><!--ieb--></div>
in your JavaScript:
var do_stuff = function()
{
// load your common svg file with this goo.
$('#common_svg_defs').svg({
loadURL: 'path/filename.svg',
onLoad: function(svg, error) { run_test(svg, error);} });
}
var run_test = function(svg, error)
{
if (typeof(error) !== "undefined")
{
if (typeof(console.log) !== "undefined")
{
console.log(error);
}
}
else
{
// load your other svg files here, or just
// set a flag letting you know it's ready.
$('#first_thing').svg({
loadURL: 'path/anotherfilename.svg',
onLoad: function(svg, error) { somecallback(svg, error);} });
$('#second_thing').svg({
loadURL: 'path/anotherfilename.svg',
onLoad: function(svg, error) { somecallback(svg, error);} });
}
}
Because the id can be found in the documents scope, the SVG are capable of finding the IRI reference.
This allows you to define things once (that would not otherwise be defined in a css) and avoid id collisions.
Cheers,
Christopher Smithson

Resources