Contain draggable element based on offset of one of its children - javascript

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?

Related

How to set scrollTop properly when scrolling with keys in div to keep selected item at same position

I'm trying to create my own input+dropdown control from scratch (in vue.js, though not relevant). I want to use mouse or keyboard for scrolling down the list of items.
For that I'm using a div with a fixed height and overflow-y and in that div, for each item, I use another div. When scrolling with the keyboard, I keep track of the selected item and use that to set the scrollTop position of the div, so that the scroll bar moves with the keyboard input and the selected item stays visible in the middle of the div. Here is the sample in fiddle https://jsfiddle.net/ce6k2a3j/11/
But the part I'm having issues with is setting the .scrollTop property when there are a lot of items in the list and there is some kind of scaling.
setScrollPosition () {
if(+this.keyIndex >= 6){
this.$refs.testMainDiv.scrollTop = (+this.keyIndex - 6) * +this.$refs.testItemDiv[+this.keyIndex].clientHeight;
}
else{
this.$refs.testMainDiv.scrollTop = 0;
}
},
My problem is that, in Windows 10, if I change the scaling of my display to 125% (since I use a 4k monitor), scrolling all the way down the list will move the selected item slightly up each time the key.down fires. Is there a way to make this scale proof ? It also happens when using page zoom.
this is what happens:
In case of transforms, the offsetWidth and offsetHeight returns the element's layout width and height, while getBoundingClientRect() returns the rendering width and height.
MDN: Determining the dimensions of elements
So, in this case clientHeight has the same result as offsetHeight. You neeed to use getBoundingClientRect().
setScrollPosition () {
if(+this.keyIndex >= 6){
this.$refs.testMainDiv.scrollTop = (+this.keyIndex - 6) * this.$refs.testItemDiv[+this.keyIndex].getBoundingClientRect().height;
}
else{
this.$refs.testMainDiv.scrollTop = 0;
}
}
I tested it with the browser scaling and it works, you try it with your display scaling and let's see.

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();

interact.js dragged item move on top

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);
...
}

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

how to adjust the width of all the right or left elements at the same time?

I have some divs(sibblings) and on drag and resize of one of them, according to wether its being resized to west or east i would like to either increase or decrease all the other sibblings widths accordingly.
$(function () {
var west;
var east;
var clientX;
$("#resizable1").resizable({
start: function (event, ui) {
west = $(event.srcElement).hasClass('ui-resizable-w');
east = $(event.srcElement).hasClass('ui-resizable-e');
clientX = event.clientX;
},
resize: function (event, ui) {
if (east) {
//decrease one by one --?
$(this).nextAll().css('width', --? );
}
}
});
});
Set width to:
if (east) {
//decrease one by one
var newWidth = ($('#resizable1').width() - $(this).outerWidth(true)) / $(this).nextAll().length
...
}
EDIT:
How do the 3rd and 4rd sibling react when you resize the second one?
When calculating the remaining space, I only took the first one into account... if they fill the remaining space, yout must add up all outerWidths from the previous sibling instead of just taking $(this).outerWidth(true).
btw, the above code is not tested, just a suggestion to get remaining space to fill.
Make them all share the same width variable, decrease or increase the variable to affect all the div's at the same time.
Either by manually setting every divs size by calling a function and using the same variable as parameter for all of the calls.
Or make the divs share a css class and call the resize function on all the html elements sharing that css class.
EDIT:
Ah i completely miss understood you...
Make all the divs sizes relative and floating, then they should fill their parent.

Categories