interact.js dragged item move on top - javascript

it's my first time to use this plugin.
I want to add a stacking of elements whenever it's dragged. I use z-index and position relative.
onstart: function (event) {
//get max z-index on page
var maxZ = Math.max.apply(null,
$.map($('body > *'), function(e,n) {
if ($(e).css('position') != 'static')
return parseInt($(e).css('z-index')) || 1;
}));
event.target.style.background = 'red';
event.target.style.zIndex = maxZ + 1;
event.target.style.position = 'relative';
},
Is there more efficient way to do this?
original drag and drop demo
my edited drag and drop demo with stacking

Your solution works well if the elements are not also used as dropzones. When dropzones overlap, interact.js chooses the element that is deepest in the DOM and so should appear above the other dropzones ulness some CSS properties change the drawing order (eg. position, transform, z-index, etc.) If overlapping dropzones are re-ordered with z-index then it might happen that the drop targets look incorrect.
If the elements that you're targeting are all siblings and all have absolute or fixed position (before any drag happens), then appending one element to it's parent should bring it in front of the others without using z-index so drop checks should work fine.
onstart: function (event) {
var target = event.target;
// Bring element in front of its siblings
target.parentNode.appendChild(target);
...
}

Related

Change background on hover of a draggable div

I have a small draggable division (black) and many nodes with different IDs beside it
I need a hovering effect when I drag the black element on the nodes. What I am doing in allowDrop function is:
var dragObj;
function drag(ev){
dragObj = ev;
}
function allowDrop(ev){
ev.preventDefault();
var Dragged = dragObj;
var Hovered = ev;
var leftIndent = Dragged.layerX;
var hoveredID = Hovered.target.id.toString().split('_');
var nodesOnLeft = Math.floor(leftIndent/12);
var startingNode = hoveredID[0]-nodesOnLeft;
for (i=1;i<=Math.floor(draggableElementLength/12);i++){
var toApplyCssID = startingNode.toString() + '_1';
var toApplyCss = document.getElementById(toApplyCssID);
$('#'+toApplyCssID).css('background-color','lightgreen');
}
}
basically I am using the layerX property to find out the distance between my mouse pointer and draggable division's border and adjusting that to calculate number of nodes where I have to apply new CSS and applying that by finding the ID of node.
This is working but its making the process very slow as it involves many calculations and its not the hover effect as its not going away when I am removing the draggable division.
Is there any other way to achieve this or do I need to make code changes in existing code.
thanks
Without the HTML and the rest of the script, I can only point you in the direction you should be taking in this kind of scenario:
Don't constantly repeat calculations (that do not change) in a loop, store them outside the function:
Use document.getElementById(toApplyCssID) for each node and store the
elements in an array
Get the distance from the mouse position to the required edge or
edges of the div (leftIndent) on initial drag/mousedown and store
that in a variable
Store Math.floor(draggableElementLength/12) in another variable (you
haven't shown where this originates, but it doesn't seem to change in
the function...)
Apply the CSS to the relavent elements (from the array of elements)
using the other stored values to select them
Remove the CSS on those that had it applied earlier
This may not be the ultimate solution, but these are some of the things you can look at to speed up what you (may) have currently.

Unable to calculate the hover element's exact position

I am just trying to get the mouse hover div's position at the right according to the space around. Somehow I am able to do this in first two columns but not for other columns. May be my calculations while writing the condition state are wrong.
Can anyone please help?
JS Fiddle URL:
http://jsfiddle.net/mufeedahmad/2u1zr11f/7/
JS Code:
$('.thumb-over-team li').find('.cover').css({opacity:0});
$('.thumb-over-team li').on('mouseenter', function(){
var $this = $(this),
thisoffset = $this.position().left,
openDivId = $(this).find('.cover'),
thumbContainer = '.thumb-over-team',
speedanim = 200;
if(thisoffset + openDivId.outerWidth() >= $(thumbContainer).outerWidth()){
//thisoffset = $(thumbContainer).outerWidth() - openDivId.outerWidth() - 212;
thisoffset = thisoffset - openDivId.outerWidth()+10;
openDivId.stop().css({'z-index':'9999'}).animate({'opacity':'1', 'left':-thisoffset}, 200);
}else{
openDivId.stop().css({'z-index':'9999'}).animate({'opacity':'1', 'left':'212px'}, 200);
}
}).on('mouseleave', function(){
$(this).find('.cover').stop().css({'z-index':'-1'}).animate({'opacity':'0', 'left':'200px'}, 200);
});
$('.close-parent').on('click', function(){
$(this).parents('.cover').stop().css({'z-index':'-1'}).animate({'opacity':'0', 'left':'200px'}, 200);
});;
In your first conditional, try to calculate the position of the offset as:
thisoffset = ($(thumbContainer).outerWidth() - openDivId.outerWidth() - thisoffset);
That way, you're adjusting the appearing square (.cover) when it doesn't fit inside the container, to be as close possible to its rightmost edge: (maximum width - appearing square width - current li position)
Calculated this way, you can animate it with the new offset in positive:
openDivId.stop().css({'z-index':'9999'}).animate({'opacity':'1', 'left':thisoffset}, 200);
See it working here.
For elements that "almost" fit, the current system isn't completely precise because of what I already pointed out in my previous comment: the appearing square, even if it were at 0 opacity, would still be inside the containing element (($(thumbContainer)) or .thumb-over-team) and it would add its width to the total width of the container.
So your conditional may think that there's enough available space in the container to make the expandable element fit, but that would go out of the screen. As an example, notice that there's a horizontal scrollbar from the very beginning, caused by this effect, where your containing .thumb-over-team element doesn't fit in the screen.
But I would say that more precision in this point would require a fresh new approach to your system where the appearing .cover elements were out of the containing ul .thumb-over-team
Fresh take on the problem, essentially based on the main issue: the expandable text block (.cover) used to add its width to the container (.thumb-over-team). This altered the calculations on available container space, and made the text containers go off screen.
The solution is to make sure the expandable .cover elements aren't contained inside the .thumb-over-team element, so they won't impact the calculations on available width.
Here is a JSFiddle containing this new approach: link.
Explanation of how it works:
The idea was to create a separate element called .cover-container and let's put all the expandable .cover elements in there.
We want to associate every image in the li elements in .thumb-over-team with their appropriate .cover (so the first image triggers the first .cover to show, the second image would show the second cover, and so on.) We achieve is by finding out the index of the element that triggered the event:
thisLiIndex = $this.index() + 1
And then selecting the cover in the matching position:
openDivId = $('.cover-container .cover:nth-child(' + thisLiIndex + ')')
The expandable covers shouldn't interfere with the mouseenter or mouseleave events of .thumb-over-team, so we make it to ignore mouse events via CSS:
.cover-container{pointer-events:none;}
Changing from one image to another would automatically trigger new events, so the expanding covers stay visible when the mouse stays on the images, but close automatically when the mouse exits them.
Since the covers are now outside of $(thumbContainer), openDivID.outerWidth() does not alter $(thumbContainer).outerWidth(), and we can use that safely in our positioning.
If I understand the placement that you want, for covers that fit, the position is the current offset (position of the li element that triggered the event) plus the width of the image and some subtle margin
imageWidth + rightSeparation + thisoffset
And for covers that won't fit inside of the screen, we keep them just inside of the screen
thisoffset = $(thumbContainer).outerWidth() - openDivId.outerWidth();

Determine nearest element with a z-index?

Is there a proper way of determining if an element (any element) closest to the targetted element has a z-index?
For example, I append a div to a layer of DOM and I want to know if there's an element near that div with a z-index so i can position that div accordingly so it won't get lost under the layers because I don't know where and when it would be appended.
Thanks!
EDIT:
I have a hint plugin that appends hints to a page dynamically and then saves them on a server. I'd post the code but i'm afraid it won't be of much relevance to my question.
I would crate a function like the below one. It traverses through each of the element's parents and checks if it has a z-index
function getElement($target){
var $parents = $target.parents();
for(var i=0;i<$parents.length;i++){
if($parents[i].css('z-index').length>0){
return $parents[i];
}
}
return false;
}
My is a similar solution as the one from Sethunath.
/**
* Helper to retrieve the z-index which is required to bring up the dragged element in front.
* #returns {undefined}
*/
getZIndex: function () {
if (this.z_index === undefined) {
var recrusive_z_index_search = function(element) {
// Break on body.
if (element[0] === $('body')[0]) {
return 2;
}
// Get parent element
var parent = $(element).parent();
// Check if parent has z-index
var z_index = parent.css('z-index');
if (z_index.length > 0 && !(/\D/).test(z_index)) {
// Return found z-index.
return z_index;
}
// Z-index not found, try again on next parent.
return recrusive_z_index_search(parent);
};
this.z_index = recrusive_z_index_search($(this.element));
}
return this.z_index;
},
This solution was added within an object class. this.element is the starting point from where it searchs. It stops when we found an element with z-index not really a one with a numeric value, because z-index can also be set to "auto". If we reached the body we break the chain and set it to 2 by default as a fallback.
Hope this helps others.
You can use Firefox firebug https://getfirebug.com/ or Chrome Developer Tools (built in Chrome) to inspect the element you'd like to find the z-index of.
Appending the element near another div isn't related to the z-index property. The z-index will bring the highest number element in front when objects overlap. Otherwise, it is not the property you are looking for. Especially if you didn't define any z-index properties for other elements.
CSS specificity is what you might be looking for here

Contain draggable element based on offset of one of its children

I have a draggable container that holds a varying amount of children displayed side by side with display: inline-block. I want the containment to be based on the offset of either the first or last child, so the user is able to drag all but one (either first or last) offscreen in either direction.
drag: function(){//prevent dragging once last element offset is within 10% of window width
if ($('.element:last').offset().left < $(window).width() * .10)){
return false;
}
}
The problem is that once I return false, I can no longer drag. Some variation of containment seems like the only option here, but I'm not quite sure how to feed it such a complicated condition.
What I ended up doing was putting everything inside an object that gets passed to the draggable method. Once I have the object I can update the containment property whenever the position of the child changes. After it's set, I can reinitialize draggable.
var borg = {
axis: 'x',
drag:function(){}
}
$('#draggable').draggable(borg);
//set property after each time the child offset changes
borg.containment = [
-1 * ($('.child:first').offset().left + $(window).width() * .18), //x1
0,
$(window).width()/2, //x2
0
];
//reinitialize
$('#draggable').draggable(borg);
//would $('#draggable').draggable('destroy').draggable(borg) be better or unnecessary?

Dragging a div out from underneath multiple other divs

I have been trying to create a test case for a webapplication we are making and I am quite close to a solution, but the final part has me stumped...
We need to be able to drag divs around (no problem there), but some of the divs need to be locked in place (again, no problem).
The problem arises when a draggable div is stuck underneath a non draggable div. I have fixed this somewhat, but it only works if there is only ONE non draggable div on top of the draggable one.The moment it overlaps with another non draggable div, it won't work. Which is weird, as I am correctly accessing the draggable div.
my HTML:
<body>
<div id="div1" class="draggable"></div>
<div id="div2" class="locked"></div>
<div id="div3" class="locked"></div>
</body>
And here is my javascript:
<script>
$(document).ready(function () {
$(document).mousemove(function (e) {
window.mouseXPos = e.pageX;
window.mouseYPos = e.pageY;
});
});
$(function () {
$(".draggable").draggable();
});
$('.locked').ready(function () {
$('.locked').mousedown(function (e) {
var layer = e.target;
$("#data").html($("#data").html() + " " + layer.id);
$(layer).addClass("hide");
var lowerLayer = document.elementFromPoint(window.mouseXPos, window.mouseYPos);
if ($(lowerLayer).hasClass("locked")) {
$(lowerLayer).mousedown();
}
else if ($(lowerLayer).hasClass("draggable")) {
$("#data").html($("#data").html() + " " + lowerLayer.id);
$(lowerLayer).trigger(e);
}
$(layer).removeClass("hide");
});
});
</script>
Okay, so the idea is this, I use jquery to set everything with the class "draggable" to be able to be dragged. The body gets a mousemove event to store the current mouse position. While all the elements with the "locked" class, will fire a mousedown event. In this event, I hide the element I am clicking on by adding a class called "hide" (which contains only a display: none).
At this point, the element underneath the clicked element is visible. Using the elementFromPoint combined with my stored mouse positions, I grab the lower element.
By then checking if it is "locked" or "draggable" I can determine what this element should do. If it's locked, I force it to execute a mousedown event. If it's draggable, I use the .trigger(e) to start dragging.
I then remove the "hide" class again, so that the element does not stay hidden.
Using the div called data, I can see that the function does indeed reach the draggable div. However, if both locked divs are on top of it, it will not start dragging. If there is only one locked div on top, I can then start dragging the draggable div.
In my opinion, this should work without any problems. I mean, it works with only 1 overlapping div, and even with 2 (or more), I am still triggering the code in the else if statement.
What am I missing/overlooking?
-Ferdy
I rewrote your script slightly differently and it seems to work. The problem might be that you're using e.target, but i'm not really sure. Here's what i did:
$(".draggable").draggable();
$(".locked").mousedown(function(e) {
var layer = $(this);
layer.addClass("hide");
var lowerLayer = $(document.elementFromPoint(e.pageX, e.pageY));
if (lowerLayer.hasClass("draggable") || lowerLayer.hasClass("locked"))
lowerLayer.trigger(e);
layer.removeClass("hide");
});
http://jsfiddle.net/AXycq/

Categories