Drag and mouseover in snap.svg - javascript

I'm relatively new to javascript, and am learning about drag and drop using snap.svg. My problem is in the drop. I can't tell if the dragged element is over the drop target. In this code, I want to drag the circle over the square, and thought I could use mouseover. My (distilled) example may also be a simpler version of this post.
var paper = Snap(300, 300);
var square = paper.rect(100, 100, 40, 40).attr({fill:"blue"});
var circle = paper.circle(50, 50, 20).attr({fill:"red"});
circle.drag();
square.mouseover(
function() {
console.log("Over the square");
}
);
As written, the mouseover will fire when you move the pointer over the blue square, but not when you drag the red circle over the blue square. If you reverse the creation of the square and circle, the mouseover fires either way, but of course the circle is behind the square.
Evidently the event gets caught in the view hierarchy (or something) and doesn't propagate. There must be an easy way around this. Any help?
(And if the best answer is, "use jQuery," fine, but I'd love to learn how to make this work directly, since snap.svg makes dragging so easy.)
Addition: The direction I'm hoping for: the snap.svg documentation for Element.drag() says, in part, "When Element is dragged over another element, drag.over.<id> fires as well." A fine, event-based direction, which would let me (for example) highlight the drop target without a lot of fuss.
But I haven't figured out how to listen for that event! Any help or advice?

Only quick way without collision or element detection from points that I can think of, is to place an almost invisible clone in front of the object, later in the DOM that you can't really see, eg ...
paper.append( square.clone().attr({ opacity: 0.000001 }) )
jsfiddle
Depends how complex your svgs are going to be as to whether this would work I guess, you also have a slight issue if you drop the element over it, your redrag start won't get picked up, so you would need to code around that as well. I think some testing is probably going to be the most bug free solution (there are a few solutions on S.O for getElementFromPoint or hit detection type solutions).
jsfiddle workaround for point above

Related

Circular canvas corners clickable in Chrome

I have two canvases. I have made them circular using border-radius. The 2nd is positioned inside the first one (using absolute position).
I have click events on both circles. If you click on inside canvas, the color at the point of the click is loaded in the outside canvas with opacity varying from white to the picked color and finally to black. If you click on outer canvas the exact color value at that point is loaded in the text-box at the bottom
I am unable to click in red zones (as shown in figure below) of the outer canvas when using chrome. I tried z-idex, arcs but nothing is helping me. But In Firefox everything is working fine.
Note: You can drag the picker object in the outer circle. But if you leave it in red zones, you would not be able to click it again in Chrome. Clicking in green zone will get you its control again
Code in this JSFiddle
Edit
I excluded all irrelevant code to make it easy. Now there is only a container having two canvas.
Filled simply with two distinct colors. Open following fiddle link in both chrome and firefox. Click on both cirles in different zones and see difference in chrome and firefox. I want them to behave in chrome as they do in firefox
Note I will ultimately draw an image in inner canvas.
Updated Fiddle Link
-
Your problem is because canvases currently are always rectangular, even if they don't look rectangular. Border radius makes the edges except the circle transparent, but it still doesn't stop events in Chrome on the corner areas. This is why you cannot click the bottom circle in those areas
I even tried putting it inside of a container that had a border-radius instead but the click event still goes through
With that being said, you have two options. You could either change your code to only use one canvas with the same type of layout, just drawing the background circle before the other each time. Essentially you'd draw a circle, draw your black to color to white gradient, use the xor operation to combine the two into one circle, then do the same with the rainbox gradient. You must draw the background circle first because canvas paints over the old layers every time
or
You could use javascript to only detect clicks in the circular area which takes just a little bit of math (: This solution is featured in edit below
In the future, CSS Shapes may allow canvases to be non-rectangular elements to be used, I'm actually not sure, but we don't have that capability yet at least
Edit
Alright, so after going through your code a bit it seems there are some things I should cover before I offer a solution
Setup all your finite variables outside of the functions that run every time. This means you don't put them (like radiuses, offsets, etc.) in the click function or something that runs often since they don't change
Your "radius"es are actually "diameter"s. The format of .rect goes .rect(x, y, width (diameter of circle), height (diameter of circle))
Almost always when overlaying canvases like you are you want to make them equal dimensions and starting position to prevent calculation error. In the end it makes it easier, doing all relative positioning with javascript instead of mixing it with CSS. In this case, however, since you're using border-radius instead of arc to make a circle, keep it like it is but position it using javascript ....
jQuery isn't needed for something this simple. If you're worried about any load speed I'd recommend doing it in vanilla javascript, essentially just changing the .click() functions into .onclick functions, but I left jQuery for now
You can declare multiple variables in a row without declaring var each time by using the following format:
var name1 = value1,
name2 = value2;
Variables with the same value you can declare like so:
var name1 = name2 = sameValue;
When children have position:absolute and you want it to be positioned relative to the parent, the parent can have position:relative, position:fixed, or position:absolute. I would think you'd want position:relative in this case
When you don't declare var for a variable it becomes global (unlessed chained with a comma like above). For more on that read this question
Now, onto the solution.
After talking with a friend I realized I could sort do the math calculation a lot easier than I originally thought. We can just calculate the center of the circles and use their radiuses and some if statements to make sure the clicks are in the bounds.
Here's the demo
After everything is set up correctly, you can use the following to detect whether or not it's in the bounds of each
function clickHandler(e, r) {
var ex = e.pageX,
ey = e.pageY,
// Distance from click to center
l = Math.sqrt(Math.pow(cx - ex, 2) + Math.pow(cy - ey, 2));
if(l > r) { // If the distance is greater than the radius
if(r === LARGE_RADIUS) { // Outside of the large
// Do nothing
} else { // The corner area you were having a problem with
clickHandler(e, LARGE_RADIUS);
}
} else {
if(r === LARGE_RADIUS) { // Inside the large cirle
alert('Outer canvas clicked x:' + ex + ',y:' + ey);
} else { // Inside the small circle
alert('Inner canvas clicked x:' + ex + ',y:' + ey);
}
}
}
// Just call the function with the appropriate radius on click
$(img_canvas).click(function(e) { clickHandler(e, SMALL_RADIUS); });
$(wheel_canvas).click(function(e) { clickHandler(e, LARGE_RADIUS); });
Hopefully the comments above and code make enough sense, I tried to clean it up as best as I could. If you have any questions don't hesitate to ask!

Is there a way to check if specific point (X,Y) is in the SVG element?

What I need to do is to understand if mouse leaves SVG object (path, i.e it is not a rectangular - can't use just offset, not a circular - can't use radius and center position, etc. ). I can not use mouse leave/enter events because I have a pointer for mouse that is always above all elements. Obviously I also can't just use elementFromPoint - because it gives the top layer element.
So the question:
Is there a way to understand if coordinates (X,Y) are in the specific element $("#element").
UPD:
I uploaded my current code to my website http://pekap.co/example/
I didn't create jsfiddle because I have SVG object to ebmed.
There you can find my JS, svg object I use, etc.
If you go to the svg object it changes its color and pointer appears (orange circle). The goal is to change color of the SVG area whenever we leave it/ enter it and display orange circle under mouse only inside SVG area.
Whereas currently I can accomplish on one of goals (either one with different code)
UPD 2.
Erik Dahlström gave almost perfect solution for me: set pointer-events to none in CSS. I will go for this now, however to make my day perfect it would be great if there was a way to detect when any part of circle is out of the SVG area.
I'm not sure I follow what you mean, the pointer is the little circle that follows the mouse?
If so, then just make that circle have pointer-events: none and it will be "transparent" to mouse events. Note that webkit/safari/chrome/blink doesn't yet support mouseenter and mouseleave so you'll likely need some scriptbased workaround (not sure if D3 does this already).
It should also be possible to do a solution based on using a CSS :hover rule on the path element. Set some property to some value on hover, and then check with getComputedStyle what the property is currently set to on the path element.
My suggestion would be to to create a image map of the area, its a lot of work but this seems to be what you need: http://jsfiddle.net/sb9j7/
<area shape="poly" name="dip" coords="253,102, 277,100, 280,105, 290,107, 295,111, 304,130, 290,140, 287,147, 240,157, 238,159, 227,153, 203,146, 198,125, 200,116, 214,102, 231,102" href="#">
this fiddle is from image mapster

I would like to reposition a circle created through Raphael js, and to use it as a letter in a title

I have created a vector graphic using Raphael JS - specifically a circle.
I would now like to use this circle as the letter "O" in a title. It is also a circle that will animate upon click. I would like to know if this is at all possible.
Here is a fiddle to explain better what I'm trying to say.
The html is very simple:
<h2>N<span id="canvas_cont"></span>OTRE APPROCHE :</h2>
Here is the jsfiddle
Basically the circle will act as the second letter in "Notre", and when clicked will move to the right of the screen. Other things will happen after, but this effect is what I'm trying to get....
Placing the Raphael canvas in a span is clever, but at the end of the day I suspect you'll regret mixing native HTML with Raphael in this way. Doing so would probably require a lot of absolute positioning and z-indexes that are better handled and supported in Raphael.
I recommend you simple draw the text in Raphael:
var text = r.set();
text.push(
r.text(10,20,"N"),
r.text(70,20,"TRE APPROCHE :")
);
text.attr({
'text-anchor': 'start',
'font-size':'36px'
});
If the SVG/VML styling is inadequate, you could also just use an image. Again, I would recommend placing that image on the canvas using Raphael (paper.image());
Note that, in the updated fiddle, I made the canvas a div the width of the logo.
Updated fiddle.

How to implement sketch brush, like in deviantART muro?

deviantART muro has a set of brilliant tools of painting. And I'm very curious how to implement these brushes like Sketch and Paintbrush, arithmetically?
Using any normal programming language to explain is okay, though I prefer C++ or JavaScript. I think it's better than read their JS source code.
I'd say it works something like:
Track mouse movement
On captured mouse movement, draw your desired brush from saved "Old mouse position" to captured "New mouse position", iterating at a pixel's distance at a time
If you move the mouse too fast for the script to capture, it will just look like a computed long straight line (which is what it looks like Muro is doing). If you want to get real fancy you can calculate the trajectory from previous mouse positions and draw that instead for a "smoother" line.
Since you specified Javascript you'd probably want to draw it in a canvas object.
EDIT 1:
Sketch specifically seems to save mouse movements and then loop through, say the 20 latest mouse movements for each mouse movement and draw a bezier curve from that point to the current point.
So, something like (pseudo code)
Object mousemovements = [];
on.mousemove(event)
{
if (mousemovements.length > 20)
{
mousemovements.removeLast();
}
mousemovements.insertAtBeginning([ event.mouseX, event.mouseY ]);
for-each (movement in mousemovements)
{
drawBeziercurveFromTo(movement.mouseX, movement.mouseY,
event.mouseX, event.mouseY);
}
}
Jquery/Canvas DEMO based on the above pseudo code
EDIT 2:
I had a closer look at how "Sketch" worked and it seems that they update the mouse pointer positions, moving the older points closer to the newer points. Something like this:
This DEMO works pretty much like the sketch brush

Disabling graphic element selection in VML and Internet Explorer

I have a JavaScript application that lets users move shapes around a drawing area, and I happen to be using the Google Closure library. In FF/Safari all is good. In IE, as graphic elements are moved, they get selected by the browser (both the moving element and other elements), showing colored dotted background around some elements in unpredictable ways:
http://i.imgur.com/O33MN.png
How can I turn off this behavior in IE?
It's hard to diagnose your problem on the information provided. IE VML is not very well supported and therefore pretty buggy.
In DojoX Drawing, I ran into a similar problem when drawing lines. VML has a bug where you can't drag and resize at the same time – but, you can drag and create at the same time, so I redraw the line, I don't transform it.
Further, I don't attach my click/drag events to the shape, I attach them to the overall main container, detect the id on the mousedown event, then track the mousemove and move the shape via doing a setTransform on the shape's container.
Essentially, because of the weak VML support, you have to be willing to try totally different things to get it to work.
After some experimentation, I found a partial answer.
The goog.events.Event class has a preventDefault method. Simply handle the MOUSEMOVE event on the graphics' element. Then call the event#preventDefault method:
var element = ... // some element
var graphics = goog.graphics.createGraphics('400', '300');
var fill = new goog.graphics.SolidFill('#00ff00', 0.5);
var stroke = new goog.graphics.Stroke(1, 'black');
graphics.drawEllipse(60, 60, 10, 10, stroke, fill);
graphics.drawEllipse(90, 90, 10, 10, stroke, fill);
graphics.render(element);
goog.events.listen(graphics.getElement(), goog.events.EventType.MOUSEMOVE, function(e) {
e.preventDefault();
e.stopPropagation();
});
Clicking inside the graphics element, then dragging no longer selects the circles. Again, this is only necessary on IE.
One minor problem remains. Pressing the mouse outside of the graphics area, then dragging the cursor into the graphics area results in the entire area being selected, or both the area and graphical elements.

Categories