If anyone could help me figure out how to make the draggable elements contained in a div that changes scale based on window size, i'd really appreciate any guidance.
If I do:
element.draggable({
cursor: "move",
containment: '#container'
});
What will happen is it gives me the containment for the regular size of the container. So if I have a transform: scale(1.5), there will be space in the container that the draggable element can not go.
I've also tried containment: 'parent' but that get's very glitchy.
EDIT
I've found out how to get the top and left containment but I can't figure out how to get the right and bottom.
var containmentArea = $("#container");
containment: [containmentArea.offset().left, containmentArea.offset().top, ???, ???]
I've tried width and height from containmentArea[0].getBoundingClientRect() but that doesn't seem to be the right move either.
Here is a jsfiddle of some example code.
A version with resetting the coordinates in the drag event (since they were being recalculated already for the scale transformations), without using the containment:
var percent = 1, containmentArea = $("#container");
function dragFix(event, ui) {
var contWidth = containmentArea.width(), contHeight = containmentArea.height();
ui.position.left = Math.max(0, Math.min(ui.position.left / percent , contWidth - ui.helper.width()));
ui.position.top = Math.max(0, Math.min(ui.position.top / percent, contHeight- ui.helper.height()));
}
$(".draggable").draggable({
cursor: "move",
drag: dragFix,
});
//scaling here (where the percent variable is set too)
Fiddle
In the example width and height of the container are obtained inside the dragevent, you could also store them when scaling for better performance. By having them calculated inside the event, they still work after rescaling, although the percent variable still has to be set. To be truly generic, it could be obtained inside the event as well (and instead of a fixed container, ui.helper.parent() could be used)
Since the offset inside the dragevent is (0,0) related to the container (at least it is for the current setup), took the liberty of simplifying originalleft + (position - originalposition)/percent to position / percent
Start offset didn't seem to be necessary any more, so left it out in the fiddle, but can be re-added if needed.
Take a look to this :
http://jsfiddle.net/z0gqy9w2/3/
The edited code is the following one :
// Matrix regex to take the scale value property of $('#container') element
var matrixRegex = /matrix\((-?\d*\.?\d+),\s*0,\s*0,\s*(-?\d*\.?\d+),\s*0,\s*0\)/,
matches = $('#container').css('transform').match(matrixRegex);
// Matches have this value : ["matrix(1.5, 0, 0, 1.5, 0, 0)", "1.5", "1.5"] , so we need matches[1] value :
var scaleValue = matches[1];
$(".draggable").draggable({
cursor: "move",
start: startFix,
drag: dragFix,
containment: [containmentArea.offset().left, containmentArea.offset().top,
( ( containmentArea.offset().left + ( containmentArea.width() * scaleValue ) ) - ( $(".draggable").width() * scaleValue ) ) ,
( ( containmentArea.offset().top + ( containmentArea.height() * scaleValue ) ) - ( $(".draggable").height() * scaleValue ) ) ]
});
As you see, here is the trick :
( ( containmentArea.offset().left + ( containmentArea.width() * scaleValue ) ) - ( $(".draggable").width() * scaleValue ) )
Your right max position will be : The main container left offset + the true width of the container ( with scale ) - the item true width (to let it inside the container).
(Tip: Be free to change the "percent" var value as you want too see the results)
regex ref
Here is my solution:
var _zoom = 1.2,
$element = $('.draggable-element'),
$container = $('#container');
var containmentW,
containmentH,
objW,
objH;
$element.draggable({
start: function(evt, ui) {
ui.position.left = 0;
ui.position.top = 0;
containmentW = $container.width() * _zoom;
containmentH = $container.height() * _zoom;
objW = $(this).outerWidth() * _zoom;
objH = $(this).outerHeight() * _zoom;
},
drag: function(evt, ui) {
var boundReached = false,
changeLeft = ui.position.left - ui.originalPosition.left,
newLeft = ui.originalPosition.left + changeLeft / _zoom,
changeTop = ui.position.top - ui.originalPosition.top,
newTop = ui.originalPosition.top + changeTop / _zoom;
// right bound check
if(ui.position.left > containmentW - objW) {
newLeft = (containmentW - objW) / _zoom;
boundReached = true;
}
// left bound check
if(newLeft < 0) {
newLeft = 0;
boundReached = true;
}
// bottom bound check
if(ui.position.top > containmentH - objH) {
newTop = (containmentH - objH) / _zoom;
boundReached = true;
}
// top bound check
if(newTop < 0) {
newTop = 0;
boundReached = true;
}
// fix position
ui.position.left = newLeft;
ui.position.top = newTop;
// inside bounds
if(!boundReached) {
// do stuff when element is dragged inside bounds
}
}
});
Link to fiddle
Related
I have created a drag n drop file in which the user will enter the coordinates like 3*4 when the dropped element is clicked, using scalar factor I get height and width for the element. I want to change the height and width of the dropped element. Can anyone help?
$(document).ready(function () {
var x = null;
// Make element draggable
$(".drag").draggable({
helper: 'clone',
cursor: 'move',
tolerance: 'fit'
});
var i = 1, j = 0;
var x1, x2, y1, y2, tmp, tmp2;
var sf = pf;
$("#droppable").droppable({
drop: function(e, ui) {
var attr = ui.helper.attr('id');
if (typeof attr == typeof undefined || attr == false) {
ui.helper.attr('id',"id"+i);
}
tmp = ui.helper.attr('id');
x = ui.helper.clone().bind("click",'img',function(){
alert("clicked"+ ui.helper.attr('id') + tmp);
leftpos = ui.offset.left-210;
toppos = ui.offset.top;
var cor = window.prompt("Enter coordinates x1,y1");
var c = cor.split(',');
var w = c[0]*sf;
var h = c[1]*sf;
w = w/2;
h = h/2;
ui.helper.width(leftpos-w);
ui.helper.height(toppos-h);
});
x.draggable({
helper: 'original',
containment: '#droppable',
tolerance: 'fit',
});
x.appendTo('#droppable');
ui.helper.remove();
i++;
}
});
});
There are couple of things which are missing in your code.
The click event handler is a closure , which means it would have access to ui.helper even though the event handler function is executed outside its defined scope. but to change the width and height, you need to refer to the current event bounded object via this keyword.
variable pf is undefined, so I have defined one local variable with the same name.
With your current logic, the target dropped item looks odd, so I have set the static value of 300 for width and height in below jsfiddle.
JS CODE:
x= ui.helper.clone().bind("click", 'img', function() {
//leftpos = ui.helper.offset.left - 210;
//toppos = ui.helper.offset.top;
leftpos = ($(this).offset().left) - 210;
toppos = ($(this).offset().top);
...
//ui.helper.width(leftpos-w);
//ui.helper.height(toppos-h);
$(this).width(300);
$(this).height(300);
Live Demo # JSFiddle
I'm using JQuery Draggable to move items round a grid. Objects snap to a 32x32 grid area. I want to be able to cancel a grid snap if an object is in the same position.
The drag cannot be cancelled, it must just be prevented from entering the square. After it is prevented and moved back to the previous position, if the user continues to drag into a new unoccupied grid position, it must snap to that one.
I've created a demo which serves the purpose explained above however the image glitches when it tries to enter the new position but is then cancelled back to the old position.
https://jsfiddle.net/dtx7my4e/1/
Here's the code in that fiddle:
HTML:
<div class="drop-target">
<div class="drag-item" object-id="0"></div>
<div class="drag-item" style="left: 32px;" object-id="1"></div>
</div>
Javascript:
var objects = [
[0, 0],
[1, 1]
];
$(function() {
$(".drag-item").draggable({
grid: [ 32, 32 ],
containment: '.drop-target',
drag: function (event, obj){
let objectId = $(this).attr('object-id');
var objectPositionX = $(this).position().left / 32;
var objectPositionY = $(this).position().top / 32;
var previousPositionX = Math.floor(objects[objectId][0]);
var previousPositionY = Math.floor(objects[objectId][1]);
if (objectPositionX != previousPositionX || objectPositionY != previousPositionY) {
if(!isObjectInPosition(objects, [objectPositionX, objectPositionY])) {
objects[objectId] = [objectPositionX, objectPositionY];
} else {
obj.position.left = previousPositionX * 32;
obj.position.top = previousPositionY * 32;
}
}
}
});
});
function isObjectInPosition(arrayToSearch, positionToFind)
{
for (let i = 0; i < arrayToSearch.length; i++) {
if (arrayToSearch[i][0] == positionToFind[0]
&& arrayToSearch[i][1] == positionToFind[1]) {
return true;
}
}
return false;
}
CSS:
.drag-item {
background-image: url("http://i.imgur.com/lBIWrWw.png");
background-size: 32px auto;
width: 32px;
height: 32px;
cursor: move;
}
.drop-target {
background: whitesmoke url("http://i.imgur.com/uUvTRLx.png") repeat scroll 0 0 / 32px 32px;
border: 1px dashed orange;
height: 736px;
left: 0;
position: absolute;
top: 0;
width: 736px;
}
Thank you, any help is greatly appreciated.
Toby.
If you're willing to modify draggable itself, I think it would make the logic easier to apply. Once the drag event is triggered you can do lots of things, but you have much more control if you modify the _generatePosition method of draggable. It may look more complicated at first but for this kind of behavior, it's sometimes easier to work.
Basically, you can run your isInPosition function after the check for grid and containment has been applied. Normally next step is to set the new position, but if your isInPosition returns true, you prevent dragging. Something like this:
'use strict'
// This is the function generating the position by calculating
// mouse position, different offsets and option.
$.ui.draggable.prototype._generatePosition = function(event, constrainPosition) {
var containment, co, top, left,
o = this.options,
scrollIsRootNode = this._isRootNode(this.scrollParent[0]),
pageX = event.pageX,
pageY = event.pageY;
// Cache the scroll
if (!scrollIsRootNode || !this.offset.scroll) {
this.offset.scroll = {
top: this.scrollParent.scrollTop(),
left: this.scrollParent.scrollLeft()
};
}
/*
* - Position constraining -
* Constrain the position to a mix of grid, containment.
*/
// If we are not dragging yet, we won't check for options
if (constrainPosition) {
if (this.containment) {
if (this.relativeContainer) {
co = this.relativeContainer.offset();
containment = [
this.containment[0] + co.left,
this.containment[1] + co.top,
this.containment[2] + co.left,
this.containment[3] + co.top
];
} else {
containment = this.containment;
}
if (event.pageX - this.offset.click.left < containment[0]) {
pageX = containment[0] + this.offset.click.left;
}
if (event.pageY - this.offset.click.top < containment[1]) {
pageY = containment[1] + this.offset.click.top;
}
if (event.pageX - this.offset.click.left > containment[2]) {
pageX = containment[2] + this.offset.click.left;
}
if (event.pageY - this.offset.click.top > containment[3]) {
pageY = containment[3] + this.offset.click.top;
}
}
if (o.grid) {
//Check for grid elements set to 0 to prevent divide by 0 error causing invalid argument errors in IE (see ticket #6950)
top = o.grid[1] ? this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1] : this.originalPageY;
pageY = containment ? ((top - this.offset.click.top >= containment[1] || top - this.offset.click.top > containment[3]) ? top : ((top - this.offset.click.top >= containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
left = o.grid[0] ? this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0] : this.originalPageX;
pageX = containment ? ((left - this.offset.click.left >= containment[0] || left - this.offset.click.left > containment[2]) ? left : ((left - this.offset.click.left >= containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
}
if (o.axis === "y") {
pageX = this.originalPageX;
}
if (o.axis === "x") {
pageY = this.originalPageY;
}
}
// This is the only part added to the original function.
// You have access to the updated position after it's been
// updated through containment and grid, but before the
// element is modified.
// If there's an object in position, you prevent dragging.
if (isObjectInPosition(objects, [pageX - this.offset.click.left - this.offset.parent.left, pageY - this.offset.click.top - this.offset.parent.top])) {
return false;
}
return {
top: (
pageY - // The absolute mouse position
this.offset.click.top - // Click offset (relative to the element)
this.offset.relative.top - // Only for relative positioned nodes: Relative offset from element to offset parent
this.offset.parent.top + // The offsetParent's offset without borders (offset + border)
(this.cssPosition === "fixed" ? -this.offset.scroll.top : (scrollIsRootNode ? 0 : this.offset.scroll.top))
),
left: (
pageX - // The absolute mouse position
this.offset.click.left - // Click offset (relative to the element)
this.offset.relative.left - // Only for relative positioned nodes: Relative offset from element to offset parent
this.offset.parent.left + // The offsetParent's offset without borders (offset + border)
(this.cssPosition === "fixed" ? -this.offset.scroll.left : (scrollIsRootNode ? 0 : this.offset.scroll.left))
)
};
}
var objects = [
[0, 0],
[1, 1]
];
$(function() {
$(".drag-item").draggable({
grid: [32, 32],
containment: '.drop-target',
// on start you remove coordinate of dragged item
// else it'll check its own coordinates
start: function(event, obj) {
var objectId = $(this).attr('object-id');
objects[objectId] = [null, null];
},
// on stop you update your array
stop: function(event, obj) {
var objectId = $(this).attr('object-id');
var objectPositionX = $(this).position().left / 32;
var objectPositionY = $(this).position().top / 32;
objects[objectId] = [objectPositionX, objectPositionY];
}
});
});
function isObjectInPosition(arrayToSearch, positionToFind) {
for (let i = 0; i < arrayToSearch.length; i++) {
if (arrayToSearch[i][0] === (positionToFind[0] / 32) && arrayToSearch[i][1] === (positionToFind[1] / 32)) {
return true;
}
}
return false;
}
https://jsfiddle.net/bfc4tsrh/1/
I adapted this code to create a large div which scrolls horizontally inside a smaller div, depending on the position of the mouse.
You can see my example here.. http://thetally.efinancialnews.com/tallyassets/20years/index.html
What I am trying to achieve is for the inner (yellow) div to stop at a maximum of left:0px, in other words the far left of the yellow div will become stuck to the far left of the outer div if you go that far.
I tried to implement this with an 'if else' statement, however as this piece of code gets run every 30th of a second it creates a strange result, which I can't find a solution for. I'm sure its very simple but its stumped me
You can see my code here...
var x=0,
rate=0,
maxspeed=10;
var backdrop = $('.container');
var years = $('.events');
$('.direction', backdrop).mousemove(function(e){
var $this = $(this);
var left = $this.is('.left');
if (left){
var w = $this.width();
rate = (w - e.pageX - $(this).offset().left + 1)/w;
} else {
var w = $this.width();
rate = -(e.pageX - $(this).offset().left + 1)/w;
}
});
backdrop.hover(function(){
var scroller = setInterval( moveBackdrop, 30 );
$(this).data('scroller', scroller);
},
function(){
var scroller = $(this).data('scroller');
clearInterval( scroller );
});
function moveBackdrop(){
if ( parseInt(years.css("left"), 10) <= 0 ) {
x += maxspeed * rate;
var newpos = x+'px';
years.css('left',newpos);
} else {
years.css('left','0');
}
}
The code in question is right here at the end^
Is this what you were trying to do?
function moveBackdrop(){
if ( parseInt(years.css("left"), 10) <= 0 && rate < 0 ) {
// Means that the element is already at left: 0 or less,
// and we are trying to move it even more left with rate being negative.
// So keep the element at left: 0
years.css('left','0');
} else {
x += maxspeed * rate;
var newpos = x+'px';
years.css('left',newpos);
}
}
Extra note for future: parseInt uses base 10 by default :) so parseInt("20px") will equal 20
Final Edit: Ah there is an even better way to do it.
function moveBackdrop(){
x += maxspeed * rate;
if( x < 0 ) x = 0; // If left less than 0, fix it at 0
var newpos = x+'px';
years.css('left',newpos);
}
I made a carousel using 2 divs named "left" and "right" putting mousemove events on them. I wanted to make it go up and down as well so I created a "top" and "bottom" and noticed that I couldn't make them combine to go the way the cursor goes.
I thus thought of targeting a specific area in the container (i.e top half of my container div) instead of creating divs inside triggering a specific direction, this way (I think) I can trigger all these event altogether. However after now hours of research I couldn't find a way to do so.
How should I proceed ? here is the code : http://jsfiddle.net/pool4/vL5g3/3/
var x=0,
y=0,
rateX=0,
rateY=0,
maxspeed=10;
var backdrop = $('.backdrop');
$('.directionx', backdrop).mousemove(function(e){
var $this = $(this);
var left = $this.is('.left');
var right = $this.is('.right');
if (left){
var w = $this.width();
rateX = (w - e.pageX - $this.offset().left + 1)/w;
}
else if (right){
var w = $this.width();
rateX = -(e.pageX - $this.offset().left + 1)/w;
}
});
$('.directiony', backdrop).mousemove(function(e){
var $this = $(this);
var top = $this.is('.top');
var bottom = $this.is('.bottom');
if (top){
var h = $this.height();
rateY = (h - e.pageY - $this.offset().top + 1)/h;
}
else if (bottom) {
var h = $this.height();
rateY = -(e.pageY - $this.offset().top + 1)/h;
}
});
backdrop.hover(
function(){
var scroller = setInterval( moveBackdrop, 30 );
$(this).data('scroller', scroller);
},
function(){
var scroller = $(this).data('scroller');
clearInterval( scroller );
}
);
function moveBackdrop(){
x += maxspeed * rateX;
y += maxspeed * rateY;
var newpos = x+'px '+y+'px';
backdrop.css('background-position',newpos);
}
Your problem is that the divs that control movement up and down are placed over the ones that control left and right, so the latter do not receive the mousemove event ever. Mouse events do not propagate through layers, even if they're transparent. I changed your code and CSS, so each div is in one of the corners. To make things easier, I've used data-* attributes so the direction controlled by each div is set in a declarative way, without the need to change the code. You'll see that the code is much simpler (and it could be simplified even more).
By the way, you could achieve this witout extra divs, just controlling where the cursor is (to the top, right, left or bottom of the center of the div).
backdrop.on('mousemove', '.dir', function(e){
var $this = $(this);
var direction = $(e.target).attr('data-direction');
var left = direction.indexOf('left') > - 1;
var right = direction.indexOf('right') > - 1;
var top = direction.indexOf('up') > - 1;
var bottom = direction.indexOf('down') > - 1;
if (left){
var w = $this.width();
rateX = (w - e.pageX - $this.offset().left + 1)/w;
}
else if (right){
var w = $this.width();
rateX = -(e.pageX - $this.offset().left + 1)/w;
}
if (top){
var h = $this.height();
rateY = (h - e.pageY - $this.offset().top + 1)/h;
}
else if (bottom) {
var h = $this.height();
rateY = -(e.pageY - $this.offset().top + 1)/h;
}
});
I've updated your fiddle.
EDIT In this new fiddle I do it without extra divs:
var w = backdrop.width() / 2;
var h = backdrop.height() / 2;
var center = {
x: backdrop.offset().left + backdrop.width() / 2,
y: backdrop.offset().top + backdrop.height() / 2
};
backdrop.on('mousemove', function(e){
var offsetX = e.pageX - center.x;
var offsetY = e.pageY - center.y;
rateX = -offsetX / w;
rateY = -offsetY / h;
});
backdrop.hover(
function(){
var scroller = $(this).data('scroller');
if (!scroller) {
scroller = setInterval( moveBackdrop, 30 );
$(this).data('scroller', scroller);
}
},
function(){
var scroller = $(this).data('scroller');
if (scroller) {
clearInterval( scroller );
$(this).data('scroller', null);
}
}
);
As you see, the mousmove handler is considerably simpler.
To avoid issue of children losing event could use just the one.
First HTML from 4 child divs to just one
<div class="backdrop">
<div class="direction"></div>
</div>
<div id="pos"></div>
Next Inside the mousemove find your relative position
//Get Relative Position
var relX = e.pageX - $this.offset().left;
var relY = e.pageY - $this.offset().top;
Get Relative Position as a percentage of width and put 50% of it in negative for direction
var w = $this.width();
rateX = ((relX / w) - 0.5) * -1;
var h = $this.height();
rateY = ((relY / h) - 0.5) * -1;
Fiddle
I want to know if a node intersects another node like this schema:
Use the DOM attributes top and left attributes to figure out where they are along with clientWidth and clientHeight
This fiddle checks for overlaps (assuming you move both boxes): http://jsfiddle.net/maniator/JnTaq/
Code:
$('.drag').draggable({
stop: function(event, ui) {
var others = $('.drag').not(this),
_self = this,
length = $(_self).width();
others.each(function(i, item) {
var this_left = parseInt(item.style.left) - length,
this_top = parseInt(item.style.top) - length;
if (ui.position.left > this_left && ui.position.top > this_top) {
var left = Math.abs(ui.position.left - this_left),
top = Math.abs(ui.position.top - this_top);
if (left <= (length*2) && top <= (length*2)) {
alert('overlap');
}
}
});
}
});