简体   繁体   中英

Rotate an svg path around its own centre using d3

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 zm -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 zm -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 zm -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 zm -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> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM