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>
does anyone know how to convert SVG images to PNG (or JPG) on the server side? i've a node.js script that needs to do this and send the PNG to the caller. no rendering can be done on the client, it can only read PNGs or JPGs. i've looked at phantomjs, anychart-nodejs, canvas and others. they simply don't install or in the case of phantomjs, it installs and works in some cases and not in others. phantomjs works perfectly in node.js on a windows 10 computer, btw.
any ideas? thanks!
i figured it out. using the sharp js library did the trick. it's a bit difficult to get going, though. send me a message if you have trouble; i'll post my solution here.
My idea is to render a SVG image to canvas HTML element and then to convert the canvas one to a png image.
Here is my sample code demo.html as below, which I am using a pure JavaScript package canvg/canvg in browser.
<html>
<head>
<script type="text/javascript" src="https://unpkg.com/canvg#3.0.4/lib/umd.js"></script>
</head>
<body>
<svg id="svg_logo" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
height="300" width="300" viewBox="0 0 300 300">
<defs>
<g id="SVG" fill="#ffffff" transform="scale(2) translate(20,79)">
<path id="S" d="M 5.482,31.319 C2.163,28.001 0.109,23.419 0.109,18.358 C0.109,8.232 8.322,0.024 18.443,0.024 C28.569,0.024 36.782,8.232 36.782,18.358 L26.042,18.358 C26.042,14.164 22.638,10.765 18.443,10.765 C14.249,10.765 10.850,14.164 10.850,18.358 C10.850,20.453 11.701,22.351 13.070,23.721 L13.075,23.721 C14.450,25.101 15.595,25.500 18.443,25.952 L18.443,25.952 C23.509,26.479 28.091,28.006 31.409,31.324 L31.409,31.324 C34.728,34.643 36.782,39.225 36.782,44.286 C36.782,54.412 28.569,62.625 18.443,62.625 C8.322,62.625 0.109,54.412 0.109,44.286 L10.850,44.286 C10.850,48.480 14.249,51.884 18.443,51.884 C22.638,51.884 26.042,48.480 26.042,44.286 C26.042,42.191 25.191,40.298 23.821,38.923 L23.816,38.923 C22.441,37.548 20.468,37.074 18.443,36.697 L18.443,36.692 C13.533,35.939 8.800,34.638 5.482,31.319 L5.482,31.319 L5.482,31.319 Z"/>
<path id="V" d="M 73.452,0.024 L60.482,62.625 L49.742,62.625 L36.782,0.024 L47.522,0.024 L55.122,36.687 L62.712,0.024 L73.452,0.024 Z"/>
<path id="G" d="M 91.792,25.952 L110.126,25.952 L110.126,44.286 L110.131,44.286 C110.131,54.413 101.918,62.626 91.792,62.626 C81.665,62.626 73.458,54.413 73.458,44.286 L73.458,44.286 L73.458,18.359 L73.453,18.359 C73.453,8.233 81.665,0.025 91.792,0.025 C101.913,0.025 110.126,8.233 110.126,18.359 L99.385,18.359 C99.385,14.169 95.981,10.765 91.792,10.765 C87.597,10.765 84.198,14.169 84.198,18.359 L84.198,44.286 L84.198,44.286 C84.198,48.481 87.597,51.880 91.792,51.880 C95.981,51.880 99.380,48.481 99.385,44.291 L99.385,44.286 L99.385,36.698 L91.792,36.698 L91.792,25.952 L91.792,25.952 Z"/>
</g>
</defs>
<path id="base" fill="#000" d="M8.5,150 H291.5 V250 C291.5,273.5 273.5,291.5 250,291.5 H50 C26.5,291.5 8.5,273.5 8.5,250 Z"/>
<g stroke-width="38.0086" stroke="#000">
<g id="svgstar" transform="translate(150, 150)">
<path id="svgbar" fill="#ffb13b"
d="M-84.1487,-15.8513 a22.4171,22.4171 0 1 0 0,31.7026 h168.2974 a22.4171,22.4171 0 1 0 0,-31.7026 Z"/>
<use xlink:href="#svgbar" transform="rotate(45)"/>
<use xlink:href="#svgbar" transform="rotate(90)"/>
<use xlink:href="#svgbar" transform="rotate(135)"/>
</g>
</g>
<use xlink:href="#svgstar"/>
<use xlink:href="#base" opacity="0.85"/>
<use xlink:href="#SVG"/>
</svg>
<script type="text/javascript">
var svg_content=document.getElementById('svg_logo').outerHTML;
window.onload = () => {
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
v = canvg.Canvg.fromString(ctx, svg_content);//'<svg width="600" height="600"><text x="50" y="50">Hello World!</text></svg>');
// Start SVG rendering with animations and mouse handling.
v.start();
var MIME_TYPE = "image/png";
var imgURL = canvas.toDataURL(MIME_TYPE);
var dlLink = document.createElement('a');
dlLink.download = fileName;
dlLink.href = imgURL;
dlLink.dataset.downloadurl = [MIME_TYPE, dlLink.download, dlLink.href].join(':');
document.body.appendChild(dlLink);
dlLink.click();
document.body.removeChild(dlLink);
};
</script>
<canvas />
</body>
</html>
var fs = require('fs');
var svg2img = require('svg2img');
var svgString = [
'<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="236" height="120" ',
'viewBox="0 0 236 120">',
'<rect x="14" y="23" width="200" height="50" fill="#55FF55" stroke="black" stroke-width="1" />',
'</svg>'
].join('');
//convert from svg string
svg2img(svgString, function(error, buffer) {
//returns a Buffer
fs.writeFileSync('foo1.png', buffer);
});
You will find more info here:-
https://www.npmjs.com/package/svg2img
the problem is the following, have the svg file format:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px">
<symbol id="gear" viewBox="0 0 300 300" enable-background="new 0 0 300 300" xml:space="preserve">
<path fill="#inherit" stroke="#000000" d="..."/>
</symbol>
<symbol id="drill" viewBox="0 0 300 300" xml:space="preserve">
<path style="stroke:none; fill:#000000" d="..."/>
</symbol>
<svg>
do the following to make this file an icon and displayed on the map. The very icon collect:
SVG.create('img/iconsSvg.svg#gear','img/iconsSvg.svg#drill') it can be displayed in html
var SVG={
svgns:'http://www.w3.org/2000/svg',
xlink:'http://www.w3.org/1999/xlink',
create:function(){
let pathUse=arguments;
var svg=document.createElementNS(SVG.svgns, 'svg');
for (let i = 0; i < pathUse.length; i++) {
let use=document.createElementNS(SVG.svgns, 'use');
use.setAttributeNS(SVG.xlink, 'xlink:href', pathUse[i]);
svg.appendChild(use);
}
return svg;
}
}
in openlayers set the style of this:
function setStyleIcon(){
return new ol.style.Style({
image: new ol.style.Icon({
img:SVG.create('img/iconsSvg.svg#gear'),
imgSize:[30,30]
})
});
}
when it crashes the error: Argument 1 of CanvasRenderingContext2D.drawImage could not be converted to any of: HTMLImageElement, HTMLCanvasElement, HTMLVideoElement, ImageBitmap.
how to translate svg to canvas I do not know
but I can't put it on the map, thank you in advance for your help
Found a solution not very nice, the svg is inserted into a js file and work with it as with text, can someone come in handy:
Function set the style:
function setStyleSvg(type,color,scale){
var svg=svgIcon.getSvg([['location',color],[type,'rgb(100,100,100)',true]]);
var style=new ol.style.Style({
image: new ol.style.Icon( {
src:'data:image/svg+xml;utf8,'+svg,
scale: scale,
anchor: [0.5, 1],
anchorXUnits: 'fraction',
anchorYUnits: 'fraction',
})
});
return style;
};
collect svg (color need rgb/rgba format "for angularJS"):
app.factory('svgIcon',function(){
var size=[500,500];
var cssClass="unit";
var viewBox="0 0 2000 2000";
const closeTag='</svg>';
var insert='';
function icons(icon,color,label){
var out;
var offset=(label)?-250:0;
switch(icon){
case 'truck':out+=`<path viewBox="0 0 1024 538" transform="translate(448,`+(731+offset)+`)" fill="`+color+`" stroke="rgb(0,0,0)" stroke-width="10" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="1" d="...."/>
`;
break;
case 'drill':out+=`....`;
break;
case 'excavator':out+=`....`;
break;
}
return out;
}
this.setClass=(className)=>{
cssClass=className;
};
this.setViewBox=(set)=>{
viewBox=set;
};
this.setInsert=(set)=>{
insert=set;
};
this.getSvg=(arr)=>{//([[type,color,label],[type,color,label]....])
try {
var svg=`<svg width="500" height="500" viewBox="`+viewBox+`" `+insert+` class="`+cssClass+`" version="1.1" xmlns="http://www.w3.org/2000/svg">`;
for (var i = 0; i < arr.length; i++) {
svg+=icons(arr[i][0],arr[i][1],arr[i][2]);
}
svg+=closeTag;
return svg;
} catch (err) {
console.log('svg not create');
}
}
return this;});
I have a working example of exporting a selected symbol to an SVG string in Sketch 3. (Based on this code from Sketch's GitHub)
The issue is the output is distorted and I'm unable to see the next logical step in troubleshooting.
The Code
// Get all symbols
var symbols = context.document.documentData().allSymbols();
// For purpose of this example, only get the first one for testing.
var symbol = symbols[0];
// Set a temporary file to save to. Necessary for generating the SVG
var tempPath = '/tmp/com.symbolui.sketch-commands/';
var guid = [[NSProcessInfo processInfo] globallyUniqueString];
var path = tempPath + guid;
[[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:true attributes:nil error:nil]
var export_path = path;
var export_filename = export_path + '/' + 'test.svg';
// do export
[doc saveArtboardOrSlice:frame toFile:export_filename];
var file_url = [NSURL fileURLWithPath:export_filename];
var str = [[NSString alloc] initWithContentsOfURL:file_url];
At the final point in the code, str is a text value of the SVG generated from the symbol.
The Output
Input Symbol:
Generated preview of SVG:
Generated SVG text:
<?xml version="1.0" encoding="UTF-8"?>
<svg width="1200px" height="65px" viewBox="0 0 1200 65" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 42 (36781) - http://www.bohemiancoding.com/sketch -->
<title></title>
<desc>Created with Sketch.</desc>
<defs>
<rect id="path-1" x="0" y="0" width="49" height="20" rx="3"></rect>
<mask id="mask-2" maskContentUnits="userSpaceOnUse" maskUnits="objectBoundingBox" x="0" y="0" width="49" height="20" fill="white">
<use xlink:href="#path-1"></use>
</mask>
</defs>
<g id="Symbols:-Labels" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Buttons" transform="translate(-422.000000, 31.000000)"></g>
<g id="Label-/-Default" transform="translate(670.000000, 60.000000)">
<use id="background" stroke="#979797" mask="url(#mask-2)" stroke-width="2" fill="#777777" xlink:href="#path-1"></use>
<text id="-Default" font-family="HelveticaNeue-Bold, Helvetica Neue" font-size="10" font-weight="bold" fill="#FFFFFF">
<tspan x="7" y="14">Default</tspan>
</text>
</g>
</g>
Thoughts
As you can see the output is quite large in addition to being distorted. At the very least I believe I need to trim or rescale the symbol somehow. Where the distortion is coming through I have no idea.
Fixing the SVG markup itself isn't a solution - I'd like to see the solution in the Sketch plugin code. I'm finding documentation for internal Sketch code very difficult to work with and has been the big blocker for resolving this.
you can convert the MSSymbolInstance to normal group, then export it.
if (layer.symbolMaster().children().count() > 1) {
var tempSymbol = layer.duplicate(),
tempGroup = tempSymbol.detachByReplacingWithGroup();
tempGroup.resizeToFitChildrenWithOption(0);
layer.setIsVisible(false);
tempGroup.exportOptions().removeAllExportFormats();
var format = tempGroup.exportOptions().addExportFormat();
format.setFileFormat("SVG");
var slice = MSExportRequest.exportRequestsFromExportableLayer(tempGroup).firstObject();
var filePath = export_path + '/test.svg';
doc.saveArtboardOrSlice_toFile(slice, filePath);
}
I'm planning to have a geoJSON map inside my svg alongside other svg elements. I would like to be able to zoom (zoom+pan) in the map and keep the map in the same location with a bounding box. I can accomplish this by using a clipPath to keep the map within a rectangular area. The problem is that I also want to enable zooming and panning on my entire svg. If I do d3.select("svg").call(myzoom); this overrides any zoom I applied to my map. How can I apply zoom to both my entire svg and to my map? That is, I want to be able to zoom+pan on my map when my mouse is in the map's bounding box, and when the mouse is outside the bounding box, zoom+pan on the entire svg.
Here's example code: http://bl.ocks.org/nuernber/aeaac0e8edcf7ca93ade.
<svg id="svg" width="640" height="480" xmlns="http://www.w3.org/2000/svg" version="1.1">
<defs>
<clipPath id="rectClip">
<rect x="150" y="25" width="400" height="400" style="stroke: gray; fill: none;"/>
</clipPath>
</defs>
<g id="outer_group">
<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red" />
<g id="svg_map" style="clip-path: url(#rectClip);">
</g>
</g>
</svg><br/>
<script type="text/javascript">
var svg = d3.select("#svg_map");
var mapGroup = svg.append("g");
var projection = d3.geo.mercator();
var path = d3.geo.path().projection(projection);
var zoom = d3.behavior.zoom()
.translate(projection.translate())
.scale(projection.scale())
.on("zoom", zoomed);
mapGroup.call(zoom);
var pan = d3.behavior.zoom()
.on("zoom", panned);
d3.select("svg").call(pan);
mapGroup.attr("transform", "translate(200,0) scale(2,2)");
d3.json("ne_110m_admin_0_countries/ne_110m_admin_0_countries.geojson", function(collection) {
mapGroup.selectAll("path").data(collection.features)
.enter().append("path")
.attr("d", path)
.attr("id", function(d) { return d.properties.name.replace(/\s+/g, "")})
.style("fill", "gray").style("stroke", "white").style("stroke-width",1);
}
);
function panned() {
var x = d3.event.translate[0];
var y = d3.event.translate[1];
d3.select("#outer_group").attr("transform", "translate("+x+","+y+") scale(" + d3.event.scale + ")");
}
function zoomed() {
previousScale = d3.event.scale;
projection.translate(d3.event.translate).scale(d3.event.scale);
translationOffset = d3.event.translate;
mapGroup.selectAll("path").attr("d", path);
}
</script>
You need two zoom behaviours for that. The first one would be attached to the SVG and the second one to the map. In the zoom handlers you would have to take care of taking the appropriate action for each.