Vue draggable with elements that change height upon being dragged - javascript

I have a Vue3 app with vue-draggable and I have a list of sortable cards which possibly contain long text inside. To make dragging easier, I want to hide the text contained in the cards and only show their title when one is being dragged. This makes it easier to drop the card into the right position.
In order to achieve this, the elements which I want to hide inside of the cards while one is being dragged are given a CSS class hidden-while-dragging and the whole collection receives a class dragging while an item is being dragged. This is achieved by setting a boolean variable to the correct value upon receiving the events start and end and conditionally setting the class on the whole <draggable> element. Then I have this CSS rule:
.dragging .hidden-while-dragging {
display: none;
}
This works fine except for one case: if I drag an element and, upon dragging, the height of the parent container changes (due to the disappearing of the content inside of the cards), I am not able to drag the item: it instantly gets dropped in place, and no end event is emitted, so for example the collection keeps the class dragging.
Afterwards, I am able to drop the element once again: the issue doesn't occur this time, because no change in height occurs, and after I drop the element, everything goes back to "normal".
I made this repo in order to have a reproducible example: https://github.com/samul-1/vue-draggable-bug-demo
Here's a codepen as well: https://codepen.io/samul-11/pen/mdjKvZa try and drag the first or last element and you'll see the issue.
You can observe the height of the #app element changing when dragging an element. An interesting thing is that this only happens if dragging the first or third item in my example, not the second. So apparenly the issue is with elements at the edge of the parent.
Is this a bug with the library or is there a way around it?

Related

Move divs with mouseclick between div container

I would like to drag and drop divs within a container-div. Only up and down the list, using the mouse. How can I create this?
Searching the internet and stackoverflow didn't give me a good answer, since I would like to have it in vanilla Javascript and everything is nowadays in jQuery. :S
This is wat I have:
<div class="chapcontainer" data-chaporder="1000">
<div class="chapter">Big fire</div>
<div class="subchapter" data-chapid="1">Forest burning</div>
<div class="subchapter" data-chapid="2">Balu hoses</div>
<div class="subchapter" data-chapid="3">Forest animals die</div>
<div class="subchapter" data-chapid="4">Lovely fire</div>
</div>
</div>
The chapter-div is the container. Within this div I want to be able to move the subchapter-div with a click of the mousebutton up and down the list of subchapter-divs.
For example I move the subchapter-div with data-chapid 4 to the top, then it should be moved to the top and the data-chapid changed to 1. Is something like this possible in vanilla javascript?
I've read about AppendChild, but I don't know how I can trigger this with the mouse.
You could do it with jQuery:
$(".subchapter[data-chapid='1']").on("click", function() {
$(this).css('margin-left', '20px') // add margin/padding/etc here
})
Fiddle
Doing drag and drop yourself is complicated, but here are some of the basic steps. One advantage of rolling your own is you can do more advanced things than your standard jquery ui drag and drop can, like smooth scrolling the container, auto-opening nested tree items if the user hovers over them, or doing animations while moving items.
Steps:
Listen for mousedown events on your items in your container. When the user presses down on an item, store the mouse's offset relative to the top of container (this includes the scroll of the container). Also store the mouse's offset relative to the item being dragged.
Clone the element that's being dragged and absolute position it under the mouse, using the relative offset you stored in step 1.
Make the original element invisible.
Listen for mousemove events on your container. Reposition the dragged item appropriately.
As the mouse moves, you'll need to update the mouse's new offset from the top (including scroll) and figure out which item you're over. You can do this by getting the height of each item in your list. So if your mouse is 710 pixels from the top, and each item is 100 pixels high, you're over index 8. You can use this info to change the style of the item you're over to show that it's a drop target. One thing to watch out for is if you do any styling changes that change the heights of items, you'll need to recalculate your heights.
On mouseup, you already know which item you're over, so now you need to update your data array to move the dragged item, probably using array.splice. Delete the absolutely positioned dragged element, and rerender your list to reflect your changes. I'm assuming you're using some sort of templating library to render your stuff, or at least a render function that clears what's there and replaces it with the current state.

Drag and Drop Element into a Container fails with too many elements

I'm trying to use Drag and Drop to move <li> elements from one <ul> to another <ul>. I've got it working up to a point. My problem is I can't "drop" a <li> element when it's over another <li> element in my <ul> container. So when I fill up the visible portion of my container I can no longer drag and drop.
This CodePen demonstrates my problem.
not sure exactly what you are trying to achieve, if you want the drop target
to enlarge to accomodate, then be sure you set the CSS style on the target
with a text size using px definitions instead of pt specs.
then each time a drop occurs add to your drop routine to increase the size
of the drop target by the pixel height of 1 line of text. so it grows by 1 on
every drop, or the height is equal to lineHeight x (numElements + 1)
always leaving you room for 1 more drop
if you are trying to maintain a fixed size of the drop target, ie, you dont want
to move other elements down the page, take the above mentioned drop target
and put it inside a fixed size div with scrollable properties.
that way you have a drop element that can grow as described above,
but the fixed div constrains the screen real estate so the drop target doesnt
take up more space than you want
I solved my issue. I had to comment out an if statement in my handleDragOver function that was checking if the event.target was the dropzone element. I also changed my handleDrop function to use event.currentTarget instead of event.target

selecting an element on mouse click overlapped by another transparent div

I am working on a dashboard where user can drag and drop elements to create html pages.Now,he can have multiple images using an image component.We have managed to calculate the z-index of the images and they can be adjusted using up-down keys.
Issue:
The issue we are facing is when we select a image component we attach a dotted layer above it for helping the user to easily drag and resize it.If the user places the images as shown in the image below
we are not able to select the inner image again because the z-index of the selection div(the one with the blue dots) is(has to be) the highest(highest bcoz we have to use it for all components).So if I try to select the inner image now it cannot be selected.How can I handle the situation? For reference it works on this site as expected.
I believe we have get the element under the parent when it is clicked.But not sure how!We are using javascript,jquery to handle the events.
You can use JavaScript or jQuery to get the position of the inner image, and when the user clicks on the outer image, check to see whether the mouse position lies within the range of the smaller image. The range can be calculated with the position, width, and height of the inner element.
To get the element's position: use jQuery .offset() or .position() (The former is relative to the document, the latter to the parent).
To get the mouse position: http://docs.jquery.com/Tutorials:Mouse_Position
You could consider hiding the masking element quickly in order to gather the coordinate for your underlying element, when done, you could re enable visibility for the masking element. Use document.elementFromPoint() in order to get the DOM item from mouse coordinate.
An example:
http://jsfiddle.net/s94cnckm/14/
Alternatively you can use The CSS property pointer-events: none; on the masking element.
Related:
https://developer.mozilla.org/de/docs/Web/CSS/pointer-events
How to detecting a click under an overlapping element?

Apply gradient over page without hindering user interaction [duplicate]

I have a div that has background:transparent, along with border. Underneath this div, I have more elements.
Currently, I'm able to click the underlying elements when I click outside of the overlay div. However, I'm unable to click the underlying elements when clicking directly on the overlay div.
I want to be able to click through this div so that I can click on the underlying elements.
Yes, you CAN do this.
Using pointer-events: none along with CSS conditional statements for IE11 (does not work in IE10 or below), you can get a cross browser compatible solution for this problem.
Using AlphaImageLoader, you can even put transparent .PNG/.GIFs in the overlay div and have clicks flow through to elements underneath.
CSS:
pointer-events: none;
background: url('your_transparent.png');
IE11 conditional:
filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='your_transparent.png', sizingMethod='scale');
background: none !important;
Here is a basic example page with all the code.
Yes, you CAN force overlapping layers to pass through (ignore) click events.
PLUS you CAN have specific children excluded from this behavior...
You can do this, using pointer-events
pointer-events influences the reaction to click-, tap-, scroll- und hover events.
In a layer that should ignore / pass-through mentioned events you set
pointer-events: none;
Children of that unresponsive layer that need to react mouse / tap events again need:
pointer-events: auto;
That second part is very helpful if you work with multiple overlapping div layers (probably some parents being transparent), where you need to be able to click on child elements and only that child elements.
Example usage:
.parent {
pointer-events:none;
}
.child {
pointer-events:auto;
}
<div class="parent">
I'm unresponsive
I'm clickable again, wohoo !
</div>
Allowing the user to click through a div to the underlying element depends on the browser. All modern browsers, including Firefox, Chrome, Safari, and Opera, understand pointer-events:none.
For IE, it depends on the background. If the background is transparent, clickthrough works without you needing to do anything. On the other hand, for something like background:white; opacity:0; filter:Alpha(opacity=0);, IE needs manual event forwarding.
See a JSFiddle test and CanIUse pointer events.
I'm adding this answer because I didn’t see it here in full. I was able to do this using elementFromPoint. So basically:
attach a click to the div you want to be clicked through
hide it
determine what element the pointer is on
fire the click on the element there.
var range-selector= $("")
.css("position", "absolute").addClass("range-selector")
.appendTo("")
.click(function(e) {
_range-selector.hide();
$(document.elementFromPoint(e.clientX,e.clientY)).trigger("click");
});
In my case the overlaying div is absolutely positioned—I am not sure if this makes a difference. This works on IE8/9, Safari Chrome and Firefox at least.
Hide overlaying the element
Determine cursor coordinates
Get element on those coordinates
Trigger click on element
Show overlaying element again
$('#elementontop').click(e => {
$('#elementontop').hide();
$(document.elementFromPoint(e.clientX, e.clientY)).trigger("click");
$('#elementontop').show();
});
I needed to do this and decided to take this route:
$('.overlay').click(function(e){
var left = $(window).scrollLeft();
var top = $(window).scrollTop();
//hide the overlay for now so the document can find the underlying elements
$(this).css('display','none');
//use the current scroll position to deduct from the click position
$(document.elementFromPoint(e.pageX-left, e.pageY-top)).click();
//show the overlay again
$(this).css('display','block');
});
I currently work with canvas speech balloons. But because the balloon with the pointer is wrapped in a div, some links under it aren't click able anymore. I cant use extjs in this case.
See basic example for my speech balloon tutorial requires HTML5
So I decided to collect all link coordinates from inside the balloons in an array.
var clickarray=[];
function getcoo(thatdiv){
thatdiv.find(".link").each(function(){
var offset=$(this).offset();
clickarray.unshift([(offset.left),
(offset.top),
(offset.left+$(this).width()),
(offset.top+$(this).height()),
($(this).attr('name')),
1]);
});
}
I call this function on each (new) balloon. It grabs the coordinates of the left/top and right/down corners of a link.class - additionally the name attribute for what to do if someone clicks in that coordinates and I loved to set a 1 which means that it wasn't clicked jet. And unshift this array to the clickarray. You could use push too.
To work with that array:
$("body").click(function(event){
event.preventDefault();//if it is a a-tag
var x=event.pageX;
var y=event.pageY;
var job="";
for(var i in clickarray){
if(x>=clickarray[i][0] && x<=clickarray[i][2] && y>=clickarray[i][1] && y<=clickarray[i][3] && clickarray[i][5]==1){
job=clickarray[i][4];
clickarray[i][5]=0;//set to allready clicked
break;
}
}
if(job.length>0){
// --do some thing with the job --
}
});
This function proofs the coordinates of a body click event or whether it was already clicked and returns the name attribute. I think it is not necessary to go deeper, but you see it is not that complicate.
Hope in was enlish...
Another idea to try (situationally) would be to:
Put the content you want in a div;
Put the non-clicking overlay over the entire page with a z-index higher,
make another cropped copy of the original div
overlay and abs position the copy div in the same place as the original content you want to be clickable with an even higher z-index?
Any thoughts?
I think the event.stopPropagation(); should be mentioned here as well. Add this to the Click function of your button.
Prevents the event from bubbling up the DOM tree, preventing any parent handlers from being notified of the event.
Just wrap a tag around all the HTML extract, for example
<a href="/categories/1">
<img alt="test1" class="img-responsive" src="/assets/photo.jpg" />
<div class="caption bg-orange">
<h2>
test1
</h2>
</div>
</a>
in my example my caption class has hover effects, that with pointer-events:none; you just will lose
wrapping the content will keep your hover effects and you can click in all the picture, div included, regards!
An easier way would be to inline the transparent background image using Data URIs as follows:
.click-through {
pointer-events: none;
background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);
}
I think that you can consider changing your markup. If I am not wrong, you'd like to put an invisible layer above the document and your invisible markup may be preceding your document image (is this correct?).
Instead, I propose that you put the invisible right after the document image but changing the position to absolute.
Notice that you need a parent element to have position: relative and then you will be able to use this idea. Otherwise your absolute layer will be placed just in the top left corner.
An absolute position element is positioned relative to the first parent
element that has a position other than static.
If no such element is found, the containing block is html
Hope this helps. See here for more information about CSS positioning.
You can place an AP overlay like...
#overlay {
position: absolute;
top: -79px;
left: -60px;
height: 80px;
width: 380px;
z-index: 2;
background: url(fake.gif);
}
<div id="overlay"></div>
just put it over where you dont want ie cliked. Works in all.
This is not a precise answer for the question but may help in finding a workaround for it.
I had an image I was hiding on page load and displaying when waiting on an AJAX call then hiding again however...
I found the only way to display my image when loading the page then make it disappear and be able to click things where the image was located before hiding it was to put the image into a DIV, make the size of the DIV 10x10 pixels or small enough to prevent it causing an issue then hiding the containing div. This allowed the image to overflow the div while visible and when the div was hidden, only the divs area was affected by inability to click objects beneath and not the whole size of the image the DIV contained and was displaying.
I tried all the methods to hide the image including CSS display=none/block, opacity=0, hiding the image with hidden=true. All of them resulted in my image being hidden but the area where it was displayed to act like there was a cover over the stuff underneath so clicks and so on wouldn't act on the underlying objects. Once the image was inside a tiny DIV and I hid the tiny DIV, the entire area occupied by the image was clear and only the tiny area under the DIV I hid was affected but as I made it small enough (10x10 pixels), the issue was fixed (sort of).
I found this to be a dirty workaround for what should be a simple issue but I was not able to find any way to hide the object in its native format without a container. My object was in the form of etc. If anyone has a better way, please let me know.
I couldn't always use pointer-events: none in my scenario, because I wanted both the overlay and the underlying element(s) to be clickable / selectable.
The DOM structure looked like this:
<div id="outerElement">
<div id="canvas-wrapper">
<canvas id="overlay"></canvas>
</div>
<!-- Omitted: element(s) behind canvas that should still be selectable -->
</div>
(The outerElement, canvas-wrapper and canvas elements have the same size.)
To make the elements behind the canvas act normally (e.g. selectable, editable), I used the following code:
canvasWrapper.style.pointerEvents = 'none';
outerElement.addEventListener('mousedown', event => {
const clickedOnElementInCanvas = yourCheck // TODO: check if the event *would* click a canvas element.
if (!clickedOnElementInCanvas) {
// if necessary, add logic to deselect your canvas elements ...
wrapper.style.pointerEvents = 'none';
return true;
}
// Check if we emitted the event ourselves (avoid endless loop)
if (event.isTrusted) {
// Manually forward element to the canvas
const mouseEvent = new MouseEvent(event.type, event);
canvas.dispatchEvent(mouseEvent);
mouseEvent.stopPropagation();
}
return true;
});
Some canvas objects also came with input fields, so I had to allow keyboard events, too.
To do this, I had to update the pointerEvents property based on whether a canvas input field was currently focused or not:
onCanvasModified(canvas, () => {
const inputFieldInCanvasActive = // TODO: Check if an input field of the canvas is active.
wrapper.style.pointerEvents = inputFieldInCanvasActive ? 'auto' : 'none';
});
it doesn't work that way. the work around is to manually check the coordinates of the mouse click against the area occupied by each element.
area occupied by an element can found found by 1. getting the location of the element with respect to the top left of the page, and 2. the width and the height. a library like jQuery makes this pretty simple, although it can be done in plain js. adding an event handler for mousemove on the document object will provide continuous updates of the mouse position from the top and left of the page. deciding if the mouse is over any given object consists of checking if the mouse position is between the left, right, top and bottom edges of an element.
Nope, you can't click ‘through’ an element. You can get the co-ordinates of the click and try to work out what element was underneath the clicked element, but this is really tedious for browsers that don't have document.elementFromPoint. Then you still have to emulate the default action of clicking, which isn't necessarily trivial depending on what elements you have under there.
Since you've got a fully-transparent window area, you'll probably be better off implementing it as separate border elements around the outside, leaving the centre area free of obstruction so you can really just click straight through.

animating div elements left to right and back

I have a menu system made up of divs and i want to animate the left property to slide each time the user mouses over a menu item but i need the outer div(which is black) element to expand as the menu items move left to right also I want the div element(.container) to slide back and contract the outer div element(this black div which is 0 width) I have a basic example done in jsFiddle it olny moves the elements to the left
Having a little trouble fully understanding, but is this sort of what you mean?
http://jsfiddle.net/V3RHr/2/
If I could rewrite your html a bit, I would put make each .menu-item into an unordered list.
When you mouseenter the unordered list, you expand the second container. Inside that mouseenter function, I would have a second event when you mouseenter a list item, you populate the second container and stopPropogation.
You could probably still do it with a mouseenter on the first container, and another mouseenter on the div.menu-item, but your first container has extra height and width.
You should be able to fix the left is null issue by having the code not execute on the last .content, like this:
$('.container').not(':last').find('.menu-item').mouseenter(function () {
This will not apply to the menu-items within the green box.
For the re-show issue, I would change the way you are showing. Instead of sliding the box out from behind the other, you can position it where you want it to end up and hide it, then you can use:
.animate({width: 'show'})
Which will give a similar sliding effect.
That, or do it similar to my response to your other question, only without the collapsing I had previously:
http://jsfiddle.net/V3RHr/3/

Categories

Resources