SVG Path Fill property not working as it should - svg

please have a look at this code and let me know where I'm going wrong, the fill property does not appear to be working? This is supposed to mimic a car turn signal.
I have checked and it appears this is the correct use of the fill property but it is not working as expected. Also I have tried with a much simple path such as a triangle and this worked - so what is unique about this shape if path = path?
function trafficLighttoggle() {
var pth = document.getElementById("pth");
if (pth.classList == "traffic") {
pth.classList = "white";
} else {
pth.classList = "traffic";
}
}
setInterval(trafficLighttoggle, 500);
.traffic {
stroke: #ffbf00;
stroke-width: 1px;
fill: #ffbf00;
fill-opacity: 1;
}
.white {
stroke: white;
stroke-width: 1px;
fill: white;
fill-opacity: 1;
}
<svg overflow="visible">
<path id="pth" class="traffic" d="M160,90 L210,50 M210,50 V70 M210,70 H290 M290,70 V120 M290,120 H210 M210,120 V140 M210,140 L160,90 Z"/>
</svg>

I found out what is wrong with my code, I got my path generator incorrect it should have been
<path id="pth" class="traffic" d="M160,90 L210,50 V70 H290 V120 H210 V140 Z"/>
So this is now resolved.
Thanks.

Related

SVG - Draw scrolling issue with multiple instances

I am trying to create a page (in Wordpress), which is essentially a timeline. As the user scrolls to the next sections, I have a vertical line that "connects" to the next content section. After a LOT of trial and error, I was able to create a line that "draws" itself on scroll, and reverses when scrolling back up. My issue is, when I try to use the code again in the same page, it is already drawn, -- in other words, I *think there is an issue with the code not knowing that is is not supposed to trigger yet. I do not know enough about this to know why it is not working. ideally, I want each line to start drawing as the view-box/browser window is in view.
I have tried creating unique ID's, unique div's and ID's, etc. I originally thought it may be an issue with needing unique containers/ID's. Now, I am *thinking it might be because I do not know how to tell the "line" to not be visible until it is pulled into view.
Here is my pen:
// Get the id of the <path> element and the length of <path>
var triangle = document.getElementById("triangle");
var length = triangle.getTotalLength();
// The start position of the drawing
triangle.style.strokeDasharray = length;
// Hide the triangle by offsetting dash. Remove this line to show the triangle before scroll draw
triangle.style.strokeDashoffset = length;
// Find scroll percentage on scroll (using cross-browser properties), and offset dash same amount as percentage scrolled
window.addEventListener("scroll", myFunction);
function myFunction() {
var scrollpercent = (document.body.scrollTop + document.documentElement.scrollTop) / (document.documentElement.scrollHeight - document.documentElement.clientHeight);
var draw = length * scrollpercent;
// Reverse the drawing (when scrolling upwards)
triangle.style.strokeDashoffset = length - draw;
}
body {
height: 200vh;
}
#mySVG {
position: relative;
top: 15%;
left: 50%;
width: 50px;
height: 710px;
}
<svg id="mySVG" preserveAspectRatio="none" viewBox="0 0 4 100">
<path fill="none" stroke="#000000" stroke-width="1"
id="triangle" d="M 0 0 V 100 0"/>
</svg>
Whenever you need to manipulate multiple elements, you need to query these elements to get an array/list and then loop through all nodes in this array.
Usually it's a better approach to use class names to avoid non-unique IDs like so
let triangles = document.querySelectorAll(".triangle");
triangles.forEach( (triangle) => {
// do something
});
So add class names (or just replace id attributes) to your <path> elements and add a scroll EventListener
let triangles = document.querySelectorAll(".triangle");
// calculate path length and init
triangles.forEach((triangle) => {
let pathLength = triangle.getTotalLength();
triangle.setAttribute("stroke-dasharray", `0 ${pathLength}`);
});
// Find scroll percentage on scroll
window.addEventListener("scroll", (e) => {
drawLines(triangles);
});
function drawLines(triangle, pathLength) {
var scrollpercent =
(document.body.scrollTop + document.documentElement.scrollTop) /
(document.documentElement.scrollHeight -
document.documentElement.clientHeight);
triangles.forEach((triangle) => {
let pathLength = triangle.getAttribute("stroke-dasharray").split(" ")[1];
var dashLength = pathLength * scrollpercent;
triangle.setAttribute("stroke-dasharray", `${dashLength} ${pathLength}`);
});
}
body {
padding:0 5em;
height: 200vh;
margin:0;
}
svg {
position: relative;
top: 15%;
left: 50%;
width: 50px;
height: 710px;
}
path{
transition:0.4s 0.4s;
}
<svg preserveAspectRatio="none" viewBox="0 0 4 100">
<path fill="none" stroke="#000000" stroke-width="1" class="triangle" d="M 0 0 V 100 0" />
</svg>
<svg preserveAspectRatio="none" viewBox="0 0 4 100">
<path fill="none" stroke="#000000" stroke-width="1" class="triangle" d="M 0 0 V 100 0"/>
</svg>
You can also simplify your stroke animation by using the stroke-dasharray attributes specifying 2 arguments:
dashlength
dash gap
stroke-dasharray="50 100"
You can even skip the length calculation getTotalLength() by applying these fixed initial attributes pathLength="100" and stroke-dasharray="0 100".
This way you can work with percentages without the need to calculate the exact length of your element.
let triangles = document.querySelectorAll(".triangle");
// Find scroll percentage on scroll
window.addEventListener("scroll", (e) => {
drawLines(triangles);
});
function drawLines(triangle, pathLength) {
var scrollpercent =
(document.body.scrollTop + document.documentElement.scrollTop) /
(document.documentElement.scrollHeight -
document.documentElement.clientHeight);
triangles.forEach((triangle) => {
var dashLength = 100 * scrollpercent;
triangle.setAttribute("stroke-dasharray", `${dashLength} 100`);
});
}
body {
padding:0 5em;
height: 200vh;
margin:0;
}
svg {
position: relative;
top: 15%;
left: 50%;
width: 50px;
height: 710px;
}
path{
transition:0.4s 0.4s;
}
<svg preserveAspectRatio="none" viewBox="0 0 4 100">
<path fill="none" stroke="#000000" stroke-width="1" class="triangle" d="M 0 0 V 100 0" pathLength="100" stroke-dasharray="0 100"/>
</svg>
<svg preserveAspectRatio="none" viewBox="0 0 4 100">
<path fill="none" stroke="#000000" stroke-width="1" class="triangle" d="M 0 0 V 100 0" pathLength="100" stroke-dasharray="0 100"/>
</svg>

SVG elements to zoom whole SVG group on click or mouseover

I would like to use the circles within my SVG file to trigger a zoom in centred on the circle. I have got it working with a div acting as the trigger for the zoom but if I instead apply id="pin" to one of the circle elements within the SVG it no longer zooms in. Can anyone tell me why this is?
Is there a better way for me to achieve what I am trying to do? Ideally, I would like it to be possible to click to zoom and then to access other interactivity within the SVG while zoomed in.
If this is not possible is there a simple way to zoom and pan an SVG and to be able to access SVG interactivity while zoomed?
If I have missed something obvious please forgive me, I’m very much still learning the basics!
Rough example:
CodePen link
<div id="pin">click to trigger zoom</div>
<div class="map" id="mapFrame">
<svg class="image" id="mapSVG" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1920 1442.5"" style="
enable-background:new 0 0 1920 924.9;" xml:space="preserve">
<g id="Layer_7" data-name="Layer 7">
<image width="1800" height="1350" transform="translate(0) scale(1.069)" opacity="0.3"
xlink:href="https://media.npr.org/assets/img/2020/07/04/seamus-coronavirus-d3-world-map-20200323_wide-a3888a851b91a905e9ad054ea03e177e23620015.png" />
</g>
<g id="one">
<circle cx="929.664" cy="944.287" r="81.191"/>
</g>
<g id="two">
<circle cx="638.164" cy="456.863" r="81.191" />
</g>
<g id="three">
<circle cx="1266.164" cy="498.868" r="81.191" />
</g>
</svg>
</div>
<script src="app.js"></script>
svg {
width: 100%;
height: auto;
}
#pin {
position: absolute;
height: 65px;
width: 75px;
top: 300px;
left: 550px;
padding: 10px;
background-color: yellow;
}
let imgElement = document.querySelector('#mapFrame');
let pinElement = document.querySelector('#pin');
pinElement.addEventListener('click', function (e) {
imgElement.style.transform = 'translate(-' + 0 + 'px,-' + 0 + 'px) scale(2)';
pinElement.style.display = 'none';
});
imgElement.addEventListener('click', function (e) {
imgElement.style.transform = null;
pinElement.style.display = 'block';
});
When you click on the circle, you are also clicking on the background image as well, triggering two events which is essentially cancelling the zoom. You can see this if you place alert('click 1'); and alert('click 2'); in your listeners.
This doesn't happen on the #pin element because it's outside background div and avoids the event bubbling up. This is solved by adding event.stopPropagation();
Code from your CodePen:
let imgElement = document.querySelector('#mapFrame');
let pinElement = document.querySelector('#one'); //changed to #one
pinElement.addEventListener('click', function (e) {
imgElement.style.transform = 'translate(-' + 0 + 'px,-' + 0 + 'px) scale(2)';
pinElement.style.display = 'none';
event.stopPropagation(); //added to prevent bubbling
});
imgElement.addEventListener('click', function (e) {
imgElement.style.transform = null;
pinElement.style.display = 'block';
});

Making a looping drawSVG animation smoother

I'm a novice with GSAP and am experimenting with the plugin. I want to create an effect where a rectangles border is drawn by a small path in a continuous loop. I've mostly achieved this, but the end of the animation is pretty choppy when it resets.
Here's a link to the codepen I'm working on.
How can I avoid that abrupt ending to the animation?
I've tried setting the animation to drawSVG: "100% 115%" and that didn't work.
I haven't tried changing the easing yet as I don't think that would fix it.
Thanks for helping!
I believe this tutorial is exactly what you're trying to achieve Codepen
code required for codepen link
I forked your pen so you can see it in action
Codepen
Hope this is what you where looking for
The previous answer didn't sit right with me as I felt like something more elegant could be done. I found a simple solution where GSAP isn't used, but CSS animations are.
The result is a smooth animation using only one element.
GSAP manipulates CSS animations anyway so this is a simplification of the original problem.
https://codepen.io/strigiforme/pen/zLbYWm
<svg viewbox = "0 0 200 200">
<rect id = "rect" x = "85" y = "20" width="30" height="30" stroke = "red" stroke-width = "1.5" fill="none" stroke-dasharray="30 90" stroke-dashoffset = "240"></rect>
body {
background-color: #222;
overflow: hidden;
}
#rect{
animation: draw 5s linear infinite;
}
#keyframes draw{
14%{
stroke: orange;
}
28%{
stroke: yellow;
}
42%{
stroke: green;
}
56%{
stroke: blue;
}
70%{
stroke: indigo;
}
84%{
stroke: violet;
}
98%{
stroke: red;
}
to{
stroke-dashoffset: 0;
}
}

Node JS Sharp - Maintain SVG Font When Converting To JPG

I am using the sharp library to create dynamic JPEG license plate images.
Basically, I have a PNG that is a vanity license plate with no numbers. Then I create an svg in code like so
const svg = new Buffer(
`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="${x} ${y} 500 40">
<defs>
<style type="text/css">
<![CDATA[
#font-face {
font-family: LicensePlate;
src: url('LicensePlate.ttf');
}
svg {
width: 100%;
height: 100%;
}
]]>
</style>
</defs>
<text x="0" y="0" font-family="LicensePlate" font-size="${fontsize}" letter-spacing="${letterspace}">
${platenumber.toUpperCase()}
</text>
</svg>`
);
Passing in the desired width, height, and license plate number. Then I use the sharp library to overlay my SVG in the middle of the license plate. This all works just fine.
However, I have imported a custom license plate font (LicensePlate.ttf). In order to debug my in-code SVG image I made an actual svg image file that I open in the browser to make sure that it all looks correct, which it does.
The problem is that when the final JPEG file is created it does not contain my custom font. Instead it falls back on Verdana.
My question is, is there any way I can maintain the SVG font while creating the image with sharp?
Thanks!
Full Code
function createImage(platenumber) {
//Trying to create some sort of responsiveness
let fontsize = 80;
let letterspace = 10;
let width = 300;
let height = 90;
let x = 0;
let y = -45;
const inputlength = platenumber.length;
//Minumum Length
if (inputlength == 2) {
x = -200;
}
if (inputlength == 3) {
x = -150;
}
if (inputlength == 4) {
x = -130;
}
if (inputlength == 5) {
x = -105;
}
if (inputlength == 6) {
x = -65;
}
try {
console.log('stream is duplex, ', pipe instanceof stream.Duplex);
//Read the svg code into a buffer with a passed in plate number
const svg = new Buffer(
`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="${x} ${y} 500 40">
<defs>
<style type="text/css">
<![CDATA[
#font-face {
font-family: LicensePlate;
src: url('LicensePlate.ttf');
}
svg {
width: 100%;
height: 100%;
}
]]>
</style>
</defs>
<text x="0" y="0" font-family="LicensePlate" font-size="${fontsize}" letter-spacing="${letterspace}">
${platenumber.toUpperCase()}
</text>
</svg>`
);
const plateid = rand.generate(10);
//Create a write stream to a randomly generated file name
const write = new fs.createWriteStream(`plates/${plateid}.jpg`);
//Create the sharp pipeline
const pipeline = pipe
.overlayWith(svg, { gravity: sharp.gravity.center })//we center the svg image over the top of whatever image gets passed into the pipeline
.jpeg();//we convert to JPG because it is a compressed file format and will save space (we could also do webp if we really want to be slick about it)
//Create the read stream from the license plate template
const read = new fs.createReadStream('plate-2.png')
.pipe(pipeline)//pipe out sharp pipeline
.pipe(write);//add the write stream so that our sharp pipeline knows where to put the image
return plateid;
} catch (e) {
console.log(e);
return null;
}
}
SVG Image
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="90" viewBox="0 -50 500 40">
<defs>
<style type="text/css">
<![CDATA[
#font-face {
font-family: LicensePlate;
src: url('LicensePlate.ttf');
}
]]>
</style>
</defs>
<text x="0" y="0" font-family="LicensePlate" font-size="150" letter-spacing="10">
Pl#T3#
</text>
</svg>
Exact Font
Here is a link to the exact font I used
http://www.fontspace.com/dave-hansen/license-plate
I dealt with this problem by installing the font in my OS, when the file is being converted, the libraries can only access OS fonts.

How do I make fabric.js output svg including alpha color?

I need to output the result of a fabric canvas drawing to an SVG file.
I'm using fabric.js version 1.7.6 and when I have a path drawn to a canvas with an rgba fill like rgba(255,0,0,.15) the resulting SVG has a fill of rgb(0,0,0). Is there some setting I need to enable to make it output the alpha chanel?
In my sample code the purple circle converts to SVG properly, but the rectangle just shows up as black.
Sample HTML:
<html>
<head>
<script src="fabric.js"></script>
</head>
<body>
<div id="canvasHolder" style="border: 3px solid black;">
<canvas id="canvasElement" width="400" height="400" />
</div>
<div id="svgHolder" style="border: 3px solid blue;">
</div>
</body>
<script>
var canvas = new fabric.Canvas('canvasElement');
var rect = new fabric.Path('M,0,0,h,100,v,100,h,-100,z',{
top:100,
left:100,
stroke: 'green',
fill: 'rgba(255,0,0,.15)'
});
canvas.add(rect);
var circ = new fabric.Circle({
radius: 30,
top:30,
left:30,
stroke: 'blue',
fill: 'purple'
});
canvas.add(circ);
canvas.renderAll();
// Make an SVG object out of the fabric canvas
var SVG = canvas.toSVG();
document.getElementById('svgHolder').innerHTML = SVG;
</script>
</html>
Output SVG:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="400" height="400" viewBox="0 0 400 400" xml:space="preserve">
<desc>Created with Fabric.js 1.7.6</desc>
<defs>
</defs>
<path d="M 0 0 h 100 v 100 h -100 z" style="stroke: rgb(0,128,0); stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" transform="translate(150.5 150.5) translate(-50, -50) " stroke-linecap="round"></path>
</svg>
As I said in comment, this looks like a bug, that you should report on the project's issue tracker.
Colors are all converted to rgb() (rgba, hsl, hsla, hex, keywords) and thus don't support alpha channel...
For the time being, here is an heavy workaround :
toSVG accepts an reviver function, which will receive all the svg nodes markups. From there, you can reapply the correct styles, but not so easily.
The only parameter I could find allowing us to identify which object corresponds to the svg markup we get, is the id one.
So first, we will construct a dictionary, which will store our colors, by id.
Then, we will assign the id and colors to our fabric's objects.
Finally, in the reviver, we will parse our markup to convert it to an svg node, check its id atribute, and then change its style.fill and style.stroke properties, before returning the serialization of this modified node.
var canvas = new fabric.Canvas('canvasElement');
var colors_dict = {};
// an helper function to generate and store our colors objects
function getColorId(fill, stroke) {
var id = 'c_' + Math.random() * 10e16;
return colors_dict[id] = {
id: id,
fill: fill || 'black',
stroke: stroke || 'black' // weirdly fabric doesn't support 'none'
}
}
// first ask for the color object of the rectangle
var rect_color = getColorId('hsla(120, 50%, 50%, .5)', 'rgba(0,0,255, .25)');
var rect = new fabric.Path('M,0,0,h,100,v,100,h,-100,z', {
top: 60,
left: 60,
stroke: rect_color.stroke, // set the required stroke
fill: rect_color.fill, // fill
id: rect_color.id // and most importantly, the id
});
canvas.add(rect);
var circ_color = getColorId('rgba(200, 0,200, .7)');
var circ = new fabric.Circle({
radius: 30,
top: 30,
left: 30,
stroke: circ_color.stroke,
fill: circ_color.fill,
id: circ_color.id
});
canvas.add(circ);
canvas.renderAll();
var parser = new DOMParser();
var serializer = new XMLSerializer();
function reviveColors(svg){
// first we parse the markup we get, and extract the node we're interested in
var svg_doc = parser.parseFromString('<svg xmlns="http://www.w3.org/2000/svg">' + svg + '</svg>', 'image/svg+xml');
var svg_node = svg_doc.documentElement.firstElementChild;
var id = svg_node.getAttribute('id');
if (id && id in colors_dict) { // is this one of the colored nodes
var col = colors_dict[id]; // get back our color object
svg_node.style.fill = col.fill; // reapply the correct styles
svg_node.style.stroke = col.stroke;
// return the new markup
return serializer.serializeToString(svg_node).replace('xmlns="http://www.w3.org/2000/svg', '');
}
return svg;
}
// Make an SVG object out of the fabric canvas
var SVG = canvas.toSVG(null, reviveColors);
document.getElementById('svgHolder').innerHTML = SVG;
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.12/fabric.js"></script>
<div id="canvasHolder" style="border: 3px solid black;">
<!-- beware canvas tag can't be self-closing -->
<canvas id="canvasElement" width="400" height="200"></canvas>
</div>
<div id="svgHolder" style="border: 3px solid blue;">
The colors are converted to RGB because svg specs wants color in CSS2 format and so rgba is unsupported.
cit: https://www.w3.org/TR/2008/REC-CSS2-20080411/syndata.html#value-def-color
Fabric fulfill the transparency with the fill-opacity rull. The point is that fabric.Color color parser looks like is choking over the .15 notation for the alpha channel.
Please use 0.15 and it will work.
i agree that fabric.Color could be fixed for this.

Resources