1

I have a bunch of objects that are using the same svg path which is basically an arrow. I want to position the objects on another svg path of a circle so the arrows look like they are "radiating" outward.

I read a few articles that say using <object> for svg gives you more control but I cant find how to position <object>'s onto another svg <object> path(circle).

rather than just manually rotating each <object> I would like to position them all along a path so animating them in the future would be easier. I want them to move out a bit from the center on rollover and I imagine the animation for that would be easier with a path rather than manually animating each arrow.

I can animate along path like here http://demo.hongkiat.com/scalable-vector-graphics-animation/ and by changing the path on "following Path" to a circle but I dont want to animate, just position....

UPDATE: The only thing I seem to be able to find online to get an element onto a path is animationTransform or to animate something along a path but I want to only position elements along another element(path) like this: svg circle The extra arrow sticking out is how I plan on animating it in the future on rollover...but for now I just am curious how to position the arrows. Also I don't have much but its a start and I got working finally. Any help appreciated!

http://jsfiddle.net/74JRf/1/

I was just thinking, would it be better to rotate the arrows into a circle with css3, or svg?

mike
  • 749
  • 2
  • 13
  • 24
  • 1
    By `` do you mean a `` tag? Or are you embedding separate SVG files within multiple different HTML `` tags? Positioning separate SVG files on a page on top of each other is much more complicated than positioning multiple graphics within the same SVG... Some sample code would be useful. – AmeliaBR Mar 06 '14 at 04:04
  • 1
    http://jsfiddle.net/74JRf/1/ I updated the description and got the working now in the fiddle but I can find no resources online for positioning along path, only animating. – mike Mar 06 '14 at 10:35
  • 1
    Positioning an element at a point on the path is easy with Javascript -- you can use the path's `getPointAtLength` method, possibly in combination with the `getTotalLength` method ([see examples in this question on animation](http://stackoverflow.com/a/21228701/3128209)). However, that doesn't auto-rotate the element to match the tangent angle of the path like SVG animation does. And there isn't any easy way to figure out the angle -- you'd need to query multiple points and calculate a tangent with trigonometry. For your example, it's probably easiest to position the arrows directly. – AmeliaBR Mar 06 '14 at 16:19
  • 1
    However, for browsers that support SVG animation, you could use the animation syntax for positioning the element -- but just set the start and end positions to be the same and zero duration. But be aware that not all browsers support SVG animation, in IE this would look very broken! – AmeliaBR Mar 06 '14 at 16:23
  • Hey thanks for clarifying guys...looks like it would be better to consider doing this in css3. I till want to use a SVG object as its file size is smaller than a jpg and its scalable, but I have one last question. Is it a better idea to make a bunch of arrows with the tag and reference the same .svg file, or should I do tag and use the tag? Which would give me more freedom and which is less file size? Thanks! – mike Mar 06 '14 at 20:23

2 Answers2

1

Your application demonstrates the neat dynamic aspects of SVG. I hope my answer is not too verbose :)

Create your 'arrows' so their center/bottom point is at(0,0) - This is used when placing them around a circle, and for the 'hilite/extend' feature for onmouseover.

I would recommend using a polygon rather than a path for each black and gray arrow. Place then in a <defs> for creating the needed use elements. e.g

<defs>
<polygon id="myBlackArrow" points="10,0 -10,0 -10,-80 -15,-80 0,-100 15,-80 10,-80"  fill="black" />
<polygon id="myGrayArrow" points="10,0 -10,0 -10,-80 -15,-80 0,-100 15,-80 10,-80"  fill="silver" />
</defs>

Then, with a bit of Javascript you can build the arrows around a center point:

var NS="http://www.w3.org/2000/svg"
var xref="http://www.w3.org/1999/xlink"
var centerX=200
var centerY=200
//---every 40 degrees around a center point--
function placeArrowsAroundCircle()
{
    for(var k=0;k<9;k++)
    {
        var rotateAngle=k*40
        var grayArrow=document.createElementNS(NS,"use")
        grayArrow.setAttributeNS(xref,"href", "#myGrayArrow")
        grayArrow.setAttribute("onmouseover", "hilite(evt)")
        grayArrow.setAttribute("onmouseout", "unhilite(evt)")
        grayArrow.setAttribute("rotateAngle", rotateAngle)
        grayArrow.setAttribute("transform","translate("+centerX+" "+centerY+")rotate("+rotateAngle+")")
        mySVG.appendChild(grayArrow)
    }

    for(var k=0;k<9;k++)
    {
        var rotateAngle=k*40+20
        var blackArrow=document.createElementNS(NS,"use")
        blackArrow.setAttributeNS(xref,"href", "#myBlackArrow")
        blackArrow.setAttribute("onmouseover", "hilite(evt)")
        blackArrow.setAttribute("onmouseout", "unhilite(evt)")
        blackArrow.setAttribute("rotateAngle", rotateAngle)

        blackArrow.setAttribute("transform","translate("+centerX+" "+centerY+")rotate("+rotateAngle+")")
        mySVG.appendChild(blackArrow)
    }
}

The two functions attached to each arrow for mouseover/out will extend/contract the target arrow( the arrow are scaled to 1.3 in the y direction).

//--onmouseover---
function hilite(evt)
{
    //---IE and Chrome---
    if(evt.target.correspondingUseElement)
        var target=evt.target.correspondingUseElement
    else  //---FF---
        var target=evt.target

    var rotateAngle=target.getAttribute("rotateAngle")
    target.setAttribute("transform","translate("+centerX+" "+centerY+")rotate("+rotateAngle+")scale(1,1.3)")
}
//---onmouseout--
function unhilite(evt)
{
    //---IE and Chrome---
    if(evt.target.correspondingUseElement)
        var target=evt.target.correspondingUseElement
    else  //---FF---
        var target=evt.target

    var rotateAngle=target.getAttribute("rotateAngle")
    target.setAttribute("transform","translate("+centerX+" "+centerY+")rotate("+rotateAngle+")")
}

Below is an example you can place in an .htm document to see it work. This tested OK for IE/CH/FF

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<center>
<div id="svgDiv" style='background-color:lightblue;width:400px;height:400px;'>
<svg id="mySVG" width="400" height="400">
<defs>
<polygon id="myBlackArrow" points="10,0 -10,0 -10,-80 -15,-80 0,-100 15,-80 10,-80"  fill="black" />
<polygon id="myGrayArrow" points="10,0 -10,0 -10,-80 -15,-80 0,-100 15,-80 10,-80"  fill="silver" />
</defs>
</svg>
</div>
<button onClick=placeArrowsAroundCircle()>Place Arrows Around Circle</button>
</center>
<script id=myScript>
var NS="http://www.w3.org/2000/svg"
var xref="http://www.w3.org/1999/xlink"
var centerX=200
var centerY=200
//---every 40 degrees around a center point--
//---button---
function placeArrowsAroundCircle()
{
    for(var k=0;k<9;k++)
    {
        var rotateAngle=k*40
        var grayArrow=document.createElementNS(NS,"use")
        grayArrow.setAttributeNS(xref,"href", "#myGrayArrow")
        grayArrow.setAttribute("onmouseover", "hilite(evt)")
        grayArrow.setAttribute("onmouseout", "unhilite(evt)")
        grayArrow.setAttribute("rotateAngle", rotateAngle)
        grayArrow.setAttribute("transform","translate("+centerX+" "+centerY+")rotate("+rotateAngle+")")
        mySVG.appendChild(grayArrow)
    }

    for(var k=0;k<9;k++)
    {
        var rotateAngle=k*40+20
        var blackArrow=document.createElementNS(NS,"use")
        blackArrow.setAttributeNS(xref,"href", "#myBlackArrow")
        blackArrow.setAttribute("onmouseover", "hilite(evt)")
        blackArrow.setAttribute("onmouseout", "unhilite(evt)")
        blackArrow.setAttribute("rotateAngle", rotateAngle)

        blackArrow.setAttribute("transform","translate("+centerX+" "+centerY+")rotate("+rotateAngle+")")
        mySVG.appendChild(blackArrow)
    }
}
//--onmouseover---
function hilite(evt)
{
    //---IE and Chrome---
    if(evt.target.correspondingUseElement)
        var target=evt.target.correspondingUseElement
    else  //---FF---
        var target=evt.target

    var rotateAngle=target.getAttribute("rotateAngle")
    target.setAttribute("transform","translate("+centerX+" "+centerY+")rotate("+rotateAngle+")scale(1,1.3)")
}
//---onmouseout--
function unhilite(evt)
{
    //---IE and Chrome---
    if(evt.target.correspondingUseElement)
        var target=evt.target.correspondingUseElement
    else  //---FF---
        var target=evt.target

    var rotateAngle=target.getAttribute("rotateAngle")
    target.setAttribute("transform","translate("+centerX+" "+centerY+")rotate("+rotateAngle+")")
}
</script>
</body>
</html>
Francis Hemsher
  • 3,478
  • 2
  • 13
  • 15
  • I really appreciate your time doing this and it works great! My main question is can you do this with paths or path groups? The main svg image I actually need to use is not an arrow but something similar with a very detailed top with bezier curves and lots of points, not sure if this can be done with polygon or path only. Also, the top is separated from the body so when I export to svg in illustrator there are actually two paths in a group. Is there any way to do this with, say, groups? so where you put the black and grey polygon it could be a black and grey group, each group with two paths – mike Mar 07 '14 at 00:26
  • or maybe its possible to apply this JS to an tag that is loading the entire .svg file? not sure which would be easier or even possible – mike Mar 07 '14 at 00:56
  • 1
    Yes, you can use a element for your element as you have shown in your fiddle. However, it must be transformed so it 'fits' the svg viewPort where you plan to use it. Let see what I can do to use your imported paths for my example. I'll post another answer. – Francis Hemsher Mar 07 '14 at 20:46
1

This is similar to the previous answer, except it uses your imported paths as the arrows, rather than polygons.

Firstly, your imported paths are humongus so they are too big for my 400x400 svg. Therfore, I have to scale them down to a reasonable size for my svg. I chose 80px as the height of the arrow. To do this, I have to get the actual size of the imported paths, which I have placed in a element. I use getBBox() to do this as follows.

//--get actual size---
var bb=importedPaths.getBBox()
var bbx=bb.x
var bby=bb.y
var bbw=bb.width
var bbh=bb.height
//---find the center point of this <g>---
var cx=bbx+.5*bbw
var cy=bby+.5*bbh

//---set desired size, i.e. scale the import--
var heightSize=80
var scale=heightSize/bbh
//---translate so it's  bottom is centered is at (0,0)
var transX=(-cx)*scale
var transY=(-cy-.5*bbh)*scale
importedPaths.setAttribute("transform","translate("+transX+" "+transY+")scale("+scale+" "+scale+")")

Now I have this sized and located with bottom of arrow centered at(0,0), so I can use it to make elements for symbols.

I make a copy of the re-sized import as the black arrow and gray arrow and place them in a <defs> element. <defs> are used to store/hide elements used for symbols.

//---create your symbol objects from transformed import, by placing <g> in defs--
var blackArrow=importedPaths.cloneNode(true)
blackArrow.id="myBlackArrow"
myDefs.appendChild(blackArrow)
var grayArrow=importedPaths.cloneNode(true)
grayArrow.id="myGrayArrow"
grayArrow.setAttribute("fill","silver")
myDefs.appendChild(grayArrow)

The following is a html file where you can see this in action. I have included a textarea showing how the svg is created. (I left the original arrow at upper left). Note: works OK in IE and Chrome, with FF not fully rendering the group of arrows.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<div  style='padding:5px;background-color:gainsboro;'>OK in:IE11/CH32, but FF23 does not fully render arrow in in circle<br /></div>
<br />
<br />
<br />
<center>
<div id=svgDiv style='width:400px;height:400px;background-color:lightgreen'>
<svg id="mySVG" width="400" y="400" overflow="visible" >
<defs id="myDefs">
</defs>
<g id="importedPaths" fill="black"><path  d="M2553.826,1137.28l-2.772-1.719l29.799,1445.434l195.02,1.004l9.765-1448.729 c0,0-63.94,42.007-113.36,42.007S2553.826,1137.28,2553.826,1137.28z" /><path d="M2451.84,1058.265c0,0,171.983,75.005,216.505,75.005s188.535-75.005,188.535-75.005l-190.691-366.037 L2451.84,1058.265z" /></g>
</svg>
</div>
<button onClick=placeArrowsAroundCircle()>Place Arrows Around Circle</button><br />
  <br />SVG Source:<br />
<textarea id=svgSourceValue style='font-size:110%;font-family:lucida console;width:90%;height:200px'></textarea>
</center>

<script id=myScript>
var NS="http://www.w3.org/2000/svg"
var xref="http://www.w3.org/1999/xlink"
//--place this at the center of this svg---
var centerX=200
var centerY=200
//---every 30 degrees around a center point--
//---button---
function placeArrowsAroundCircle()
{
    for(var k=0;k<12;k++)
    {
        var rotateAngle=k*30
        var grayArrow=document.createElementNS(NS,"use")
        grayArrow.setAttributeNS(xref,"href", "#myGrayArrow")
        grayArrow.setAttribute("onmouseover", "hilite(evt)")
        grayArrow.setAttribute("onmouseout", "unhilite(evt)")
        grayArrow.setAttribute("rotateAngle", rotateAngle)
        grayArrow.setAttribute("transform","translate("+centerX+" "+centerY+")rotate("+rotateAngle+")")
        mySVG.appendChild(grayArrow)
    }
    for(var k=0;k<12;k++)
    {
        var rotateAngle=k*30+15 //--offset 15 degrees---
        var blackArrow=document.createElementNS(NS,"use")
        blackArrow.setAttributeNS(xref,"href", "#myBlackArrow")
        blackArrow.setAttribute("onmouseover", "hilite(evt)")
        blackArrow.setAttribute("onmouseout", "unhilite(evt)")
        blackArrow.setAttribute("rotateAngle", rotateAngle)
        blackArrow.setAttribute("transform","translate("+centerX+" "+centerY+")rotate("+rotateAngle+")")
        mySVG.appendChild(blackArrow)
    }
    svgSourceValue.value=svgDiv.innerHTML
}
//--onmouseover---
function hilite(evt)
{
    //---IE and Chrome---
    if(evt.target.correspondingUseElement)
        var target=evt.target.correspondingUseElement
    else  //---FF---
        var target=evt.target

    var rotateAngle=target.getAttribute("rotateAngle")
    target.setAttribute("transform","translate("+centerX+" "+centerY+")rotate("+rotateAngle+")scale(1,1.3)")
}
//---onmouseout--
function unhilite(evt)
{
    //---IE and Chrome---
    if(evt.target.correspondingUseElement)
        var target=evt.target.correspondingUseElement
    else  //---FF---
        var target=evt.target

    var rotateAngle=target.getAttribute("rotateAngle")
    target.setAttribute("transform","translate("+centerX+" "+centerY+")rotate("+rotateAngle+")")
}

document.addEventListener("onload",init(),false)
//---adjust(transform) the imported paths so they fit this svg viewPort---
function init()
{
    //--get actual size---
    var bb=importedPaths.getBBox()
    var bbx=bb.x
    var bby=bb.y
    var bbw=bb.width
    var bbh=bb.height
    //---find the center point of this <g>---
    var cx=bbx+.5*bbw
    var cy=bby+.5*bbh

    //---set desired size, i.e. scale the import--
    var heightSize=80
    var scale=heightSize/bbh
    //---translate so it's  bottom is centered is at (0,0)
    var transX=(-cx)*scale
    var transY=(-cy-.5*bbh)*scale
    importedPaths.setAttribute("transform","translate("+transX+" "+transY+")scale("+scale+" "+scale+")")

    //---create your symbol objects from transformed import, by placing <g> in defs--
    var blackArrow=importedPaths.cloneNode(true)
    blackArrow.id="myBlackArrow"
    myDefs.appendChild(blackArrow)
    var grayArrow=importedPaths.cloneNode(true)
    grayArrow.id="myGrayArrow"
    grayArrow.setAttribute("fill","silver")
    myDefs.appendChild(grayArrow)
    //---see textarea---
    svgSourceValue.value=svgDiv.innerHTML
}
</script>
</body>
</html>
Francis Hemsher
  • 3,478
  • 2
  • 13
  • 15
  • This works awesome! I have a few questions... I can find resources to style svg but no resources to style use{} in css. can I style the in css or would it have to be more JS? Also I want to animate the transitions, maybe with jquery or css3... could I just apply a css3 transition: all to the somehow in css3 or will that not work since the rotation/size changes are done inline on the ? Also how do I remove the reference object/group in the upper left? Thanks again! – mike Mar 09 '14 at 03:19
  • I think most serious dynamic interaction with svg requires Javascript. To remove the element use: mySVG.removeChild(importedPaths) – Francis Hemsher Mar 09 '14 at 16:25
  • I see. Thanks Francis...I tried a few different ways to get rid of the reference object but it always removes all the arrows, not just the reference paths...the code you just gave above does the same thing, it removes all arrows..not just the reference object in the upper left – mike Mar 09 '14 at 21:43
  • 1
    At the very end of function init() insert the line: mySVG.removeChild(importedPaths) – Francis Hemsher Mar 10 '14 at 02:09