I'm creating an interactive infographic using SVG, audio and some JavaScript.
I can validate the document by direct input using the W3C Validator, however, I get this error when trying to validate via URI or file upload:
XHTML element audio not allowed as child of SVG element
What am I missing? I understand <audio> is not standard SVG (ditto for the use of data-* attributes actually). What I don't understand is why the namespace declarations in the SVG tag wouldn't be sufficient.
Here is a minimum case:
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:html="http://www.w3.org/1999/xhtml"
viewBox="0 0 640 640">
<defs>
<audio id="consonant_pig_audio" xmlns="http://www.w3.org/1999/xhtml"><source type="audio/mpeg" src="https://bilingueanglais.com/tmp/ipa-infographic-preview-v1/audio/IPA-PIG.mp3"/></audio>
</defs>
<title>SVG with audio</title>
<rect class="trigger" width="640" height="640" data-target="consonant_pig" />
<script><![CDATA[
/** Shortcut to querySelector **/
function $(sel) { return document.querySelector(sel); }
function $all(sel) { return document.querySelectorAll(sel); }
/** Execute when the SVG is ready **/
(function() {
$all( '.trigger' ).forEach( function( element ) {
element.addEventListener( 'click', function() {
var audio = $( '#' + this.getAttribute('data-target') + '_audio' );
if ( audio !== null ) {
try { audio.currentTime=0; } catch(e) {} audio.play();
}
}, false );
});
})();
]]></script>
</svg>
As per Robert Longson's comment, I merely used <foreignObject> to produce 100% valid SVG -- note that its content needs to be wrapped in a <body> to be valid.
(I also got rid of data-attribute as I wanted the code to fully validate, even though they do work in the browsers in practice and are part of the SVG2 spec.)
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 640 640">
<defs>
<foreignObject width="0" height="0" requiredExtensions="http://www.w3.org/1999/xhtml">
<body xmlns="http://www.w3.org/1999/xhtml">
<audio id="consonant_pig_audio" xmlns="http://www.w3.org/1999/xhtml"><source type="audio/mpeg" src="https://bilingueanglais.com/tmp/ipa-infographic-preview-v1/audio/IPA-PIG.mp3"/></audio>
</body>
</foreignObject>
</defs>
<title>SVG with audio</title>
<rect class="trigger" width="640" height="640">
<metadata>consonant_pig</metadata>
</rect>
<script><![CDATA[
/** Shortcut to querySelector **/
function $(sel) { return document.querySelector(sel); }
function $all(sel) { return document.querySelectorAll(sel); }
/** Execute when the SVG is ready **/
(function() {
$all( '.trigger' ).forEach( function( element ) {
element.addEventListener( 'click', function() {
//console.log( this.querySelector( 'metadata' ).textContent );
var audio = $( '#' + this.querySelector( 'metadata' ).textContent + '_audio' );
if ( audio !== null ) {
try { audio.currentTime=0; } catch(e) {} audio.play();
}
}, false );
});
})();
]]></script>
</svg>
For the data-*-like attributes, another approach that validates would have been to rely on the id attribute or on <desc> elements. (If multiple data-*-like attributes are required on a single element, <metadata> ceases to be an option because it does not support the class attribute, while <desc> does.)
Related
This is what React SVG currently supports: http://facebook.github.io/react/docs/tags-and-attributes.html#svg-attributes
I'm trying to figure out how to make a shape I drew using the SVG path clickable.
If there is another way to draw a shape that can be made clickable, that works too.
Thanks!
I wrap my SVG with a div and apply any attributes that I desire (click handlers, fill colors, classes, width, etc..), like so (fiddle link):
import React, { PropTypes } from 'react'
function XMark({ width, height, fill, onClick }) {
return (
<div className="xmark-container" onClick={onClick}>
<svg className='xmark' viewBox="67 8 8 8" width={width} height={height} version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink">
<polygon stroke="none" fill={fill} fillRule="evenodd" points="74.0856176 9.4287633 71.5143809 12 74.0856176 14.5712367 73.5712367 15.0856176 71 12.5143809 68.4287633 15.0856176 67.9143824 14.5712367 70.4856191 12 67.9143824 9.4287633 68.4287633 8.91438245 71 11.4856191 73.5712367 8.91438245 74.0856176 9.4287633 74.0856176 9.4287633 74.0856176 9.4287633" />
</svg>
</div>
)
}
XMark.propTypes = {
width: PropTypes.number,
height: PropTypes.number,
fill: PropTypes.string,
onClick: PropTypes.func,
}
XMark.defaultProps = {
width: 8,
height: 8,
fill: '#979797',
onClick: null,
}
export default XMark
You can of course ditch the wrapper and apply the onClick to the svg element as well, but I've found this approach works well for me!
(I also try and use pure functions when possible https://hackernoon.com/react-stateless-functional-components-nine-wins-you-might-have-overlooked-997b0d933dbc)
This worked for me.
svg {
pointer-events: none;
}
path{
pointer-events: auto;
}
Then we can add on click event on path. worked!! thanks
I did it this way:
Just using polyline for example, it could be any SVG element.
export default class Polyline extends React.Component {
componentDidMount() {
this.polyline.addEventListener('click', this.props.onClick);
}
componentWillUnmount(){
this.polyline.removeEventListener('click', this.props.onClick);
}
render() {
const {points, style, markerEnd} = this.props;
return <polyline points={points}
ref={ref => this.polyline = ref}
style={style}
markerEnd={markerEnd}/>;
}
}
get ref in ref callback
on componentDidMount add click event listener to the ref
remove event listener in componentWillUnmount
I had similar problem with react, I was trying to handle onclick event for svg.
Simple css solved problem for me:
svg {
pointer-events: none;
}
You can use onClick as you do with other DOM elements.
Two major ways to do this:
You put an HTML event listener on the 'path' tag in the svg code. You will have to escape your code properly if you choose this method.
The following example features a star shape cut in two paths each of which logs "Hello" in the console ( console.log("Hello") )
Example:
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 225 213.6" style="enable-background:new 0 0 225 213.6;" xml:space="preserve">
<style type="text/css">
.st0{fill:#4D4D4D;}
.st1{fill:#494949;}
</style>
<g>
<path id="right" onmouseover="console.log("Hello")" class="st0" d="M43.5,131c4.4,4.2,6.3,8.7,5,15.1
c-3.4,17.2-6,34.6-9,51.9c-1.5,8.5,1.6,14.4,9.1,15.5c3.1,0.5,6.8-0.9,9.8-2.4c14.9-7.6,29.8-15.4,44.5-23.5c3-1.6,5.8-2.5,8.6-2.7
V0.1c-1.1,0.2-2.1,0.5-3.2,1.1c-2.7,1.4-5.2,4.2-6.7,7c-7.8,15.2-15.6,30.5-22.8,46C75.6,61,71,64.7,63.5,65.7
c-16.6,2.2-33.1,5.1-49.7,6.9C6.4,73.5,2.4,77.2,0,83.5c0,0.7,0,1.3,0,2c3.5,4.5,6.7,9.4,10.7,13.4C21.4,109.8,32.5,120.4,43.5,131
z"/>
<path id="left" onmouseover="console.log("Hello")" class="st1" d="M206.4,71.9c-15.9-2.1-31.8-4.7-47.7-6.9c-5.3-0.7-8.8-3.5-11-8.2c-8-16.2-16-32.5-24.1-48.7
c-2.9-5.8-7.3-8.8-12-8v184.8c3.5-0.2,6.9,0.7,10.6,2.7c14.7,8.1,29.6,15.8,44.5,23.5c2.9,1.5,6.7,2.9,9.8,2.4
c7.3-1,10.5-6.8,9.2-15c-3-17.8-5.9-35.6-9.2-53.3c-1.1-5.6,0.7-9.8,4.5-13.4c11.2-10.9,22.6-21.7,33.6-32.8c4-4,7.1-8.9,10.7-13.4
c0-0.7,0-1.3,0-2C222.3,74.1,214.5,72.9,206.4,71.9z"/>
</g>
</svg>
Example: https://svgshare.com/i/aex.svg
In Adobe Illustrator there is (currently called) SVG Iteractivity tool.
You can find it under the Window top menu.
Then select the path you need with direct selection tool, choose the HTML event from the SVG Interactivity widow and write your Javascript Code below.
Then click 'Export Selection' from the file menu or Save As... and save all as .svg
The result will be .svg file with automatically properly escaped code and HTML event on the 'path' tag.
I am trying to display svg markers using the HERE Javascript API. I have followed the documentation, however my twist is that my svg contains links to other svgs. The reason for this is that I would like to display markers that have the same pin shape, but a different icon in the centre of the pin. As the icons will also be used in other places on my website, it makes sense to save the svgs separately so they only need updating in one place.
I can get standard svgs to display as markers, but when I try and nest the svgs, no marker is displayed on the map.
This is my code so far:
pin.svg - This is the basic pin shape that all markers will use
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
width="46.093765"
height="63.352634">
<g
transform="translate(-8.9327811,-0.45623957)"
id="layer1">
<path
d="M 22.15625,0 A 21.544797,22.053723 0 0 0 0,22.0625 21.544797,22.053723 0 0 0 10.40625,40.9375 c 1.062265,1.795846 11.125,19.40625 11.125,19.40625 l 12.125,-20.0625 A 21.544797,22.053723 0 0 0 43.09375,22.0625 21.544797,22.053723 0 0 0 22.15625,0 z"
transform="translate(10.432788,1.9651232)"
id="path5014"
style="fill:#cccccc;fill-opacity:1;stroke:#6e6e6e;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
d="m 50.723066,23.370655 a 17.218699,16.540131 0 1 1 -34.437397,0 17.218699,16.540131 0 1 1 34.437397,0 z"
transform="matrix(0.94189641,0,0,1.0010512,0.4199413,0.48436132)"
id="path5019"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
</g>
</svg>
here-example.svg - This is the example from the HERE site. I am using it as an icon that gets placed inside the pin
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg">
<rect stroke="black" fill="red" x="1" y="1" width="22" height="22" />
<text x="12" y="18" font-size="12pt" font-family="Arial" font-weight="bold" text-anchor="middle" fill="yellow">
C
</text>
</svg>
pin-with-icon.svg - This is my nested svg - it pulls in the pin and the icon and overlaps them
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<image x="20" y="20" width="300" height="80" xlink:href="pin.svg" />
<image x="120" y="32" width="100" height="30" xlink:href="here-example.svg" />
</svg>
index.html - This is where the HERE map is used and I try and display the markers. It makes heavy use of the HERE developer documentation. Replace <App_ID> and <App_Code> with the HERE credentials. In this page I also try and load the problematic svg into a div to prove that it works.
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
<script src="http://js.api.here.com/v3/3.0/mapsjs-core.js" type="text/javascript" charset="utf-8"></script>
<script src="http://js.api.here.com/v3/3.0/mapsjs-service.js" type="text/javascript" charset="utf-8"></script>
<script src="http://js.api.here.com/v3/3.0/mapsjs-ui.js" type="text/javascript" charset="utf-8"></script>
<script src="http://js.api.here.com/v3/3.0/mapsjs-mapevents.js" type="text/javascript" charset="utf-8"></script>
<script src="http://code.jquery.com/jquery.min.js" type="text/javascript" charset="utf-8"></script>
<link rel="stylesheet" type="text/css" href="http://js.api.here.com/v3/3.0/mapsjs-ui.css" />
</head>
<body>
<div style="width: 640px; height: 480px" id="mapContainer"></div>
<!-- Display the svg just to prove that the loads correctly -->
<div id="svgPin"></div>
<script>
// Initialize the platform object:
var platform = new H.service.Platform({
'app_id': '<App_ID>',
'app_code': '<App_Code>'
});
// Obtain the default map types from the platform object
var maptypes = platform.createDefaultLayers();
// Instantiate (and display) a map object:
var map = new H.Map(
document.getElementById('mapContainer'),
maptypes.normal.map,
{
zoom: 6,
center: { lng: 13.4, lat: 52.51 }
});
// MapEvents enables the event system
// Behavior implements default interactions for pan/zoom (also on mobile touch environments)
var behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(map));
// Example with svg pins not showing (svg contains links to other svgs)
var svgMarkupRetrieval = $.get('pin-with-icon.svg', function (svg) {
// Just to prove that the svg is loaded correctly
$('#svgPin').html(svg);
var icon = new H.map.Icon(svg);
// Add the first marker
var marker1 = new H.map.Marker({ lat: 52.4, lng: 13.3 },
{ icon: icon });
map.addObject(marker1);
// Add the second marker.
var marker2 = new H.map.Marker({ lat: 51.45, lng: 13.3 },
{ icon: icon });
map.addObject(marker2);
}, 'text');
// Example with svg pins showing (from HERE developer guide)
var svgMarkupRetrieval = $.get('here-example.svg', function (svg) {
var icon = new H.map.Icon(svg);
// Add the first marker
var marker3 = new H.map.Marker({ lat: 50.4, lng: 13.3 },
{ icon: icon });
map.addObject(marker3);
// Add the second marker.
var marker4 = new H.map.Marker({ lat: 49.45, lng: 13.3 },
{ icon: icon });
map.addObject(marker4);
}, 'text');
</script>
</body>
</html>
To use the code, create each file with the name I specify and put them in a folder in IIS. index.html has to be run through IIS, otherwise a cross origin request error occurs.
The pin-with-icon.svg loads separately (I load it under the map), as do the standard svg markers. But I cannot see why the markers I create using pin-with-icon.svg do not show on the map. Any help would be appreciated.
Use H.map.DomMarker and H.map.DomIcon
I'd like to obtain object IDs from an SVG-file via coordinates.
For example in
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1"
height="50" width="50">
<rect id="rectRED"
x="15" y="5" height="30" width="30"
style="fill:#ff0000;fill-opacity:0.5;stroke:#000000;stroke-width:1.5" />
<rect id="rectBLUE"
x="5" y="15" height="30" width="30"
style="fill:#0000ff;fill-opacity:0.5;stroke:#000000;stroke-width:1.5" />
</svg>
getObjectsAt(10,25) should return a List containing rectBLUE
getObjectsAt(25,25) should return a List containing rectRED and rectBLUE
getObjectsAt(10,10) should return something like NIL
Is there a way to accomplish this?
There's document.elementFromPoint method, but it only returns the topmost element. To get all the elements under a point you could find the topmost one, hide it and look at the point again until no more elements are there:
var elementsAt = function( x, y ){
var elements = [], current = document.elementFromPoint( x, y );
// at least one element was found and it's inside a ViewportElement
// otherwise it would traverse up to the <html> root of jsfiddle webiste.
while( current && current.nearestViewportElement ){
elements.push( current );
// hide the element and look again
current.style.display = "none";
current = document.elementFromPoint( x, y );
}
// restore the display
elements.forEach( function( elm ){
elm.style.display = '';
});
return elements;
}
http://jsfiddle.net/duo02d38/
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?
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