How to prevent preservation of aspect ratio in svg.js - html

I am trying to render a rectangle with svg.js (https://svgjs.dev) and NOT preserve the aspect ratio when the parent/window is resized. It works fine for a plain svg element but not for the svg.js elements:
https://jsfiddle.net/h8sgown7/3/
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/svg.js/2.6.4/svg.min.js"></script>
<script type="text/javascript">
SVG.on(document, 'DOMContentLoaded', function() {
var draw = SVG('drawing')
var rect = draw.rect(300, 50).move(0, 0).fill('#ff0000');
draw.viewbox(0, 0, 300, 55);
draw.attr('preserveAspectRatio', 'none');
})
</script>
<title>SVGTest</title>
<style>
.drawing
{
width: 100%;
height:50px;
}
</style>
</head>
<body>
<div id="drawing"></div>
<svg width="100%" height="100px" viewbox="0 0 100 100" preserveAspectRatio="none">
<rect width="100" height="100" style="fill:rgb(0,0,255)" />
</svg>
</body>
</html>

You have set the height to 100px in your plain svg element. By default the svg is set to width="100%" height="100%" by svg.js.
Just change your svg.js generated svg height to match your inline svg.
draw.viewbox(0, 0, 300, 55).height('100px');
https://jsfiddle.net/h8sgown7/7/

Related

CSS styles not applied to SVG when SVG is rendered on Canvas

I am styling an SVG image using CSS in a separate file. Then I am rendering the SVG onto a canvas that can be saved as a PNG.
The SVG receives the CSS styles properly when it is just an SVG element on an HTML page, and renders as expected. However, when the SVG is rendered in a canvas element, the styles are not applied.
Is it possible to use external CSS to style an SVG and save that to a canvas without losing the styles? I cannot use inline CSS due to Content Security Policy in the browser.
Here is a sample.
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>svg to png</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<button>svg to png</button>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="200" height="200">
<path d="M10 40, C20 20, 50 20, 70 30, 70 30, 110 50, 160 20" id="path-1" fill="#CCCCCC" />
<text class="text-red">
<textpath xlink:href="#path-1" startOffset="50%" text-anchor="middle">Sample Path Text</textpath>
</text>
<rect x="10" y="14" width="10" height="10" />
<text x="0" y="100" class="text-primary">Text Style 1</text>
<text x="0" y="150" class="text-secondary">Text Style 2</text>
</svg>
<canvas id="canvas"></canvas>
<script src="./script.js"></script>
</body>
</html>
style.css
.text-primary {
font-size: 24px;
font-family: calibri;
}
.text-secondary {
font-size: 12px;
font-family: arial;
}
.text-red {
fill: #ff0000;
}
script.js
var btn = document.querySelector("button");
var svg = document.querySelector("svg");
var canvas = document.querySelector("canvas");
btn.addEventListener("click", function () {
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var data = new XMLSerializer().serializeToString(svg);
var DOMURL = window.URL || window.webkitURL || window;
var img = new Image();
var svgBlob = new Blob([data], { type: "image/svg+xml;charset=utf-8" });
var url = DOMURL.createObjectURL(svgBlob);
img.onload = function () {
ctx.drawImage(img, 0, 0);
DOMURL.revokeObjectURL(url);
var imgURI = canvas
.toDataURL("image/png")
.replace("image/png", "image/octet-stream");
};
img.src = url;
});
Here is a render of the basic example, svg on the left and canvas on the right.
css styles applied to svg on left, css styles not applied to svg on canvas on right

Drag an image and drop on top of a SVG element

I've created two circles using SVG and an image. I'm trying to drag the image into the circles and while I'm able to do so after dropping the image it is not being visible. How can I drop it on top of the circles.
<!DOCTYPE html>
<html>
<body>
<div id="circle" >
<svg id="dest" ondrop="drop(event)" ondragover="allowDrop(event)" width="250" height="100">
<circle id="yelcirc" cx="50" cy="50" r="50" fill="yellow" />
<circle id="greencirc" cx="160" cy="50" r="50" fill="green" />
</svg>
</div>
<img id="draglogo" src="logo.gif" draggable="true" ondragstart="drag(event)" class="draggable" ondragend="" width="105" height="73">
</body>
<script>
function allowDrop(ev) {
ev.preventDefault();
}
function drag(ev) {
ev.dataTransfer.setData("text", ev.target.id);
}
function drop(ev) {
ev.preventDefault();
var data = ev.dataTransfer.getData("text");
ev.target.appendChild(document.getElementById(data));
}
</script>
</html>
Apparently the ondrop and ondragover events are not detected on your svg tag. On top of that, images in SVG don't have the same syntax than in regular HTML.
This is a simple example of how you can achieve the basics of what you want to do, of course there are some adjustments to do, the position of the image, its size etc. So basically what i do here is getting the original image attributes to create an SVG image. You could also have a regular image placed outside of the SVG tag, but i'm not sure it will be easier for positioning and such.
You can also read this answer about emulating the drag events on SVG elements
NOTE: this works only for the first drag, even if the image still looks draggable after being moved, the function will throw an error because of the way img is selected from the DOM, it has been removed, so the img tag is not found anymore.
<!DOCTYPE html>
<html>
<body>
<div id="circle" ondrop="drop(event)" ondragover="allowDrop(event)" >
<svg id="dest" width="250" height="100">
<circle id="yelcirc" cx="50" cy="50" r="50" fill="yellow" />
<circle id="greencirc" cx="160" cy="50" r="50" fill="green" />
</svg>
</div>
<img id="draglogo" src="https://placeimg.com/105/73/any" draggable="true" ondragstart="drag(event)" class="draggable" ondragend="" width="105" height="73">
</body>
<script>
function allowDrop(ev) {
ev.preventDefault();
}
function drag(ev) {
ev.dataTransfer.setData("text", ev.target.id);
}
function drop(ev) {
ev.preventDefault();
var data = ev.dataTransfer.getData("text"),
img = document.getElementById(data),
imgSrc = img.getAttribute('src'),
imgW = img.getAttribute('width'),
imgH = img.getAttribute('height'),
//for example you can calculate X position from event circle
imgX = ev.target.getAttribute('cx') - ev.target.getAttribute('r');
ev.target.parentElement.innerHTML += '<image xlink:href="' + imgSrc + '" x="' + imgX + '" y="0" width="' + imgW + 'px" height="' + imgH + 'px"/>';
img.parentNode.removeChild(img);
}
</script>
</html>

Style SVG loaded to HTML as content tag with CSS

I have 3 files:
index.html
<!DOCTYPE html>
<html>
<head>
<link type="text/css" rel="stylesheet" href="style.css" />
</head>
<body>
<i class="logo myRed" aria-hidden="true"></i>
</body>
</html>
style.css
.logo:before {
content: url("logo.svg");
}
.myRed {
color: #ff2000;
}
logo.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" xmlns:xlink="http://www.w3.org/1999/xlink"
width="300" height="100">
<rect id="logo" width="300" height="100" />
</svg>
How to style the SVG that is pointed out in the CSS content property? (eg. color, font-size, ...) - like in FontAwesome.
You can't.
CSS content: url(image.ext) is similar to loading your image in a <img> tag. And loading an image in a <img> is under the hood loading it in an isolated document, inaccessible for anyone, and which can't access anything.
FontAwesome doesn't load icons like that, they build font-faces, and then call corresponding characters in the content property, e.g something like "\f07b".
So for the browser, FontAwesome icons are just text, and you can style it like any other text.
To style an SVG through CSS, it needs to be in the same document as your stylesheet (or cloned through <use>).
Ok, there is one hack, which may help you, but I can't tell how well it is nor will be supported:
Lea Verou demonstrated that we can (ab)use the :target CSS selector along with the #elem_id fragment identifier to show specific nodes of an SVG Element or apply specific rules.
In you svg's <style> you can create rules like these ones :
#elem_id_1:target ~ #elem_to_color{
fill: red;
}
#elem_id_2:target ~ #elem_to_color{
fill: green;
}
Then in your markup, you just need to have some empty elements placed before #elem_to_color with corresponding ids.
<g id="elem_id_1"></g>
<g id="elem_id_2"></g>
<rect id="elem_to_color"/>
Now when you will load your svg as yourfile.svg#elem_id_1, the first rule will apply and #elem_to_color will be red. If you load it as yourfile.svg#elem_id_2, then #elem_to_color will be green.
This hack allows to have a single svg file, on which we can externally control the rendered styles.
/* a single file for all colors */
.logo::after {
content: url(https://dl.dropboxusercontent.com/s/2pkolmx0d9pebgl/logo.svg);
}
.logo.green::after {
content: url(https://dl.dropboxusercontent.com/s/2pkolmx0d9pebgl/logo.svg#green);
}
.logo.red::after {
content: url(https://dl.dropboxusercontent.com/s/2pkolmx0d9pebgl/logo.svg#red);
}
<!-- logo.svg content
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="30" height="30">
<style>
#green:target ~ #elem_to_color{
fill: green;
}
#red:target ~ #elem_to_color{
fill: red;
}
</style>
<g id="red"></g>
<g id="green"></g>
<rect id="elem_to_color" width="30" height="30"/>
</svg>
-->
<i class="logo"></i>
<i class="logo green"></i>
<i class="logo red"></i>
Use these CSS properties:
fill: /* color */
font-size: /* font-size */
It will override the original SVG values, as demonstrated below.
.logo svg {
fill: #eee;
}
.logo svg text {
font-size: 30px;
}
.myRed {
color: #ff2000;
}
<div class="logo myRed" aria-hidden="true">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect id="logo" width="300" height="100" />
<text style=" stroke:black; fill:red;" x="30" y="50" font-size="8">Text Here</text>
</svg>
</div>
A more simplified solution would be the following. The first line of css sets the text color (fill), the second line sets the font properties for the text element and the third line targets the rectangle id (logo) to set the fill.
.logo svg {
fill: #ff0000;
}
.logo svg text {
font-size: 45px;
font-face:Arial,Helvetica;
font-weight:bold
}
#logo {
fill:#eee;
}
<div class="logo" aria-hidden="true">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect id="logo" width="300" height="100" />
<text x="30" y="50">Text Here</text>
</svg>
</div>

Chrome Memory Leak with moving SVG text and blur filter

I have a page with several SVG elements and each of them has an SVG Blur filter applied to it. I noticed that Chrome was running out of memory quickly if the elements keep moving continuously.
To reproduce check this codepen: http://codepen.io/anon/pen/xZJMVz
<html>
<head>
<meta charset="UTF-8">
<style> body {padding: 0; margin: 0;} </style>
<svg height="0" width="0">
<defs>
<filter id="f1">
<feGaussianBlur in="SourceGraphic" stdDeviation="2" />
</filter>
</defs>
</svg>
</head>
<body>
<svg height="30" style="position: absolute; top: 100px; width: 1000px;height: 500px;">
<text x="0" y="15" fill="black" id="t0">BLUR TEST!</text>
</svg>
<script>
(function() {
var svgText = document.getElementById('t0');
svgText.setAttribute('filter', 'url(#f1)');
var x = 0;
var y = 0;
setInterval(function(){
x+=1;
y+=1;
if(x > 1000) {
x = 0;
}
if( y > 500) {
y = 0;
}
svgText.setAttribute('transform', 'translate(' + x + ',' + y + ')');
}, 100);
})();
</script>
</body>
</html>
Open Chrome's Task Manager and notice memory usage by that tab. I filed a bug report on Chromium's Issue tracker. But wondering if anybody has run into this before and if there's a work around to this.
Thanks!
edit: Noticing this issue on Chrome 48.0.2564.97 m on Windows 7.

How to create a clickable grid of triangles using html, svg?

I have already created a grid of triangles like so:
svg {
margin-left: 0px;
margin-right: -60px;
padding: 0;
}
<div data-bind="foreach: Grid">
<div data-bind="foreach: $data.rowData">
<!-- ko if: $data.owner() === 0 && ($data.pos[0] + $data.pos[1])%2 === 0-->
<svg height="103.92" width="120">
<polygon class="" points="60,0 0,103.92 120,103.92" style="fill:grey;" data-bind="click: $root.test.bind($data, $data)" />
</svg>
<!-- /ko -->
<!-- ko if: $data.owner() === 0 && ($data.pos[0] + $data.pos[1])%2 === 1-->
<svg height="103.92" width="120">
<polygon class="" points="0,0 120,0 60,103.92" style="fill:grey;" data-bind="click: $root.test.bind($data, $data)" />
</svg>
<!-- /ko -->
</div>
</div>
My problem is that only the left half of the triangles is clickable. I think this is due to the (still rectangular) shape of the svg-element. But I have no idea how to fix this. Is there any way to make every triangle clickable in its whole area?
At the moment, all your individual SVGs are overlapping one another and any click that misses a triangle will be swallowed by the parent <svg> element.
The cleanest solution would be to put all your polygons in one big SVG. However there is another way around your problem using the pointer-events property.
Set pointer-events="none" on your <svg> elements so that clicks will pass through them. But you'll also need to set an explicit pointer-events="fill" on your polygons, since otherwise they'll inherit the "none" from their parent SVGs.
var output = document.getElementById("output");
document.getElementById("red").addEventListener("click", function(e) {
output.textContent = "red";
});
document.getElementById("green").addEventListener("click", function(e) {
output.textContent = "green";
});
svg {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
}
polygon {
pointer-events: fill;
}
#output {
margin-top: 120px;
}
<svg width="100px" height="100px">
<polygon points="0,0,100,0,100,100" fill="red" id="red"/>
</svg>
<svg width="100px" height="100px">
<polygon points="0,0,100,100,0,100" fill="green" id="green"/>
</svg>
<div id="output"></div>
You should use one svg tag with both polygons inside it. This way the Square svg elements won't overlap each other:
polygon {
fill: grey;
}
polygon:hover {
fill: #000;
}
<svg height="103.92" width="185">
<polygon points="60,0 0,103.92 120,103.92" />
<polygon points="65,0 185,0 125,103.92" />
</svg>