Rotate an svg path around its own centre using d3 - javascript

I have a SVG path (switch icon) made with Inkscape which I am trying to programatically rotate 90 degrees around its own centre. In my proper web page I have many of these icons all referenced by there ID so this needs to be a generic solution I can apply.
It looks like this question is identical but was never followed up by the OP and they where never able to provide any more code or a fiddle.
I have a fiddle here which shows everything for my example.
knob = d3.select("#switch1")
knob.attr('transform', 'rotate(0 0 0)')
Is the basic code I'm using for the rotation. I need to know how I can calculate the x & y values so any given icon can be made to point towards the on/off text as in the example. Or another way to get the same rotation effect using d3
If I do knob.attr('transform', 'rotate(90 0 0)') then the icon vanishes off the page - I thought 0 0 was to rotate round its relative centre?
If I manually do knob.attr('transform', 'rotate(90 15 15)') I can keep it on the page but in the wrong place.
The SVG path is made up of;
<path
id="switch1"
style="display:inline;fill:none;fill-opacity:1;stroke:#000000;stroke-width:1.12199998;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="m 35.741778,27.194664 -1.082299,0 0,-8.512001 1.082299,-9e-6 z m -5.563199,-4.274211 a 5.00005,5.000032 0 0 0 9.999999,2e-5 5.00005,5.000032 0 1 0 -9.999999,-2e-5 z"
inkscape:connector-curvature="0" />
The full SVG mark-up can be found on the fiddle.

You can get the position of the <path> with getBBox():
const centre = knob.node().getBBox();
Then, it's just a matter of calculating its centre:
knob.attr("transform", "rotate(" + angle + ", " +
(centre.x + centre.width / 2) + ", " + (centre.y + centre.height / 2) + ")");
Here angle is, obviously, the angle you want.
Here is a demo using your SVG (but smaller), click anywhere in the SVG to rotate the path:
let toggle = 0;
let svg = d3.select("svg")
const knob = d3.select("#switch1")
const centre = knob.node().getBBox();
svg.on("click", function() {
const angle = (toggle = 1 - toggle) ? 90 : 0;
knob.attr("transform", "rotate(" + angle + ", " + (centre.x + centre.width / 2) + ", " + (centre.y + centre.height / 2) + ")");
})
svg {
border: 1px solid gray;
background-color: lavender;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<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" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="200" height="100" viewBox="0 0 50 50" id="svg2" version="1.1" inkscape:version="0.91 r13725" sodipodi:docname="bar.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="11.2"
inkscape:cx="34.921875"
inkscape:cy="1047.7595"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:window-width="1920"
inkscape:window-height="1033"
inkscape:window-x="-4"
inkscape:window-y="-4"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid4157" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
id="switch1"
style="display:inline;fill:none;fill-opacity:1;stroke:#000000;stroke-width:1.12199998;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="m 35.741778,27.194664 -1.082299,0 0,-8.512001 1.082299,-9e-6 z m -5.563199,-4.274211 a 5.00005,5.000032 0 0 0 9.999999,2e-5 5.00005,5.000032 0 1 0 -9.999999,-2e-5 z"
inkscape:connector-curvature="0" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="25.714285"
y="13.612206"
id="text4135"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan4137"
x="25.714285"
y="13.612206"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;writing-mode:lr-tb;text-anchor:start">OFF</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="44.566826"
y="26.086744"
id="text4135-2"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan4137-2"
x="44.566826"
y="26.086744"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;writing-mode:lr-tb;text-anchor:start">ON</tspan></text>
</g>
</svg>
You can also add a transition:
let toggle = 0;
let svg = d3.select("svg")
const knob = d3.select("#switch1")
const centre = knob.node().getBBox();
const centreX = centre.x + centre.width / 2;
const centreY = centre.y + centre.height / 2;
svg.on("click", function() {
const angle = (toggle = 1 - toggle) ? 90 : 0;
knob.transition()
.ease(d3.easeLinear)
.attrTween("transform", function() {
return d3.interpolateString("rotate(" + (90 - angle) + ", " + centreX + ", " + centreY + ")", "rotate(" + angle + ", " + centreX + ", " + centreY + ")")
})
})
svg {
border: 1px solid gray;
background-color: lavender;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<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" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="200" height="100" viewBox="0 0 50 50" id="svg2" version="1.1" inkscape:version="0.91 r13725" sodipodi:docname="bar.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="11.2"
inkscape:cx="34.921875"
inkscape:cy="1047.7595"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:window-width="1920"
inkscape:window-height="1033"
inkscape:window-x="-4"
inkscape:window-y="-4"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid4157" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
id="switch1"
style="display:inline;fill:none;fill-opacity:1;stroke:#000000;stroke-width:1.12199998;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="m 35.741778,27.194664 -1.082299,0 0,-8.512001 1.082299,-9e-6 z m -5.563199,-4.274211 a 5.00005,5.000032 0 0 0 9.999999,2e-5 5.00005,5.000032 0 1 0 -9.999999,-2e-5 z"
inkscape:connector-curvature="0" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="25.714285"
y="13.612206"
id="text4135"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan4137"
x="25.714285"
y="13.612206"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;writing-mode:lr-tb;text-anchor:start">OFF</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="44.566826"
y="26.086744"
id="text4135-2"
sodipodi:linespacing="125%"><tspan
sodipodi:role="line"
id="tspan4137-2"
x="44.566826"
y="26.086744"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;writing-mode:lr-tb;text-anchor:start">ON</tspan></text>
</g>
</svg>

Although Gerardo's answer shows the obvious and most widely applicable solution there are other ways to do it. This answer has some experimental touch to it (Gerardo reported it to be broken on Safari) as it lays out two not off-the-shelf approaches. Think of it as exploring the possibilities and adding to the knowledge base rather than presenting the idiomatic way.
Instead of getting the bounding box and doing some, admittedly, trivial math you can use the transform-origin CSS property to control the origin for an element's transformations. Because Firefox seems to handle this differently you should also set transform-box:fill-box. Setting the origin for the rotation to 50% 50% will then work as you originally expected rotating the element around its center. Side note: rotating around the center is the default behavior for HTML whereas SVG by default rotates around the coordinates origin.
Here is an even more boiled down version of Gerardo's demo:
let toggle = 0;
const svg = d3.select("svg")
const knob = d3.select("#switch1")
svg.on("click", function() {
const angle = (toggle = 1 - toggle) ? 90 : 0;
knob.attr("transform", "rotate(" + angle + ")");
})
path {
fill: none;
stroke: #000000;
stroke-width: 1.12199998;
transform-box: fill-box;
transform-origin: 50% 50%
}
text {
font-size: 10px;
font-family: Sans;
fill: #000000;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="200" height="100" viewBox="0 0 50 50">
<g>
<path id="switch1"
d="m 35,27 -1,0 0,-9 1,0 z m -5.7,-4 a 5,5 0 0 0 10,0 5,5 0 1 0 -10,0 z" />
<text x="26" y="14">OFF</text>
<text x="45" y="26">ON</text>
</g>
</svg>
You can even push the envelope using the hidden-checkbox-label trick to do this with no JavaScript at all!
path {
fill: none;
stroke: #000000;
stroke-width: 1.12199998;
transform-box: fill-box;
transform-origin: 50% 50%
}
text {
font-size: 10px;
font-family: Sans;
fill: #000000;
}
#dummy {
display: none;
}
#dummy:checked+label path {
transform: rotate(90deg);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<input type="checkbox" id="dummy">
<label for="dummy">
<svg width="200" height="100" viewBox="0 0 50 50">
<g>
<path id="switch1"
d="m 35,27 -1,0 0,-9 1,0 z m -5.7,-4 a 5,5 0 0 0 10,0 5,5 0 1 0 -10,0 z" />
<text x="26" y="14">OFF</text>
<text x="45" y="26">ON</text>
</g>
</svg>
</label>

Related

SVG progress bar with image

I am trying to create a progress bar (arc) with SVG. I currently have the progress bar working, it is moving the desired amount using a value stored in a data attribute, and looks pretty good. although i am trying to get an image to move around the arc with the bar. The image should start at 0 with the bar and move around to the completion point, say 50% which will be at the top.
<div class="w-100 case-progress-bar input p-2" style="position: relative;" data-percentage="80">
<svg class='progress_bar' viewBox="0 0 100 50" >
<g fill-opacity="0" stroke-width="4">
<path d="M5 50a45 45 0 1 1 90 0" stroke="#EBEDF8"></path>
<path class="progress" d="M5 50a45 45 0 1 1 90 0" stroke="#f00" stroke-dasharray="142" stroke-dashoffset="142"></path>
</g>
<circle fill="url(#image)" id='case_progress__prog_fill' class="case_progress__prog" cx="0" cy="0" r="8" fill="#999" stroke="#fff" stroke-width="1" />
<defs>
<pattern id="image" x="0%" y="0%" height="100%" width="100%" viewBox="0 0 60 60">
<image x="0%" y="0%" width="60" height="60" xlink:href="https://via.placeholder.com/150x150"></image>
</pattern>
</defs>
</svg>
</div>
(function(){
var $wrapper = $('.case-progress-bar'),
$bar = $wrapper.find('.progress_bar'),
$progress = $bar.find('.progress'),
$percentage = $wrapper.data('percentage');
$progress.css({
'stroke-dashoffset': 'calc(142 - (0 * 142 / 100))',
'transition': 'all 1s'
});
var to = setTimeout(function(){
$progress.css('stroke-dashoffset', 'calc(142 - (' + $percentage + ' * 142 / 100))');
clearTimeout(to);
}, 500);
})();
this is what I currently have
this is what i'm trying to achieve
To solve, you need to combine two animations:
Painting half of the arc from the beginning to the middle (top)
Animation of movement of a circle with an image inside
Set the same time for both animations
<div class="w-100 case-progress-bar input p-2" style="position: relative;" data-percentage="80">
<svg class='progress_bar' viewBox="0 0 100 50" >
<g fill-opacity="0" stroke-width="4">
<path id="pfad" d="M5 50C5 44.1 6.1 38.5 8.2 33.4 10.8 26.8 14.9 20.9 20.2 16.3 28.1 9.3 38.6 5 50 5" stroke="#EBEDF8"></path>
<path d="M5 50a45 45 0 1 1 90 0" stroke="#EBEDF8"></path>
<!-- Animation to fill half an arc -->
<path class="progress" d="M5 50a45 45 0 1 1 90 0" stroke="#f00" stroke-dasharray="142" stroke-dashoffset="142">
<animate attributeName="stroke-dashoffset" from="142" to="71" dur="4s" fill="freeze" />
</path>
</g>
<defs>
<pattern id="image" x="0%" y="0%" height="100%" width="100%" viewBox="0 0 60 60">
<image x="0%" y="0%" width="60" height="60" xlink:href="https://via.placeholder.com/150x150"></image>
</pattern>
</defs>
<circle fill="url(#image)" id='case_progress__prog_fill' class="case_progress__prog" cx="0" cy="0" r="8" fill="#999" stroke="#fff" stroke-width="1" >
<!-- Animation of movement of a circle with an image -->
<animateMotion begin="0s" dur="4s" fill="freeze">
<mpath xlink:href="#pfad" />
</animateMotion>
</circle>
</svg>
</div>
This is a case where it has advantages not to use thestroke-dasharray trick.
SVG can draw a marker at the end of a path. That marker can be any sort of grafic, and its syntax is just like that of a <symbol>. The position of the marker is defined by the path d attribute, and not influenced by a dashed stroke.
The general strategy is to compute the endpoint of the path
endpoint_x = center_x - cos(percentage / 100 * 180°) * radius
endpoint_y = center_y - sin(percentage / 100 * 180°) * radius
It is possible to do so relatively seamlessly because you decided to use only a half-circle to represent 100%. I have changed the way the path data are written to make that possible:
`M5 50 A 45 45 0 ${large} 1 ${x} ${y}`
A means: draw an arc and use absolute coordinates.
45 45 0 use a rx of 45, a ry of 45, do not rotate the axis of the arc.
${large} is the important bit. It discerns arcs of less than 180° from those that have more than 180°. As soon as that value would be crossed, the flag must change from 0 to 1. But since you are never expecting values above 180°, you would not need it.
1 means looking in the direction of the path, the arc should be drawn to the left side.
${x} ${y} are the final coordinates expressed in absolute, not relative coordinates.
The <marker> element has a number of attributes that must be considered:
orient="0" means the marker will not change its direction with the direction of the path at its end. orient="auto" would make it turn around , as you would like to see with an arrow for example.
markerUnits="userSpaceOnUse" means the numbers in the other attributes are in px units of the coordinate system of the path. Default would be markerUnits="strokeWidth", which would mean a size relative to the width of the stroke.
viewBox="-8 -8 16 16" is choosen because the circle used is centered around the coordinate system origin.
markerWidth="16" markerHeight="16" is saying how large the marker should be drawn.
refX="0" refY="-10" describes how the marker should be positioned: Take a point in the coordinate system of the marker itself (slightly above its topmost point and in the middle), and align it exactly with the end of the path.
Finally, note the marker-end="url(#image)" presentation attribute for the path. This is what sets the marker, and defines that it will be at the end of the path.
(function(){
var $wrapper = $('.case-progress-bar'),
$bar = $wrapper.find('.progress_bar'),
$progress = $bar.find('.progress'),
$percentage = $wrapper.data('percentage');
function computePath (percentage) {
var x = 50 - Math.cos(percentage / 100 * Math.PI) * 45,
y = 50 - Math.sin(percentage / 100 * Math.PI) * 45,
large = percentage > 100 ? 1 : 0;
return `M5 50 A 45 45 0 ${large} 1 ${x} ${y}`;
}
var to = setTimeout(function(){
$progress.attr('d', computePath($percentage));
clearTimeout(to);
}, 500);
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="w-100 case-progress-bar input p-2" style="position: relative;" data-percentage="80">
<svg class='progress_bar' viewBox="0 0 100 70" >
<g fill-opacity="0" stroke-width="4">
<path d="M5 50a45 45 0 1 1 90 0" stroke="#EBEDF8"></path>
<path class="progress" d="M5 50 A 45 45 0 0 1 95 50`" stroke="#f00"
marker-end="url(#image)"></path>
</g>
<marker id="image" orient="0" markerUnits="userSpaceOnUse"
viewBox="-8 -8 16 16"
markerWidth="16" markerHeight="16"
refX="0" refY="-10">
<circle r="8" fill="#aaf" />
<path d="M-6 0h12" stroke="#000" stroke-width="2" />
</marker>
</svg>
</div>
Just use a little JavaScript since you can't do trigonometry in CSS easily yet.
Codepen: https://codepen.io/mullany/pen/02cd0773588b3d975c8443ab6a87f670
(function(){
var $wrapper = $('.case-progress-bar'),
$bar = $wrapper.find('.progress_bar'),
$progress = $bar.find('.progress'),
$percentage = $wrapper.data('percentage');
var $circpos = $('.case_progress__prog');
$progress.css({
'stroke-dashoffset': 'calc(142 - (0 * 142 / 100))',
'transition': 'all 1s'
});
var to = setTimeout(function(){
$progress.css('stroke-dashoffset', 'calc(142 - (' + $percentage + ' * 142 / 100))');
var angleInRadians = 180*(1-$percentage/100) * 0.01745329251;
var xPos = 5 + 45 * (1 + Math.cos(angleInRadians) );
var yPos = 5 + 45 * (1 - Math.sin(angleInRadians) );
$circpos.css('cx', xPos);
$circpos.css('cy', yPos);
clearTimeout(to);
}, 500);
})();
I had a very similarly shaped loading SVG. You cen see it in action here. The green one at the bottom of the main banner.
What I used was the ProgressBar.js library. It made it very easy. I just focused on adjusting my SVG in terms of shape, because of the design of my site and then just used:
<svg id="progress-bar" xmlns="http://www.w3.org/2000/svg" width="650" height="526" viewBox="0 0 650 526">
<path opacity=".55" id="progress-bar-base" fill="none" stroke="#fefefe" stroke-width="20" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M84.591 512.622S20 441.232 20 324.941 79.903 131.327 137.4 84.476 269.116 20 324.94 20s124.217 15.743 184.57 62.183 89.159 101.578 103.475 142.419c14.316 40.84 25.203 105.21 8.788 170.477-16.415 65.268-43.628 101.409-56.482 117.543"></path>
<linearGradient id="grade" gradientUnits="userSpaceOnUse" x1="592.08" y1="157.665" x2="46.149" y2="472.859">
<stop offset="0" stop-color="#2cab9a"></stop>
<stop offset="1" stop-color="#8ed1c3"></stop>
</linearGradient>
<path fill-opacity="0" id="progress-bar-indicator" stroke="url(#grade)" stroke-width="24" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M84.591 512.622S20 441.232 20 324.941 79.903 131.327 137.4 84.476 269.116 20 324.94 20s124.217 15.743 184.57 62.183 89.159 101.578 103.475 142.419c14.316 40.84 25.203 105.21 8.788 170.477-16.415 65.268-43.628 101.409-56.482 117.543" style="stroke-dasharray: 1362.73, 1362.73; stroke-dashoffset: 1140.45;"></path>
</svg>
var progressBar = new ProgressBar.Path('#progress-bar-indicator', {
easing: 'easeInOut',
duration: 2500
});
progressBar.set(0); // Initiate it at zero.
progressBar.animate(0.33); // this is your percentage of load
It's a very basic example, but I think you could adjust it to your needs.
As for the image, perhaps you could add it to your own SVG at the tip of the loading curve and it should move with it I think.

SVG rotate attribute of the <animateMotion> element set to auto not working correctly

I have a question to svg animation. Please find my code here. Using javascript I am adding animateMotion element to red arrow symbol. I would like to achieve the following thing
Arrow should move on the path designated by rectangle and rotate to align with the slope of the path (so rotate 90 deg)
I can partially meet this requirement. Moving arrow along the given path is quite simple. JS script is setting path attribute for animateMotion element. I wanted to use rotate attribute to fit rotate changes to the path.
Please have a look that when you uncomment this line
animateMotion.setAttribute("rotate", "auto");
in JS code something strange happens. Instead rotating 90 deg after reaching each corner arrow unexpected disappear.
Do you know why?
Thank you very much in advance.
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
function getArrowSymbolXcoordinate(pathAttribute) {
var commaIdx = pathAttribute.indexOf(',');
return pathAttribute.substring(1,commaIdx);
}
function getArrowSymbolYcoordinate(pathAttribute) {
var commaIdx = pathAttribute.indexOf(',');
var lineCommandIdx = pathAttribute.indexOf('l');
console.log(pathAttribute.substring(commaIdx, lineCommandIdx));
return pathAttribute.substring(commaIdx+1, lineCommandIdx);
}
function getTopPathAttribute(element, xCoordinate) {
var toTopRightCorrner = Math.abs(topRightCorrner.x - xCoordinate);
var path = 'm0,0 ' + 'h' + toTopRightCorrner + ' v' + height + ' h-' + width + ' v-' + height + ' z';
return path;
}
let topLeftCorrner = new Point(200,100);
let topRightCorrner = new Point(350,100);
let bottomLeftCorrner = new Point(200,150);
let bottomRightCorrner = new Point(350,150);
let topArrowSymbols = Array.from(document.getElementsByClassName('top'));
let width = Math.abs(topLeftCorrner.x - topRightCorrner.x);
let height = Math.abs(topLeftCorrner.y - bottomLeftCorrner.y);
topArrowSymbols.forEach( function(element) {
var xCoordinate = getArrowSymbolXcoordinate(element.getAttribute('d'));
var animateMotion = document.createElementNS('http://www.w3.org/2000/svg', "animateMotion");
var pathAttribute = getTopPathAttribute(element, xCoordinate);
animateMotion.setAttribute("dur", "7s");
animateMotion.setAttribute("path", pathAttribute);
//animateMotion.setAttribute("rotate", 'auto');
element.appendChild(animateMotion);
});
body {
height: 100vh;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<script src="main_home.js" defer="defer"></script>
</head>
<body>
<figure >
<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" viewbox="120 80 300 200">
<g>
<rect fill="none" height="50" stroke="black" stroke-width="2" width="150" x="200" y="100"/>
<path class="arrow_symbol top" d="m215.21876,99.72372l-2.49741,-2.5l1.64474,0l2.49741,2.5l-2.49741,2.5l-1.64474,0l2.49741,-2.5z" fill="#ffffff" stroke="#ff0000" stroke-width="2"/>
</g>
</svg>
</figure>
</body>
</html>
Your problem is that your arrow has a large offset from the origin of the SVG. So when it hits the end of the first curve and rotates around the corner, it is disappearing off screen.
Here is a simplified example that hopefully shows what is happening.
<svg viewbox="-100 -100 400 400" width="400">
<!-- axes to show the origin -->
<line x1="0" y1="-100" x2="0" y2="300" stroke="#cce" stroke-width="1"/>
<line x1="-100" y1="0" x2="300" y2="0" stroke="#cce" stroke-width="1"/>
<!-- the path that the arrow is actually following (ie the one in the animateMotion) -->
<rect width="80" height="80" fill="none" stroke="#888" stroke-width="2"/>
<!-- dashed line showing the offset from the animateMotionPath to the arrow -->
<line x1="0" y1="0" x2="100" y2="100" fill="none" stroke="#ff0000" stroke-width="2" stroke-dasharray="10 10">
<animateMotion dur="7s" rotate="auto"
path="M 0,0 L 80,0 L 80,80 L 0,80 Z"/>
</line>
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!-- the pon-screen path that the arrow appears to be following -->
<rect fill="none" x="100" y="100" width="80" height="80" stroke="black" stroke-width="2" />
<!-- the arrow -->
<path class="arrow_symbol top" d="M 110,100 L 100,110 L 100,90 Z" fill="#ffffff" stroke="#ff0000" stroke-width="2">
<animateMotion dur="7s" rotate="auto"
path="M 0,0 L 80,0 L 80,80 L 0,80 Z"/>
</path>
</svg>
To fix your SVG, you have to make sure that your arrow rotates around its centre. Not the origin.
let topArrowSymbols = Array.from(document.getElementsByClassName('top'));
topArrowSymbols.forEach( function(element) {
var animateMotion = document.createElementNS('http://www.w3.org/2000/svg', "animateMotion");
animateMotion.setAttribute("dur", "7s");
animateMotion.setAttribute("path", 'm 220,100 h 130 v 50 h -150 v -50 z');
animateMotion.setAttribute("rotate", 'auto');
animateMotion.setAttribute("fill", "freeze");
element.appendChild(animateMotion);
});
body {
height: 100vh;
}
svg {
overflow: visible;
position: absolute;
top: 400px;
left: 400px;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<script src="main_home.js" defer="defer"></script>
</head>
<body>
<figure >
<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" viewbox="120 80 300 200">
<g>
<rect id="my-path" fill="none" height="50" stroke="black" stroke-width="2" width="150" x="200" y="100"/>
<path class="arrow_symbol top" d="m0.21876,0.72372l-2.49741,-2.5l1.64474,0l2.49741,2.5l-2.49741,2.5l-1.64474,0l2.49741,-2.5z" fill="#ffffff" stroke="#ff0000" stroke-width="2"/>
</g>
</svg>
</figure>
</body>
</html>

SVG - Greensock Timeline for animation

What is the GSAP counterpart of the additive and accumulate animation attributes in SVG? I am animating using click events so I want to continue animation from the last state of the object. Or is there a way to access those attributes.
I am just starting out, so any links to guides or tutorials is appreciated.
var svgNS = "http://www.w3.org/2000/svg";
function anim(evt) {
if (window.svgDocument == null)
svgDoc = evt.target.ownerDocument;
rot('shape', 120);
}
function rot(target_id, angle) {
var my_element = svgDoc.getElementById(target_id);
var a = svgDoc.createElementNS(svgNS, "animateTransform");
var bb = my_element.getBBox();
var cx = bb.x + bb.width / 2;
var cy = bb.y + bb.height / 2;
a.setAttributeNS(null, "attributeName", "transform");
a.setAttributeNS(null, "attributeType", "XML");
a.setAttributeNS(null, "type", "rotate");
a.setAttributeNS(null, "dur", "1s");
a.setAttributeNS(null, "repeatCount", "1");
a.setAttributeNS(null, "fill", "freeze");
a.setAttributeNS(null, "additive", "sum");
a.setAttributeNS(null, "accumulate", "sum");
a.setAttributeNS(null, "from", "0 " + cx + " " + cy);
a.setAttributeNS(null, "to", angle + " " + cx + " " + cy);
my_element.appendChild(a);
a.beginElement();
}
<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" width="500px" height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
<g id="shape">
<circle fill="#FFA557" stroke="#000000" stroke-miterlimit="10" cx="254.186" cy="247.288" r="107.203" />
<polygon fill="#8CFFD5" stroke="#000000" stroke-miterlimit="10" points="239.634,218.5 225.083,193.5 239.634,168.5
268.736,168.5 283.288,193.5 268.736,218.5 " />
<polygon fill="#8CFFD5" stroke="#000000" stroke-miterlimit="10" points="239.634,268.5 225.083,243.5 239.634,218.5
268.736,218.5 283.288,243.5 268.736,268.5 " />
<polygon fill="#8CFFD5" stroke="#000000" stroke-miterlimit="10" points="195.634,243.5 181.083,218.5 195.634,193.5
224.737,193.5 239.288,218.5 224.737,243.5 " />
<polygon fill="#8CFFD5" stroke="#000000" stroke-miterlimit="10" points="282.635,243.5 268.083,218.5 282.635,193.5
311.736,193.5 326.288,218.5 311.736,243.5 " />
</g>
<g onclick="anim(evt)">
<rect x="123.5" y="391.5" fill="#6E005D" stroke="#000000" stroke-miterlimit="10" width="268" height="77" />
<text transform="matrix(1 0 0 1 184.0811 439.5081)" fill="#FFFFFF" font-family="'ComicSansMS'" font-size="36">Click Me</text>
</g>
</svg>
I am not sure if I understand your question correctly but is this the kind of effect you are trying to reproduce using TweenMax? Let me know.
Update #1:
Using TimelineMax:
Keep adding new TweenMax tweens into an existing timeline
instance by using the add() method of TimelineMax.
Check to see if timeline is currently in a playing state, if not, play() it.
jsFiddle #1.
Update #2:
Increment the speed of timeline using
timeScale() on a per-click basis.
Set a cap on this timeScale() property.
Add an onComplete callback to the timeline instance to clear() the timeline.
jsFiddle #2.

Can't figure out this svg animation

I have been trying to get the circles in my stage to move around the ellipses, I even copied code from someone that had a very similar setup, but no dice. Heres the codepen and the markup below.
http://codepen.io/alcoven/pen/XJXMNW
<div id="wrapper">
<div class="container">
<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 30 30" enable-background="new 0 0 30 30" xml:space="preserve">
<!--<g>
<circle fill="#F26451" cx="17.6" cy="3.3" r="1.4"/>
</g>
<g>
<circle fill="#F26451" cx="6.4" cy="23.1" r="1.4"/>
</g>-->
<line class="line" stroke-width="1.3" stroke-miterlimit="10" x1="3.7" y1="26.1" x2="26.2" y2="3.6"/>
<path class="vertical" d="M9.9,14.9a5,14 0 1,0 10,0a5,14 0 1,0 -10,0"/>
<ellipse class="_x34_5deg" transform="matrix(0.5037 0.8639 -0.8639 0.5037 20.2917 -5.4991)" stroke-width="1.3" stroke-miterlimit="10" cx="14.9" cy="14.9" rx="5" ry="14"/>
<ellipse class="_x2D_45deg" transform="matrix(-0.5037 0.8639 -0.8639 -0.5037 35.3327 9.5202)" stroke-width="1.3" stroke-miterlimit="10" cx="14.9" cy="14.9" rx="5" ry="14"/>
<path class="circlet" d="M16.4,3.2a1.3,1.3 0 1,0 2.6,0a1.3,1.3 0 1,0 -2.6,0"/>
<path class="circleb" d="M5,23.2a1.4,1.4 0 1,0 2.8,0a1.4,1.4 0 1,0 -2.8,0"/>
</svg>
</div>
</div>
CSS
#wrapper {
top:0;
left:0;
position:absolute;
width:100%;
height:100%;
background:#F26451;
}
.container {
width:100%;
position:fixed;
top:30%;
bottom:70%;
}
svg {
width:100px;
height:auto;
margin:10% auto;
display:block;
}
line, .vertical, ellipse._x34_5deg, ellipse._x2D_45deg {
stroke:#fff;
fill:none;
stroke-width:1.3;
stroke-miterlimit:10;
}
.circlet, .circleb {
stroke:#fff;
fill:#F26451;
stroke-width:1.3;
stroke-miterlimit:10;
}
.line {
display:none;
}
JS
(function($){
$('[class^="circle-"]').animate({opacity:1},500);
// get the animation path node
var $path = $('.vertical'), path = $path[0];
var $path2 = $('.vertical'), path2 = $path2[0];
// get the animation object node
var $obj = $('.circlet');
var $obj2 = $('.cirlceb');
// get path's length
var pathLength = path.getTotalLength();
var pathLength2 = path2.getTotalLength();
// create new tween by initializing TWEEN.Tween object from 0
var tween = new TWEEN.Tween({ length: 0 })
// to path length
// in 2000 milliseconds
.to({ length: pathLength }, 1500)
// on update callback fires on every tick
.onUpdate(function(){
var point = path.getPointAtLength(this.length);
$obj.css({
'transform': 'translate('+ point.x + 'px,'+ point.y +'px)'
});
}).repeat(999999999).start();
var tween2 = new TWEEN.Tween({ length: 0 })
// to path length
// in 2000 milliseconds
.to({ length: pathLength2 }, 1500)
// on update callback fires on every tick
.onUpdate(function(){
var point2 = path2.getPointAtLength(this.length);
$obj2.css({
'transform': 'translate('+ point2.x + 'px,'+ point2.y +'px)'
});
}).repeat(999999999).start();
// animate loop
animate = function(){
requestAnimationFrame(animate)
TWEEN.update()
}
//start the animation loop
animate()
}(jQuery));
Can't quite figure out why this isn't working, not sure if it's my js or the way my svg is setup please help :]
Here's the js I copied http://codepen.io/joshy/pen/cojbD
Define an animation path, which could be a circle , ellipse or Bezier curve.
Enclose the path definition between animateMotion tags ,one pair within each moving object definition, as follows:
<?xml version="1.0"?>
<svg width="120" height="120" viewBox="0 0 120 120"
xmlns="http://www.w3.org/2000/svg" version="1.1"
xmlns:xlink="http://www.w3.org/1999/xlink" >
<!-- Draw the outline of the motion path in grey, along
with 2 small circles at key points -->
<path d="M10,110 A120,120 -45 0,1 110 10 A120,120 -45 0,1 10,110"
stroke="lightgrey" stroke-width="2"
fill="none" id="theMotionPath"/>
<circle cx="10" cy="110" r="3" fill="lightgrey" />
<circle cx="110" cy="10" r="3" fill="lightgrey" />
<!-- Here is a red circle which will be moved along the motion path. -->
<circle cx="" cy="" r="5" fill="red">
<!-- Define the motion path animation -->
<animateMotion dur="6s" repeatCount="indefinite">
<mpath xlink:href="#theMotionPath"/>
</animateMotion>
</circle>
No javascript or jQuery required !

SVG animate dotted line around cicle

I'm trying to draw a line animation of a circle. I need this to work on mobile so opted for SVG. I have a perfect working example, but it is very inefficient and makes the other animations on the page stutter.
This is what I currently have and what I'm trying to achieve: http://jsfiddle.net/sj76ysqs/
<svg class="bean-halo" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"
viewBox="0 0 500 500"
preserveAspectRatio="xMidYMid"
style="width:100%; height:100%; position:absolute; top:0; left:0;">
<path d="M200,200 " id="bean-halo" fill="none" stroke="#FF0000" stroke-linecap="round" stroke-width="2.5" stroke-dasharray="0.1,10" />
</svg>
(function() {
var i = 0,
circle = document.getElementById('bean-halo'),
angle = 0,
radius = 167,
interval = 20,
d, radians, x, y, e;
window.timer = window.setInterval(function() {
angle -= 5;
angle %= 360;
radians = (angle / 180) * Math.PI;
x = 250 + Math.cos(radians) * radius;
y = 250 + Math.sin(radians) * radius;
e = circle.getAttribute('d');
d = e + (i === 0 ? ' M ' : ' L ') + x + ' ' + y;
if (angle === -5 && i !== 0) {
window.clearInterval(window.timer);
this.beanHaloisDrawn = 1;
}
circle.setAttribute('d', d);
i++;
}.bind(this), interval);
})()
I'd like to use the following technique or something similar, but don't know enough about SVGs to do this: http://css-tricks.com/svg-line-animation-works/
I've also though about having a static dotted line that is masked by an animated line that reveals it, but again, I don't know how to do this.
Any help would be appreciated.
UPDATE: The solution has to work on an element with a image as background.
How about just manipulating stroke-dasharray of a circle
<svg class="bean-halo" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"
viewBox="0 0 500 500"
preserveAspectRatio="xMidYMid"
style="width:100%; height:100%; position:absolute; top:0; left:0;">
<circle cx="200" cy="200" r="167" id="bean-halo" fill="none" stroke="#FF0000" stroke-linecap="round" stroke-width="2.5" stroke-dasharray="0.1,20000" />
</svg>
together with something like this...
(function() {
var angle = 0;
var circle = document.getElementById('bean-halo');
var dash="0.1,10 ";
var interval = 20;
window.timer = window.setInterval(function() {
circle.setAttribute("stroke-dasharray", dash + " 0, 20000");
dash = dash + "0.1,10 ";
if (angle >= 360) window.clearInterval(window.timer);
angle += 10.1/360;
}.bind(this), interval);
})()
If you don't want to use javascript you'll have to do the interpolation yourself by creating a huge animate with all the intermediate steps. I've done 4 below but you get the gist. You could create the attribute using javascript and a loop though.
<svg class="bean-halo" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"
viewBox="0 0 500 500"
preserveAspectRatio="xMidYMid"
style="width:100%; height:100%; position:absolute; top:0; left:0;">
<circle cx="200" cy="200" r="167" stroke-width="1" stroke="red" fill="white">
<animate attributeName="stroke-dasharray"
values="1,10,0,20000;1,10,1,10,0,20000;1,10,1,10,1,10,0,20000;1,10,1,10,1,10,1,10,0,20000"
dur="4s" repeatCount="1" fill="freeze" />
</circle>
</svg>
Animation trick using two circles, no coding required:-
<svg width="400" height="400" viewBox="0 0 400 400" >
<circle cx="200" cy="200" r="167" stroke-dasharray="1,6" stroke-width="1" stroke="red" fill="white" />
<circle cx="200" cy="200" r="167" stroke-dasharray="1200,1200 " stroke-width="3" stroke-dashoffset="0" stroke="white" fill="none">
<animate attributeType="XML" attributeName="stroke-dashoffset" from="0" to="1200" dur="4s" repeatCount="1" fill="freeze" />
</circle>
</svg>

Categories