简体   繁体   中英

JQuery: z-index ordering with draggable divs

In the following code I have:

  1. divs that pop up when you click the corresponding buttons
  2. if you drag them around, the (last) active object should be on top

Currently my code doesn't work as expected where the last active element is bought to the front. I am trying to create an extensible solution where there can be about five or so more buttons later with different content.

Also, is there a way to let the divs pop up near to the buttons that they are shown from? If I drag the button before I clicked it, the distance is a bit big.

My current attempt looks like this:

 $(function() { /* Combine on ready logic into one place */ $("#button-one").draggable({ stack: 'div', containment: "body" }); $("#content-one").draggable({ stack: 'div', containment: "body" }); /* Hide all trip elements except for first */ $('.trip', '#content-one').not(':first').hide(); }); $('#button-one').on('mouseup', function() { if (!$(this).hasClass('ui-draggable-dragging')) { $("#content-one").toggle(); } }); $('#content-one').on('mouseup', function() { /* Reuse same logic in #button mouseup handler */ if (!$(this).hasClass('ui-draggable-dragging')) { /* If content element if not dragging, treat mouse up as conclusion of click event and rotate visibility of trip elements like this */ let trip = $('.trip:visible', '#content-one'); let next = trip.next().length === 0 ? $('.trip:first', '#content-one') : trip.next(); trip.hide(); next.show(); } }); $(function() { $("#button-two").draggable({ stack: 'div', containment: "body" }); }); $('#button-two').on('mouseup', function() { if (!$(this).hasClass('ui-draggable-dragging')) { // your click function $("#content-two").toggle(); } }); $(function() { $("#content-two").draggable({ stack: 'div', containment: "body" }); }); 
 body, html { position: absolute; padding: 0; margin: 0; width: 100%; overflow-x: hidden; overflow-y: inherit; cursor: default; } #content { max-width: 100vw; height: 150vh; } #one { left: 5%; top: 5%; position: absolute; } #button-one { width: 100px; height: 100px; background-color: cyan; position: absolute; } #content-one { display: none; cursor: all-scroll; top: 10%; left: 10%; position: absolute; top: 20px; left: 20px; } .trip { width: 200px; height: 200px; background-color: blue; color: white; } #two { left: 15%; top: 15%; position: absolute; } #button-two { width: 100px; height: 100px; background-color: darkgrey; position: absolute; } #content-two { display: none; cursor: all-scroll; position: absolute; top: 20px; left: 20px; width: 200px; height: 200px; background-color: green; color: white; } 
 <div id="content"> <div id="one"> <div id="button-one">Button 1</div> <div id="content-one"> <div class="trip">div 1</div> <div class="trip">div 2</div> <div class="trip">div 3</div> </div> </div> <div id="two"> <div id="button-two">Button 2</div> <div id="content-two">Hey</div> </div> </div> <script src="https://code.jquery.com/jquery-1.7.2.min.js"></script> <script src="https://code.jquery.com/ui/1.8.21/jquery-ui.min.js"></script> <script src="https://raw.githubusercontent.com/furf/jquery-ui-touch-punch/master/jquery.ui.touch-punch.min.js"></script> 

Thank you for your help! :)

Based on this answer of mine to your previous question - with some modifications I'd do it like:

  • Use data-* attribute to store the target content selector ID
  • Use Event.clientX / Y - to get the click coordinates
  • Use .css({left: X, top: Y}) to place your content
  • Place all your elements inside the same parent to account for the problematic z-indexing and help stack: '.draggable', do its job

 jQuery($ => { const $drag = $('.draggable').draggable({ stack: '.draggable', containment: 'body' }); // Fix out-of-containment glitch $($drag.draggable('option').containment).on('mouseleave', () => $drag.trigger('mouseup')); let z = 10; // Contents zIndex (will increment on content toggle) $('.button').on('click', function(ev) { if ($(this).hasClass('ui-draggable-dragging')) return; $($(this).data('content')).css({left: ev.clientX, top: ev.clientY, zIndex: ++z}).toggle(); }); $('.content').on('click', function() { const $trip = $(this).find('.trip'), tripL = $trip.length; this._count |= 0; $trip.eq(++this._count % tripL).show().siblings($trip).hide(); }); }); 
 html, body { height: 100%; margin: 0; } body { background-color: grey; } .button { width: 30vh; height: 30vh; background-color: cyan; } .content { width: 45vh; height: 45vh; display: none; cursor: all-scroll; position: absolute; background-color: blue; color: white; } .trip~.trip { display: none; } [data-content="#content-two"] {background: aquamarine;} #content-two {background: fuchsia;} 
 <div class="button draggable" data-content="#content-one">Toggle one</div> <div class="content draggable" id="content-one"> <div class="trip">ONE 1</div> <div class="trip">ONE 2</div> <div class="trip">ONE 3</div> </div> <div class="button draggable" data-content="#content-two">Toggle two</div> <div class="content draggable" id="content-two"> <div class="trip">TWO 1</div> <div class="trip">TWO 2</div> </div> <script src="https://code.jquery.com/jquery-1.7.2.min.js"></script> <script src="https://code.jquery.com/ui/1.8.21/jquery-ui.min.js"></script> <script src="https://raw.githubusercontent.com/furf/jquery-ui-touch-punch/master/jquery.ui.touch-punch.min.js"></script> 

To achieve the order behaviour you're after, I would suggest some changes to your HTML structure. What you want to aim for is a "flat" HTML structure so that draggable elements (ie those that are currently nested in #one and #two ) can be z-ordered relative to one another (ie in the same containing element/DIV).

With that done, you could then use the start event hook on draggable() to set element that's "currently on top". A simple way to do that would be to introduce a CSS class that sets the z-index property.

To position the draggable near the button being clicked, you can use JQuerys position() method to get the top/left coordinates of the button being clicked. You can then pass those directly to the css() method of the draggable element like this:

  /* Position of this button */
  const position = $(buttonElement).position();

  /* Position target near the button with slight offset */
  draggableElement.css({
    left : position.left + 10,
    top : position.top + 10
  });  

The following revisions to your code should do the trick:

 /* Helper function assigns the element as "top most" element relative to other elements */ function setToTop(element) { var zIndexMax = 0; $('.content, .button').each(function(i, elem) { var zIndex = Number.parseInt($(elem).css('z-index')); zIndex = Number.isNaN(zIndex) ? 0 : zIndex; zIndexMax = Math.max(zIndexMax, zIndex); }); element.css({ zIndex: zIndexMax + 1 }); } $(function() { /* Prevent drag from continuing after mouse leaves document */ $(document).mouseleave(function() { $(document).trigger("mouseup") }); $(".content, .button").draggable({ helper: "original", containment: "body", start: function(event, ui) { /* ui.helper is the element that we're starting to drag */ setToTop(ui.helper); } }); $('.trip').not(':first').hide(); }); $('.button').on('mouseup', function() { /* Cause clicked button to come to front */ setToTop($(this)); if (!$(this).hasClass('ui-draggable-dragging')) { var toggleTarget = $(".content" + $(this).data("target")); toggleTarget.toggle(); if (toggleTarget.is(':visible')) { /* Cause newly toggled element to be visible on top */ setToTop(toggleTarget); /* Position of this button */ const position = $(this).position(); /* Position target near the button with slight offset */ toggleTarget.css({ left: position.left + 10, top: position.top + 10 }); } } }); $('.content').on('mouseup', function() { if (!$(this).hasClass('ui-draggable-dragging')) { let trip = $('.trip:visible', this); let next = trip.next().length === 0 ? $('.trip:first', this) : trip.next(); trip.hide(); next.show(); } }); 
 body, html { position: absolute; padding: 0; margin: 0; width: 100%; overflow-x: hidden; overflow-y: inherit; cursor: default; } #content { max-width: 100vw; height: 150vh; } .content { display: none; cursor: all-scroll; position: absolute; top: 20px; left: 20px; width: 200px; height: 200px; background-color: green; color: white; } .content.one { top: 10%; left: 10%; } .content.two { background-color: green; color: white; } .trip { width: 200px; height: 200px; background-color: blue; color: white; } .button { width: 100px; height: 100px; position: absolute; } #two { left: 15%; top: 15%; background-color: darkgrey; } #one { left: 5%; top: 5%; background-color: cyan; } 
 <div id="content"> <div id="one" class="button" data-target=".one">Button 1</div> <div id="two" class="button" data-target=".two">Button 2</div> <div class="content two">Hey</div> <div class="content one"> <div class="trip">div 1</div> <div class="trip">div 2</div> <div class="trip">div 3</div> </div> </div> <script src="https://code.jquery.com/jquery-1.7.2.min.js"></script> <script src="https://code.jquery.com/ui/1.8.21/jquery-ui.min.js"></script> <script src="https://raw.githubusercontent.com/furf/jquery-ui-touch-punch/master/jquery.ui.touch-punch.min.js"></script> 

Update

To prevent glitchcy drag behaviour continuing after the mouse leaves the document during a drag you can do this:

/* 
  Helper function assigns the element as "top most" element
  relative to other elements
*/
function setToTop(element) {

  var zIndexMax = 0;

  $('.content, .button').each(function(i, elem) {

    var zIndex = Number.parseInt($(elem).css('z-index'));        
    zIndex = Number.isNaN(zIndex) ? 0 : zIndex;
    zIndexMax = Math.max(zIndexMax, zIndex);     
  });

  element.css({
    zIndex: zIndexMax + 1
  });
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM