I have a <div> that displays a graph inside. Sometimes this graph gets too big for the <div>, so I need a way for users to grab the graph with their mouse and move it around. I found the jQuery UI Draggable interaction and thought this is what I need. It was easy enough to get the basics to work, however, I'm having trouble getting this right.
Ideally, the graph can only be dragged to reveal otherwise hidden parts. For example, if there's more graph hidden to the right, then you can drag it to the left and see that hidden part. But then you can't drag it to the left anymore once everything to the right is visible. How do I implement something like this with jQuery UI Draggable? Is it possible? Is jQuery UI the right tool for this?
Less than ideal, but still ok, is that you can drag the graph wherever you want even if the graph is small enough to fit in the parent <div> and nothing is hidden. I can't even get this to work right. What happens is I can just choose not to specify the containment option. Then the graph isn't constrained.. The problem now is the graph's <div> is only a certain size (100% width and height of parent <div>). The nodes are placed with absolute positioning outside this size. Then when you go to drag the graph to reveal those hidden nodes, you can no longer drag the graph... because you're now clicking outside the graph's <div>.
I can maybe have a graph container <div> that I mess with to get things right and dynamically resize that container div as nodes are added or removed.. Or I can implement this without jQuery UI, just using the mousemove event.. What's the best approach? Is there another good library for this?
I think this is what you are looking for - Your container should be overflow:hidden, your graph would be contained in some thing with a width and height, and beyond that you just need to calculate a "constraint" box, which is going to be the .offset() of the container, adding the "extra space" by the calculating "overflowed" portion I.E. only allow dragging the thing from offset.left + container.innerWidth() - draggable.width() -> offset.left
Now, if either of these contraints already "fits" you'll need to make sure to "zero" it to the offset, and if they both fit, skip adding draggable... Put it all together and you get:
var contain = $("#container"),
big = $("#bigthing"),
offset = contain.offset(),
// calculate the "constraints"
constraints = [
offset.left + contain.innerWidth() - big.width(),
offset.top + contain.innerHeight() - big.height(),
offset.left,
offset.top
],
// it "fits" if our left/top constraint is higher or equal to the right/bottom
fitsX = constraints[0] >= constraints[2],
fitsY = constraints[1] >= constraints[3];
if (!(fitsX && fitsY)) {
if (fitsX) {
constraints[0] = constraints[2];
}
if (fitsY) {
constraints[1] = constraints[3];
}
big.draggable({
containment: constraints
});
}
Fiddled: http://jsfiddle.net/gnarf/jqy2b/1/
If you need to dynamically resize the draggable thing, just recalculate the containment option!
Related
For fun, I am creating a widget that can be toggled to use vertical typing (e.g. for Japanese). The vertical part is being done with a div that is contenteditable. At the moment, you can see both the textarea and the div, and watch their content change in sync. The checkbox does nothing yet.
My problem is that the svg that is the "handle" to drag and resize was inside the editable div, and this would get messed up if the user cut from or pasted into the div. So that's the real problem I'm solving, and if there's a better fix, let me know. In an attempt to fix this, I am putting the editable div within another div that has the svg, in order that the internal editable div will not affect it.
After doing this, the resize functionality does not work correctly. After clicking down on the handle, the div resizes extremely fast downwards (it's height growing).
Here is what I was trying to do "working": https://jsfiddle.net/m4Ljuzyn/167/
And here is the one where the resize is broken after I changed the layout a bit: https://jsfiddle.net/m4Ljuzyn/190/
I am using pure JavaScript only.
The problem comes from the fact that offsetLeft and offsetTop are relative to the element's parent. You're using that relative value with the mouse event's clientX/Y value and it's producing undesired results.
Take advantage of getBoundingClientRect for proper coordinates in your mousemove handler.
var bounds = verticalTextarea.getBoundingClientRect();
verticalTextarea.style.width = (evt.clientX - bounds.left) + "px";
verticalTextarea.style.height = (evt.clientY - bounds.top) + "px";
Be warned: getBoundingClientRect is an expensive calculation, and may negatively affect performance.
Update: Added Forked JSFiddle
https://jsfiddle.net/csoh7xzv/
I'm trying to create a spot the difference game with jQuery.
Basically, several images stacked, positioned absolutely in a container. Above the container there is the page header with a logo and a menu, which takes altogather about 120px above the images container.
When someone clicks an area inside the image, I put there a new div, with either a correct (V) mark, or a wrong (red X).
I'm trying to get the position of the click inside the element, using the following code (the following used event variable e is returned in the click event just to be clear):
var parentOffset = $(this).parent().offset();
var topOffset = e.clientY - offset.top;
My problem is that the offset from the top changes when I scroll the page down a little to the footer area, and then I do not position the new marker div in the correct height.
When I'm scrolled to the top of the page, the mark is position correctly.
I've created such a game before, but can't understand why suddenly the calculation is wrong :\
Seems that I get the distance minus the scroll height, but not sure.
Thanks for your insight,
Yanipan
I played around with Firebug a little and it looks like e.originalEvent.layerY is exactly what you're looking for.
It always shows the absolute coordinates of your click within the clicked object, no matter where the screen is scrolled.
I have two containers. A thumbnail container, and a "page" container. Both are divs. Thumbnails can be dragged back and forth between the two containers. I have revert on the thumbnails set to 'invalid', so they snap back to one of the two containers if they are dropped outside of either one of them.
The thumbnails must snap to a 20x20 grid inside the "page" container. This is so client the client can put the thumbnails in the "page" container in any place, but still be able to line them up neatly.
The problem is the draggable 'grid' option doesn't seem to work too well for this. It seems the "grid" is determined by the draggables location when you start dragging it, rather than acting as if the page has a real grid that can be snapped to.
Is there a way to fix this so the grid is based off the "page" container, rather than the position of the draggable when you start dragging it?
Check the snapping example on the Jquery UI Site:
http://jqueryui.com/demos/draggable/#snap-to
You can take their same example and specify both a grid and a snap parameter.
Then the snap will be based off of the top left corner of the snap selector.
$( "#draggable5" ).draggable({ snap: ".ui-widget-header", grid: [ 80, 80 ] });
The example on the Jquery site will now let the "80x80" box snap based on the big container.
In your situation it might be easiest to create a div with 100% width and height, then set the snap: selector (using css selectors) to that div, then specifying a grid to snap to...
Good Luck
Maybe you could try to round the starting position to the nearest 20 pixels by using the start event on the draggable.
Something like (untested...):
$('#draggable').draggable(
{snap : grid: [20,20]},
{start : function(event, ui) {
var startPosition = $(ui.draggable).position();
$(ui.draggable).css({
'left' : (Math.round(startPosition.left/20)*20)+'px',
'top' : (Math.round(startPosition.top/20)*20)+'px'});
}
}
);
I'm trying myself to achieve that but I'm cloning the dragged element to another container so that's even more tricky ;-)
I still have to figure out how to set the position of the helper in the start event...
Of course it will only work if the starting position is already absolute (like when dragged).
As a matter of fact, I've nearly achieved it by applying this method to the stop event and removing the grid property.
You don't get any visual feedback when moving the object around because there's no grid per se anymore, but when dropping it, it goes to your own grid:
stop: function(event, ui) {
var stopPosition = $(ui.draggable).position();
$(ui.draggable).css({'left' : (Math.round(stopPosition.left/20)*20)+'px', 'top' : (Math.round(stopPosition.top/20)*20)+'px'});
}
Here's a working example:
http://jsfiddle.net/qNaHE/3/
We have a script used to edit certain files inline. Basically, each file is broken down into sections, and hovering over a section will bring up a set of tools (just a div with image buttons) that you can use to edit that particular section. We have the parent elements (sections) set as position: relative, and the set of tools set as position:absolute which are set to the right side of the section. This all works fine, especially since many of these are rather small.
However, we do have many of these sections which can become quite large, reaching lengths of two screens or even more. In these cases we would like for the tools to sort of flow with the user's scrolling, so say if the user is looking at the vertical-middle of the section, the buttons will rest at the vertical middle as well, however, if the vertical center of the user's screen scrolls past the section but the user is still hovering over the section, we would like for the tools to remain within their parent element and not be able to pop out.
We already have a script to move an element with the user's scroll if it goes out of the screen, so I was thinking I could modify that a bit to do that, I'm just not sure how to bound the element by it's parent.
TL;DR: How would I create an element that attempts to be vertically centered in the user's window, but cannot leave it's parent element.
Keeping it vertically aligned but only displayed when the section is hovered wouldn't do the trick?
This sounds like a manual positioning. You could use jquery to get the size of the browserwindow, get the scroll offset of the parent and calculate the top of the tools acording to screensize and scrolloffset value of the parent.
I don't thin css can handle this alone.
You could also just use one toolbox for all sections and pass the parent element as parameter.
Best wishes
Here's a quick implementation I came up with based on Andreas's suggestion
$(window).scroll(function(){
var a = $(window).scrollTop() + ($(window).height() * .35);
var b = $("#movedelement");
var c = $(window).scrollTop() + ($(window).height() * .48);
if (a < (b.parent().offset().top + 8))
b.css({position: "absolute", top: "1em" });
else if (c > (b.parent().offset().top + b.parent().height() - 8))
b.css({position: "absolute", top: b.parent().height() - 100 });
else
b.css({position: "fixed", top: "35%" });
}
Tweak some numbers around for the element height. Dirty, but works.
I have a "box" popup that appears on mouseover for some links. The box is about 300px tall and the top side of the box is on the same level as the link position, however some of these links are at the lowest scrollable part of the page, thus the popup will be cut off.
Question
What values are used to detect the bottom of the page, or remaining scrollable distance to the bottom so that you can shift the popup as required?
I'm using jQuery, but a generic JavaScript solution is also welcome for reference.
Thank you.
Basically you want to find the bottom of the viewport relative to the document, and then compare them to the coordinates of the incoming event.
function handler(event) {
var bottomOfViewport = $(window).scrollTop() + $(window).height();
var bottomOfBox = event.pageY + HEIGHT_OF_BOX;
if ( bottomOfViewport < bottomOfBox )
// code to handle overflow condition
}
Thankfully, the pageX and pageY properties are relative to the document. Similar holds for the x-axis.