How to spread SVG attributes in lit-element? - lit-element

Description
I want to pass svg attributes like following:
function svgIcon(paths: TemplateResult<2>[], props: Props) {
return svg`
<svg class="my-icon-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" ${spread(props)}>
${paths}
</svg>
`;
}
I searched in the issues but did not find any solution.
The spread directive is still under discussion since 2017...
Is there a workaround?

Related

fabricjs: v2.2.1 Where are SVG paths stored on fabric object if only a single path exists on SVG

I am upgrading fabricjs in my app from version 1.7.22 to 2.2.1.
I am mainly working with SVG objects that I am creating in Illustrator. Then in the app I am manipulating paths within the SVG objects.
In v1.7.22, the svg paths would be under a 'paths' property for each object...
Now with 2.2.1, I see that 'paths' is gone, and there is an '_objects' property.
This is all good.
My problem is that the '_objects' property exists only if there is more than 1 path on the SVG object, otherwise, I can't see where the SVG path is stored.
So if it's like this...
...then '_objects' property exists.
But if it's like this...
..then no '_objects' property.
So if there is an SVG object made with only a single path, where is the path stored in the fabric object?
As it is mentioned in doc groupSVGElements, it returns a fabric.Object (if svg contains one path)/ fabric.Group(if svg contains more than one path).
And stated in change log fabric.PathGroup has been removed.
If it contains more than one path, creates a fabric.Group object containing all the Path objects, else returns only the path object.
To get the path use object.path.
DEMO
var canvas = new fabric.Canvas('c');
var 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" 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 375 200" enable-background="new 0 0 400.5 477" xml:space="preserve"> <path d="M 100, 100m -75, 0a 75,75 0 1,0 150,0a 75,75 0 1,0 -150,0"/><path d="M250 0 L175 200 L325 200 Z" /> </svg>';
fabric.loadSVGFromString(svg, function(objects, options) {
var shape = fabric.util.groupSVGElements(objects, options);
console.log(shape.type)
if(shape.type == 'group'){
shape.forEachObject(function(obj){
console.log(obj.path)
})
}
else if(shape.type == 'path'){
console.log(shape.path)
}
canvas.add(shape);
document.getElementById('label').innerHTML = "Object is " + shape.type ;
});
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
<label id="label"></label>
<canvas id="c" height=300 width=400 style="border:1px solid #ccc"></canvas>

React & SVG: How do I make <path> support onClick?

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.

SVG use tag and ReactJS

So normally to include most of my SVG icons that require simple styling, I do:
<svg>
<use xlink:href="/svg/svg-sprite#my-icon" />
</svg>
Now I have been playing with ReactJS as of late evaluating it as a possible component in my new front-end development stack however I noticed that in its list of supported tags/attributes, neither use or xlink:href are supported.
Is it possible to use svg sprites and load them in this way in ReactJS?
MDN says that xlink:href is deprecated in favor of href. You should be able to use the href attribute directly. The example below includes both versions.
As of React 0.14, xlink:href is available via React as the property xlinkHref. It is mentioned as one of the "notable enhancements" in the release notes for 0.14.
<!-- REACT JSX: -->
<svg>
<use xlinkHref='/svg/svg-sprite#my-icon' />
</svg>
<!-- RENDERS AS: -->
<svg>
<use xlink:href="/svg/svg-sprite#my-icon"></use>
</svg>
Update 2018-06-09: Added info about href vs xlink:href attributes and updated example to include both. Thanks #devuxer
Update 3: At time of writing, React master SVG properties can be found here.
Update 2: It appears that all svg attributes should now be available via react (see merged svg attribute PR).
Update 1: You may want to keep an eye on the svg related issue on GitHub for additional SVG support landing. There are developments in the works.
Demo:
const svgReactElement = (
<svg
viewBox="0 0 1340 667"
width="100"
height="100"
>
<image width="667" height="667" href="https://i.imgur.com/w7GCRPb.png"/>
{ /* Deprecated xlink:href usage */ }
<image width="667" height="667" x="673" xlinkHref="https://i.imgur.com/w7GCRPb.png"/>
</svg>
);
var resultHtml = ReactDOMServer.renderToStaticMarkup(svgReactElement);
document.getElementById('render-result-html').innerHTML = escapeHtml(resultHtml);
ReactDOM.render(svgReactElement, document.getElementById('render-result') );
function escapeHtml(unsafe) { return unsafe.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'"); }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.1/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.1/umd/react-dom.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.1/umd/react-dom-server.browser.development.js"></script>
<h2>Render result of rendering:</h2>
<pre><svg
viewBox="0 0 1340 667"
width="100"
height="100"
>
<image width="667" height="667" href="https://i.imgur.com/w7GCRPb.png"/>
{ /* Deprecated xlink:href usage */ }
<image width="667" height="667" x="673" xlinkHref="https://i.imgur.com/w7GCRPb.png"/>
</svg></pre>
<h2><code>ReactDOMServer.renderToStaticMarkup()</code> output:</h2>
<pre id="render-result-html"></pre>
<h2><code>ReactDOM.render()</code> output:</h2>
<div id="render-result"></div>
Update september 2018: this solution is deprecated, read Jon’s answer instead.
--
React doesn’t support all SVG tags as you say, there is a list of supported tags here. They are working on wider support, f.ex in this ticket.
A common workaround is to inject HTML instead for non-supported tags, f.ex:
render: function() {
var useTag = '<use xlink:href="/svg/svg-sprite#my-icon" />';
return <svg dangerouslySetInnerHTML={{__html: useTag }} />;
}
If you encounter xlink:href, then you can get the equivalent in ReactJS by removing the colon and camelcasing the added text: xlinkHref.
You'll probably eventually be using other namespace-tags in SVG, like xml:space, etc.. The same rule applies to them (i.e., xml:space becomes xmlSpace).
As already said in Jon Surrell's answer, use-tags are supported now. If you are not using JSX, you can implement it like this:
React.DOM.svg( { className: 'my-svg' },
React.createElement( 'use', { xlinkHref: '/svg/svg-sprite#my-icon' }, '' )
)
I created a little helper that works around this issue: https://www.npmjs.com/package/react-svg-use
first npm i react-svg-use -S then simply
import Icon from 'react-svg-use'
React.createClass({
render() {
return (
<Icon id='car' color='#D71421' />
)
}
})
and this will then generate the following markup
<svg>
<use xlink:href="#car" style="fill:#D71421;"></use>
</svg>
I had problems with showing SVG in Gutenberg block, by referencing it with xlink:href. We used xlinkHref property in react, but after compiling, instead to render as xlink:href it was rendered to xlinkhref, and SVG was not displayed. After a lot of examining, I found out that xlink:href is deprecated (although it worked if we add it in html, or directly in chrome dev tools), and that href should be used instead. So after changing it to href it worked.
"SVG 2 removed the need for the xlink namespace, so instead of xlink:href you should use href." https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xlink:href
This is the code I used
SVG file
<svg id="svg-source" style="display: none;" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
<symbol id="svg-arrow-accordion" viewBox="0 0 15 24" fill="none">
<path id="Path_1662" data-name="Path 1662" d="M15.642,14.142h-3V1.5H0v-3H15.642Z" transform="translate(2 2) rotate(45)" fill="currentColor"></path>
</symbol>
</svg>
React file
<svg xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" width="15" height="24">
<use href="#svg-arrow-accordion"></use>
</svg>
This is svg Component.
const SvgComponent = () => {
return <svg width="0" height="0">
<defs>
<symbol id="test" viewBox="0 0 100 100">
<line x1='0' y1='50' x2='100' y2='50' strokeWidth='8' stroke="#000" />
</symbol>
</defs>
</svg>
}
export default SvgComponent
use component
import SvgComponent from './SvgComponent';
export default function App() {
return (
<>
<SvgComponent/>
<svg>
<use xlinkHref="#test"></use>
</svg>
</>
);
}

Using Meteor to create SVG in template works, but not in #each loop

Update: as of February 2014, Meteor supports reactive SVG, so no workaround is necessary.
Meteor 0.5.9
I would like to create a group of shapes, one for each document in the collection. I can create shapes one at a time in a template, but not inside of an {{#each loop}}.
This works:
<Template name="map">
<svg viewBox="0 0 500 600" version="1.1">
<rect x="0" y="0" width="100" height="100" fill={{color}}/>
</svg>
</Template>
Template.map.color = function() {
return "green";
};
This does not:
<Template name="map">
<svg viewBox="0 0 500 600" version="1.1">
{{#each colors}}
<rect x="0" y="0" width="100" height="100" fill={{color}}/>
{{/each}}
</svg>
</Template>
Template.map.colors = function() {
return [{color: "red"}, {color: "blue"}];
}
Anything I try to create inside of using {{#each}} just doesn't show up, even though I can create them manually, even with attributes inserted by Meteor through the template.
I also tried just sending a single object {color: "red"} to the template and using {{#with colors}}, and that does not work either. In addition to the SVG, I've also put plain s into the templates to make sure information gets to the template correctly, and those are all working as expected, with {{#each}} and with {{#with}}.
Should I be able to do what I'm trying to do?
(Updated April 1, 2013)
Found a way that combines Handlebars with insertion by Javascript. Have to give credit to this blog entry for figuring this one out:
http://nocircleno.com/blog/svg-and-handlebars-js-templates/
I created the following two files, placed them inside the client folder of a new Meteor directory and I got the html successfully.
Testing.js:
<head>
<title>testing</title>
</head>
<body>
</body>
<template name="map">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
{{#each colors}}
<rect x="0" y="{{yPosition}}" width="100" height="100" fill="{{color}}"/>
{{/each}}
</svg>
</template>
Testing.html:
(function () {
var count = 0;
Template.map.yPosition = function() {
count++;
return (count-1) * 100;
};
Template.map.colors = function() {
return [{color: "red"}, {color: "blue"}];
};
Meteor.startup(function() {
var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svgElement.width = 500;
svgElement.height = 600;
document.getElementsByTagName("body")[0].appendChild(svgElement);
var svgFragment = new DOMParser().parseFromString(Template.map(), "text/xml");
svgElement.appendChild(svgFragment.documentElement);
});
})();
I came across the same problem experimenting with Meteor and SVG elements and discovered that you can add elements and get them to show up with the two methods below. One option is to just wrap the elements in the each loop in an <svg></svg>, like this:
<svg viewbox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
{{#each pieces}}
<svg xmlns="http://www.w3.org/2000/svg"><circle cx="{{x}}" cy="{{y}}" r="1" fill="{{color}}"></circle></svg>
{{/each}}
</svg>
Another options is to (on template render) create an svg element with jQuery that contains the element you want to insert, then use jQuery to grab that inner element and insert it into the svg element already in the DOM, like so (in coffeescript):
for piece in Pieces.find().fetch()
$el = $("<svg><circle cx='#{piece.x}' cy='#{piece.y}' r='1' class='a'></circle></svg>")
$el.find('circle').appendTo #$('svg')
You could also use something like d3 or RaphaelJS to do the inserting. You can even make the individual elements reactive to your Collection and animate easily by using a library like d3 in the Collection observer callbacks like so (again, coffeescript):
Pieces.find().observe {
added: (piece)=>
# using jquery (could use d3 instead)
$el = $("<svg><circle cx='#{piece.x}' cy='#{piece.y}' r='1' fill='#{piece.color}' data-id='#{piece._id}'></circle></svg>")
$el.find('circle').appendTo #$('svg')
changed: (newPiece, oldPiece)=>
# using d3 to animate change
d3.select("[data-id=#{oldPiece._id}]").transition().duration(1000).attr {
cx: newPiece.x
cy: newPiece.y
fill: newPiece.color
}
removed: (piece)=>
#$("[data-id=#{piece._id}]").remove()
}
These methods seem to work in latest Chrome, Safari, Firefox browsers on Mac, but I haven't tested in others.
According to the Using Blaze page, Meteor will have first class support of SVG when Blaze is released.

Pure SVG way to fit text to a box

Box size known. Text string length unknown. Fit text to box without ruining its aspect ratio.
After an evening of googling and reading the SVG spec, I'm pretty sure this isn't possible without JavaScript. The closest I could get was using the textLength and lengthAdjust text attributes, but that stretches the text along one axis only.
<svg width="436" height="180"
style="border:solid 6px"
xmlns="http://www.w3.org/2000/svg">
<text y="50%" textLength="436" lengthAdjust="spacingAndGlyphs">UGLY TEXT</text>
</svg>
I am aware of SVG Scaling Text to fit container and fitting text into the box
I didn't find a way to do it directly without Javascript, but I found a JS quite easy solution, without for loops and without modify the font-size and fits well in all dimensions, that is, the text grows until the limit of the shortest side.
Basically, I use the transform property, calculating the right proportion between the desired size and the current one.
This is the code:
<?xml version="1.0" encoding="UTF-8" ?>
<svg version="1.2" viewBox="0 0 1000 1000" width="1000" height="1000" xmlns="http://www.w3.org/2000/svg" >
<text id="t1" y="50" >MY UGLY TEXT</text>
<script type="application/ecmascript">
var width=500, height=500;
var textNode = document.getElementById("t1");
var bb = textNode.getBBox();
var widthTransform = width / bb.width;
var heightTransform = height / bb.height;
var value = widthTransform < heightTransform ? widthTransform : heightTransform;
textNode.setAttribute("transform", "matrix("+value+", 0, 0, "+value+", 0,0)");
</script>
</svg>
In the previous example the text grows until the width == 500, but if I use a box size of width = 500 and height = 30, then the text grows until height == 30.
first of all: just saw that the answer doesn't precisely address your need - it might still be an option, so here we go:
you are rightly observing that svg doesn't support word-wrapping directly. however, you might benefit from foreignObject elements serving as a wrapper for xhtml fragments where word-wrapping is available.
have a look at this self-contained demo (available online):
<?xml version="1.0" encoding="utf-8"?>
<!-- SO: http://stackoverflow.com/questions/15430189/pure-svg-way-to-fit-text-to-a-box -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
width="20cm" height="20cm"
viewBox="0 0 500 500"
preserveAspectRatio="xMinYMin"
style="background-color:white; border: solid 1px black;"
>
<title>simulated wrapping in svg</title>
<desc>A foreignObject container</desc>
<!-- Text-Elemente -->
<foreignObject
x="100" y="100" width="200" height="150"
transform="translate(0,0)"
>
<xhtml:div style="display: table; height: 150px; overflow: hidden;">
<xhtml:div style="display: table-cell; vertical-align: middle;">
<xhtml:div style="color:black; text-align:center;">Demo test that is supposed to be word-wrapped somewhere along the line to show that it is indeed possible to simulate ordinary text containers in svg.</xhtml:div>
</xhtml:div>
</xhtml:div>
</foreignObject>
<rect x="100" y="100" width="200" height="150" fill="transparent" stroke="red" stroke-width="3"/>
</svg>
I've developed #Roberto answer, but instead of transforming (scaling) the textNode, we simply:
give it font-size of 1em to begin with
calculate the scale based on getBBox
set the font-size to that scale
(You can also use 1px etc.)
Here's the React HOC that does this:
import React from 'react';
import TextBox from './TextBox';
const AutoFitTextBox = TextBoxComponent =>
class extends React.Component {
constructor(props) {
super(props);
this.svgTextNode = React.createRef();
this.state = { scale: 1 };
}
componentDidMount() {
const { width, height } = this.props;
const textBBox = this.getTextBBox();
const widthScale = width / textBBox.width;
const heightScale = height / textBBox.height;
const scale = Math.min(widthScale, heightScale);
this.setState({ scale });
}
getTextBBox() {
const svgTextNode = this.svgTextNode.current;
return svgTextNode.getBBox();
}
render() {
const { scale } = this.state;
return (
<TextBoxComponent
forwardRef={this.svgTextNode}
fontSize={`${scale}em`}
{...this.props}
/>
);
}
};
export default AutoFitTextBox(TextBox);
This is still an issue in 2022. There is no way to define bounds and get text to scale in a pure scalable vector graphic. Adjusting the font size manually is still the only solution it seems, and the examples given are quite buggy. Has anybody figured out a clean solution that works? Judging by the svg spec it looks like a pure solution doesn't exist.
And to provide some sort of answer myself, this resource is the best I've found, is hacky, but works much more robustly: fitrsvgtext - storybook | fitrsvgtext - GitHub
I don't think its the solution for what you want to do but you can use textLength
with percentage ="100%" for full width.
<svg width="436" height="180"
style="border:solid 6px"
xmlns="http://www.w3.org/2000/svg">
<text x="0%" y="50%" textLength="100%">blabla</text>
</svg>
you can also add text-anchor="middle" and change the x position to center perfectly your text
this will not change the fontsize and you will have weird space letterspacing...
JSFIDDLE DEMO

Resources