I am just getting into JS, so I may be missing something. I am trying to animate an SVG rectangle with a mouseover so that the shape appears to be 'fleeing' the mouse. When I try to change x and y by adding to them, the shape disappears. If I subtract, it behaves as expected.
Any help would be greatly appreciated.
HTML
<svg width="1200" height="600">
<rect x="100" y="100" width="100" height="100" id="firstShape" onmouseover="moveShape(firstShape);">
</svg>
Javascript
function moveShape(obj) {
var newX = obj.getAttribute("x") + 5;
var newY = obj.getAttribute("y") + 5;
obj.setAttribute("x", newX);
obj.setAttribute("y", newY);
}
Attributes are strings, and Javascript is very sloppy about the way it handles strings and numbers.
What you were actually doing is adding "5" to "100" and getting "1005" as a result.
If you convert the attributes to integers before modifying them, then your code will work fine.
function moveShape(obj) {
var newX = parseInt(obj.getAttribute("x")) + 5;
var newY = parseInt(obj.getAttribute("y")) + 5;
obj.setAttribute("x", newX);
obj.setAttribute("y", newY);
}
<svg width="1200" height="600">
<rect x="100" y="100" width="100" height="100" id="firstShape" onmouseover="moveShape(firstShape);">
</svg>
Related
In the following example I have in gold the intersection of 3 shapes (in this case I'm using circles but those 3 shapes can be anything) The golden intersection is the result of clipping with clip-path.
I would like to use the intersection as a symbol and for this I would need to know the bounding box of the intersection, i.e the red stroked rectangle.
If I'm using intersection.getBBox() I'm getting the bounding box before clipping.
How can I get the bounding box of the intersection?
console.log(intersection.getBBox())
svg{border:solid}
.circles{fill:none;stroke:black}
<svg id="svg" viewBox="-150 -150 300 300" width="300">
<defs>
<circle id="c1" cx="0" cy="-50" r="80"></circle>
<circle id="c2" cx="43.3" cy="25" r="80"></circle>
<circle id="c3" cx="-43.3" cy="25" r="80"></circle>
<clipPath id="clipC2"><use xlink:href="#c2"/></clipPath>
<clipPath id="clipC3"><use xlink:href="#c3"/></clipPath>
</defs>
<g class="circles">
<use xlink:href="#c1"/>
<use xlink:href="#c2"/>
<use xlink:href="#c3"/>
</g>
<g id="intersection">
<g clip-path="url(#clipC3)">
<use fill="gold" xlink:href="#c1" clip-path="url(#clipC2)"/>
</g>
</g>
<rect x="-38" y="-42" width="75" height="74" stroke="red" fill="none"/>
</svg>
The main idea is this:
I'm taking the svg element, make it base64 and use it as the src attribute of an image.
I'm painting the svg element on a canvas with the same size as the svg element.
I get the image data from the canvas
loop through the image data and get:
the smallest x value of a black pixel
the smallest y value of a black pixel
the biggest x value of a black pixel
the biggest y value of a black pixel
I'm using using those values to build the new viewBox value for the intersection.
//the svg's viewBox
let vB = { x: -100, y: -100, w: 200, h: 200 };
//canvas
let ctx = c.getContext("2d");
//set the size of the canvas equal to the size of the svg element
c.width = vB.w;
c.height = vB.h;
// draw the svg element on the canvas
let xml = new XMLSerializer().serializeToString(svg);
// make it base64 and use it as the src attribute of the image
let img=new Image()
img.src = "data:image/svg+xml;base64," + btoa(xml);
img.onload = function() {
//paint the image on the canvas
ctx.drawImage(this, 0, 0);
//get the image data from the canvas
let imgData = ctx.getImageData(0, 0, vB.w, vB.h).data;
// x the smallest x value of a black pixel
// y the smallest y value of a black pixel
// X the biggest x value of a black pixel
// Y the biggest y value of a black pixel
let x = vB.w,
y = vB.h,
X = 0,
Y = 0;
let n = 0;
for (let i = 0; i < imgData.length; i += 4) {
n++
if (imgData[i + 3] != 0) {
//if the alpha (i+3) value of the pixel is not 0
let _y = Math.ceil(i / (4 * vB.w));
let _x = (i / 4) % vB.w;
if (_x < x) { x = _x; }
if (_y < y) { y = _y; }
if (_x > X) { X = _x; }
if (_y > Y) { Y = _y; }
}
if(n==imgData.length/4){
let newViewBox = `${x + vB.x} ${y + vB.y} ${X - x + 1} ${Y - y}`;
reuleaux.setAttribute("viewBox", newViewBox);
console.log(`viewBox="${newViewBox}"`);
}
}
}
svg,
canvas {
outline: 1px solid;
}
<svg id="svg" viewBox="-100 -100 200 200" width="200">
<defs>
<circle id="c1" cx="0" cy="-50" r="80"></circle>
<circle id="c2" cx="43.3" cy="25" r="80"></circle>
<circle id="c3" cx="-43.3" cy="25" r="80"></circle>
<clipPath id="clipC2"><use xlink:href="#c2"/></clipPath>
<clipPath id="clipC3"><use xlink:href="#c3"/></clipPath>
</defs>
<g id="intersection">
<g clip-path="url(#clipC3)">
<use xlink:href="#c1" clip-path="url(#clipC2)"/>
</g>
</g>
</svg>
<!--<img id="img" width="200" height="200"/>-->
<canvas id="c"></canvas>
<svg id="reuleaux" viewBox="-100 -100 200 200" width="200" style="background:#dfdfdf">
<use xlink:href="#intersection"/>
</svg>
Unsure if this was the type of thing you were after.
let myBB = {
x: c2.getBBox().x,
get y() {
return c1.getBBox().y + this.width
},
get width() {
// return (posNum(c3.getBBox().x)) - (posNum(myBB.x));
let leftPointOfWidth = c2.getBBox().x;
let rightPointofWidth = c3.getBBox().x + c3.getBBox().width;
// 10000 to guarantee both positive numbers. very hacky
let mywidth = (rightPointofWidth + 10000) - (leftPointOfWidth + 10000);
return mywidth;
},
get height() {
return this.width;
}
}
I'm quite sure there's a better way to put it. And need to call the getters; They won't appear in the console.log(myBB)
Inputting the obtained coordinates and width gives the rect in yellow. (Pink rects are shoing centres of circles)
I want to the mid point of arc in svg.. Can any one tell formula to find the midbpoint of arc.,
The answer to that question is not straightforward. That's because in SVG arcs can be an elliptical arc. For example, what would you say is the midpoint on the following arc?
<svg width="200" height="200" viewBox="0 0 100 100">
<path fill="none" stroke="black" stroke-width="2"
d="M 20,75 A 21,50, 45, 1 1, 60,75"/>
</svg>
Anyway, without getting into complicated formulae, the simplest method is probably to take advantage of SVG's pointAtLength() method.
var myarc = document.getElementById("myarc");
// Get the length of the path
var pathLen = myarc.getTotalLength();
// How far along the path to we want the position?
var pathDistance = pathLen * 0.5;
// Get the X,Y position
var midpoint = myarc.getPointAtLength(pathDistance)
// For fun, let's add a dot at that position to mark it.
var svg = myarc.ownerSVGElement;
var circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
circle.setAttribute("cx", midpoint.x);
circle.setAttribute("cy", midpoint.y);
circle.setAttribute("r", "5");
circle.setAttribute("fill", "red");
svg.appendChild(circle);
<svg width="200" height="200" viewBox="0 0 100 100">
<path id="myarc" fill="none" stroke="black" stroke-width="2"
d="M 20,75 A 21,50, 45, 1 1, 60,75"/>
</svg>
I'm trying to animate the following html elements to implement a functionality similar to a volume wheel.
<svg id="circle_svg" width="200" height="175">
<circle cx="100" cy="85" r="75" stroke="black" stroke-width="2" fill="lightgray"/>
<line id="line_alpha" x1="100" y1="85" x2="100" y2="160" style="stroke:rgb(0,0,255);stroke-width:2"/>
<circle id="dot_alpha" cx="100" cy="160" r="10" stroke="black" stroke-width="2" fill="red"/>
</svg>
The basic idea is that clicking on the red dot and moving the mouse around should result in the following behavior:
The red dot moves along the circle (even if mouse doesn't stay exactly on it).
The end point of the line on the circle follows the red dot.
A number shown somewhere else in the page gets incremented or decremented with the amount of angular displacement.
I found a demo online that allows to drag an svg circle all around the page, by binding the elements of interest to mousedown and mouseup events and rewriting the attribute cx and cy of the circle to the current location of the mouse.
However when testing the code on jsfiddle with my example (or even with the original code) something is not working. Could you please take a look and give me advice on what might be going wrong?
Jsfiddle my settings
Jsfiddle original settings
I was able to find the solution to my question (thanks to a friend) and will post it as reference for others:
The main problem with pasting the code from this online demo into jsfiddle is that the order in which JavaScript libraries and functions is not predictable.
So some binding might be called before the binded function is defined.
Also, the code from the demo is more complicated that what I needed.
Here is the code solution on jsfiddle
Below is a working snippet for SO site
var dragging = false
var updateGraphics = function (e) {
if (dragging) {
var parentOffset = $('#wheel').offset();
var relX = e.pageX - parentOffset.left;
var relY = e.pageY - parentOffset.top;
var cx = +$('#circle').attr('cx')
var cy = +$('#circle').attr('cy')
var r = +$('#circle').attr('r')
var dx = relX - cx
var dy = relY - cy
//var dx = e.clientX - cx
//var dy = e.clientY - cy
console.debug('cx: ' + cx);
console.debug('cy: ' + cy);
console.debug('dx: ' + dx);
console.debug('dy: ' + dy);
console.debug('clientX: ' + e.clientX);
console.debug('clientY: ' + e.clientY);
console.debug('relX: ' + relX);
console.debug('relY: ' + relY);
var a = Math.atan2(dy, dx)
var dotX = cx + r * Math.cos(a)
var dotY = cy + r * Math.sin(a)
$('#dot').attr('cx', dotX);
$('#dot').attr('cy', dotY);
$('#line_2').attr('x2', dotX);
$('#line_2').attr('y2', dotY);
}
}
$('svg').on('mousedown', function (e) {
dragging = true
updateGraphics(e)
})
$('svg').on('mouseup', function (e) {
dragging = false
})
$('svg').on('mousemove', updateGraphics)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<svg id="wheel" width="200" height="175" style="background-color:lightgreen;">
<circle id="circle" cx="100" cy="85" r="75" stroke="black" stroke-width="2" fill="lightgray"/>
<line id="line_1" x1="100" y1="85" x2="100" y2="160" stroke-dasharray="15,15" style="stroke:rgb(255,0,0);stroke-width:2"/>
<line id="line_2" x1="100" y1="85" x2="100" y2="160" style="stroke:rgb(0,0,255);stroke-width:2"/>
<circle id="dot" cx="100" cy="160" r="10" stroke="black" stroke-width="2" fill="red"/>
</svg>
I'm a bit lost computing matrix transform (rotate / translate)
I'm applying a svg transform to a svg rect (r1)
<svg width="400" height="400" viewBox="0 0 400 400"
xmlns="http://www.w3.org/2000/svg">
<g id="r1" transform="translate(20,100) rotate(30, 175, 85)">
<rect width="350" height="170" fill="#69c" fill-opacity=".1" />
</g>
<g id="r2" transform="">
<rect width="150" height="100" fill="green" fill-opacity=".1" />
</g>
<g id="r3" transform="">
<rect width="150" height="100" fill="red" fill-opacity=".1" />
</g>
</svg>
this give me the following transform matrix
SVGMatrix { a: 0.8660253882408142, b: 0.5, c: -0.5, d: 0.8660253882408142, e: 85.945556640625, f: 23.887840270996094 }
if I apply this transform matrix to another rectangle (r2) the rectangle will move to the exact same position of the first one but I lost the initial x,y position before applying the rotation
How can I get these x, y values from the transform matrix ?
translate(x,y) rotate(a, x+rect.w, y+rect.height)
I've made a jsbin to illustrate my problem
https://jsbin.com/luvome/edit?html,js,output
I'm would be very grateful if you can help.
thanks.
Using this answer https://stackoverflow.com/a/15134993/2125385
I found this function
function computePosition(m, dim, angle){
var rad = angle*Math.PI/180;
var tx = m.e - dim.width/2 + Math.cos(rad)*dim.width/2 - dim.height/2*Math.sin(rad);
var ty = m.f - dim.height/2 + dim.width/2*Math.sin(rad) + dim.height/2*Math.cos(rad);
return {x:tx, y:ty}
}
I can now create a new transform matrix and apply to the rect r3.
var angle = 30;
var m = r1.transform.baseVal.consolidate().matrix;
var dim = r3.getBBox();
var pos = computePosition(m, dim, angle);
r3.transform.baseVal.initialize(
svg.createSVGTransformFromMatrix(
svg.createSVGMatrix()
.translate(pos.x, pos.y)
.translate(dim.width/2, dim.height/2)
.rotate(30)
.translate(-dim.width/2, -dim.height/2)
)
)
I've got some html with an embedded SVG within it as below
<section id="deck-head">
<svg xmlns="http://www.w3.org/2000/svg" width="250" height="250">
<g>
<title>Marker</title>
<g id="marker-frame">
<path fill="white" d="m133.300003,64.800003l0,-33.500004l-16.100006,0l0,33.299999c-30.799995,3.700005 -53.499996,29.599998 -53.499996,60.800003c0,33.900002 27.600002,61.299995 61.299999,61.299995s61.300003,-27.399994 61.300003,-61.299995c0,-30.900002 -23.600006,-56.599998 -53,-60.599998zm-53.5,60.699997c0,-24.900002 20.299995,-45.199997 45.199997,-45.199997s45.199997,20.299995 45.199997,45.199997s-20.199997,45.300003 -45.199997,45.300003s-45.199997,-20.400009 -45.199997,-45.300003z"/>
</g>
</g>
</svg>
<mark>90°</mark>
</section>
I need to be able to replicate the following layout without using any external javascript libraries
Ignore Red
I've managed to rotate the SVG simply enough to face the correct direction - however the trouble I'm having is getting the element to absolutely position by the tick/tip of the pink circle, depending on where its rotated to.
The logic in my head that I was following was something along the lines of:
Get the circle center point
Get the rotation
Find out what the left & top position would be based off the above.
However I'm completely lost on what the calculation would be to get the top and left coordinates to be able to absolutely position the mark where it should be.
These are just simple polar coordinates. jQuery is not needed, just for easier manipulations.
var angle = 0, center = 125, radius = 110, pi = Math.PI;
setInterval(function(){
angle += 1;
$('#c').attr('transform', 'rotate(' + angle + ', 125, 125)');
// calculate new coordinates for mark
var x = center + radius * Math.cos(angle * pi / 180 - pi / 2);
var y = center + radius * Math.sin(angle * pi / 180 - pi / 2);
$('#m').css({top: y + 'px', left: x + 'px'});
}, 100);
body {background-color:black;}
#m {position:absolute;}
section {poisition:relative;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<section id="deck-head">
<svg xmlns="http://www.w3.org/2000/svg" width="250" height="250">
<g>
<title>Marker</title>
<g id="marker-frame">
<path fill="white" id="c" d="m133.300003,64.800003l0,-33.500004l-16.100006,0l0,33.299999c-30.799995,3.700005 -53.499996,29.599998 -53.499996,60.800003c0,33.900002 27.600002,61.299995 61.299999,61.299995s61.300003,-27.399994 61.300003,-61.299995c0,-30.900002 -23.600006,-56.599998 -53,-60.599998zm-53.5,60.699997c0,-24.900002 20.299995,-45.199997 45.199997,-45.199997s45.199997,20.299995 45.199997,45.199997s-20.199997,45.300003 -45.199997,45.300003s-45.199997,-20.400009 -45.199997,-45.300003z"/>
</g>
</g>
</svg>
<mark id='m'>90°</mark>
</section>