简体   繁体   中英

Check if two or more DOM elements overlap

Good day people!

I have run into an issue with my simple single day calendar script.

I've been tasked to create a single day calendar, which shows each block hour from 9am to 6pm. If an event overlaps another, they should equal the same width and not overlap. I have managed to achieve this for two events, however if more than two overlap, things go abit south, I need help figuring out a method to fix this where any number of events overlap, their widths will equal the same.

Events are rendered on the calendar using a global function:

renderDay([{start: 30, end: 120},{start: 60, end: 120}])

which takes an array of objects as an argument, where the integers are the number of minutes pasted from 9am. eg. 30 is 9:30am, 120 is 11am

here is the collision function I took from stackoverflow

// collision function to return boolean
// attribute: http://stackoverflow.com/questions/14012766/detecting-whether-two-divs-overlap
function collision($div1, $div2) {
  let x1 = $div1.offset().left;
  let y1 = $div1.offset().top;
  let h1 = $div1.outerHeight(true);
  let w1 = $div1.outerWidth(true);
  let b1 = y1 + h1;
  let r1 = x1 + w1;
  let x2 = $div2.offset().left;
  let y2 = $div2.offset().top;
  let h2 = $div2.outerHeight(true);
  let w2 = $div2.outerWidth(true);
  let b2 = y2 + h2;
  let r2 = x2 + w2;

  if (b1 < y2 || y1 > b2 || r1 < x2 || x1 > r2) return false;
  return true;
}

I run a loop on all the event divs which I want to check for overlaps

// JQuery on each, check if collision
$('.event').each(function(index, value) {

      // statement to break out on final loop
      if(index === $('.event').length - 1) return;

      console.log('at index: ', index);

      // if collison === true, halve width of both event divs, re-position
      if(collision(  $('#e-'+index) , $('#e-'+(index + 1)) )) {

        $('#e-'+index).css('width', $('#e-'+index).width() / 2);
        $('#e-'+(index+ 1)).css('width', $('#e-'+(index+ 1)).width() / 2).css('left', $('#e-'+(index + 1)).width());

        if(collision)
      }
    })
  }
})

Screenshots to help visualize :)

When two overlap, they have equal widths

When three or more overlap, things go wrong

Any help would be greatly appreciated! DW

After looking at the code, it seems overly complex to check the rendered elements for collision when you can work this out from the start and end times.

The way I've done it, is to group events which collide in arrays like so:

let collisions = [
      // only 1 event in this array so no collisions
      [{
        start: 30,
        end: 120
      }],
      // 3 events in this array which have overlapping times
      [{
        start: 300,
        end: 330
      }, {
        start: 290,
        end: 330
      }, {
        start: 300,
        end: 330
      }]
    ];

Then we iterate through each group of collisions, and create the elements with the appropriate width and positioning.

for (var i = 0; i < collisions.length; i++) {
  var collision = collisions[i];

  for (var j = 0; j < collision.length; j++) {
    var event = collision[j];

    let height = event.end - event.start;
    let top = event.start + 50;

    // 360 = max width of event
    let width = 360 / collision.length;

    // a lot of this could be moved into a css class
    // I removed the "display: inline-block" code because these are absolutely positioned. Replaced it with "left: (j * width)px"
    let div = $(`<div id=${'e-'+ (i + j)}>`).css('position', 'absolute').css('top', top)
    .css('height', height).css('width', width).css('left', (j * width) + 'px')
    .css('backgroundColor', arrayOfColors.shift()).addClass('event')
    .text('New Event').css('fontWeight', 'bold');

    // append event div to parent container
    $('#events').append(div);
  }
}

 //********************************************************************** // // TITLE - Thought Machine Coding Challenge, Single Day Calendar // AUTHOR - DOUGLAS WISSETT WALKER // DATE - 21/04/2016 // VERSION - 0.0.3 // PREVIOUS - 0.0.2 // //********************************************************************** let arr = [{ start: 30, end: 120 }, { start: 70, end: 180 }, { start: 80, end: 190 }, { start: 300, end: 330 }, { start: 290, end: 330 }, { start: 220, end: 260 }, { start: 220, end: 260 }, { start: 220, end: 260 }, { start: 220, end: 260 }, { start: 400, end: 440 }, { start: 20, end: 200 }]; let renderDay; $(document).ready(() => { renderDay = function(array) { $('.event').each(function(i, el) { $(el).remove(); }); // background colors for events let arrayOfColors = [ 'rgba(255, 153, 153, 0.75)', 'rgba(255, 204, 153, 0.75)', 'rgba(204, 255, 153, 0.75)', 'rgba(153, 255, 255, 0.75)', 'rgba(153, 153, 255, 0.75)', 'rgba(255, 153, 255, 0.75)' ] let collisions = mapCollisions(array); let eventCount = 0; // used for unique id for (let i = 0; i < collisions.length; i++) { let collision = collisions[i]; for (let j = 0; j < collision.length; j++) { let event = collision[j]; let height = event.end - event.start; let top = event.start + 50; // 360 = max width of event let width = 360 / collision.length; // a lot of this could be moved into a css class // I removed the "display: inline-block" code because these are absolutely positioned // Replaced it with "left: (j * width)px" let div = $("<div id='e-" + eventCount + "'>").css('position', 'absolute').css('top', top) .css('height', height).css('width', width).css('left', (j * width) + 'px') .css('backgroundColor', arrayOfColors.shift()).addClass('event') .text('New Event').css('fontWeight', 'bold'); eventCount++; // append event div to parent container $('#events').append(div); } } } renderDay(arr); }); // Sorry this is pretty messy and I'm not familiar with ES6/Typescript or whatever you are using function mapCollisions(array) { let collisions = []; for (let i = 0; i < array.length; i++) { let event = array[i]; let collides = false; // for each group of colliding events, check if this event collides for (let j = 0; j < collisions.length; j++) { let collision = collisions[j]; // for each event in a group of colliding events for (let k = 0; k < collision.length; k++) { let collidingEvent = collision[k]; // event which possibly collides // Not 100% sure if this will catch all collisions if ( event.start >= collidingEvent.start && event.start < collidingEvent.end || event.end <= collidingEvent.end && event.end > collidingEvent.start || collidingEvent.start >= event.start && collidingEvent.start < event.end || collidingEvent.end <= event.end && collidingEvent.end > event.start) { collision.push(event); collides = true; break; } } } if (!collides) { collisions.push([event]); } } console.log(collisions); return collisions; } 
 html, body { margin: 0; padding: 0; font-family: sans-serif; } #container { height: 100%; width: 100%; } #header-title { text-align: center; } #calendar { width: 400px; height: 620px; margin-top: 70px; } #events { position: absolute; top: 80px; left: 100px; width: 800px; height: 620px; } .event { box-shadow: 0 0 20px black; border-radius: 5px; } .hr-block { border-top: 2px solid black; height: 58px; margin: 0; padding: 0; margin-left: 100px; min-width: 360px; opacity: .5; } .hr-header { position: relative; top: -33px; left: -68px; } 
 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="css/styles.css"> <link rel="stylesheet" href="css/responsive.css"> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script> <script charset="UTF-8" src="js/moment.js"></script> <script charset="UTF-8" src="js/script2.js"></script> <title>Thought Machine Code Challenge</title> </head> <body> <div id="container"> <div class="header"> <h1 id="header-title"></h1> </div> <div id="calendar"> <div class="hr-block"> <h2 class="hr-header">09:00</h2> </div> <div class="hr-block"> <h2 class="hr-header">10:00</h2> </div> <div class="hr-block"> <h2 class="hr-header">11:00</h2> </div> <div class="hr-block"> <h2 class="hr-header">12:00</h2> </div> <div class="hr-block"> <h2 class="hr-header">13:00</h2> </div> <div class="hr-block"> <h2 class="hr-header">14:00</h2> </div> <div class="hr-block"> <h2 class="hr-header">15:00</h2> </div> <div class="hr-block"> <h2 class="hr-header">16:00</h2> </div> <div class="hr-block"> <h2 class="hr-header">17:00</h2> </div> <div class="hr-block"> <h2 class="hr-header">18:00</h2> </div> </div> </div> <div id="events"> </div> <script> document.getElementById("header-title").innerHTML = moment().calendar(); </script> </body> </html> 

working index, script and css files

 //********************************************************************** // // TITLE - Thought Machine Coding Challenge, Single Day Calendar // AUTHOR - DOUGLAS WISSETT WALKER // DATE - 21/04/2016 // VERSION - 0.0.3 // PREVIOUS - 0.0.2 // //********************************************************************** let arr = [{start: 30, end: 120},{start: 300, end: 330},{start: 290, end: 330}]; let renderDay; $(document).ready(() => { renderDay = function(array) { $('.event').each(function(i, el) { $(el).remove(); }); // background colors for events let arrayOfColors = [ 'rgba(255, 153, 153, 0.75)', 'rgba(255, 204, 153, 0.75)', 'rgba(204, 255, 153, 0.75)', 'rgba(153, 255, 255, 0.75)', 'rgba(153, 153, 255, 0.75)', 'rgba(255, 153, 255, 0.75)' ] // iterate through each event time array.forEach((eventTimes, index) => { // define event height and top position on calendar let height = eventTimes.end - eventTimes.start; let top = eventTimes.start + 50; // max width of event let width = 360; // create event div let div = $(`<div id=${'e-'+index}>`).css('position', 'absolute').css('top', top) .css('height', height).css('width', width).css('display', 'inline-block') .css('backgroundColor', arrayOfColors.shift()).addClass('event') .text('New Event').css('fontWeight', 'bold'); // append event div to parent container $('#events').append(div); }) // JQuery on each, check if collision $('.event').each(function(index, value) { // statement to break out on final loop if(index === $('.event').length - 1) return; console.log('at index: ', index); // if collison === true, halve width of both event divs, re-position if(collision( $('#e-'+index) , $('#e-'+(index + 1)) )) { $('#e-'+index).css('width', $('#e-'+index).width() / 2); $('#e-'+(index+ 1)).css('width', $('#e-'+(index+ 1)).width() / 2).css('left', $('#e-'+(index + 1)).width()); } }) } }) // collision function to return boolean // attribute: http://stackoverflow.com/questions/14012766/detecting-whether-two-divs-overlap function collision($div1, $div2) { let x1 = $div1.offset().left; let y1 = $div1.offset().top; let h1 = $div1.outerHeight(true); let w1 = $div1.outerWidth(true); let b1 = y1 + h1; let r1 = x1 + w1; let x2 = $div2.offset().left; let y2 = $div2.offset().top; let h2 = $div2.outerHeight(true); let w2 = $div2.outerWidth(true); let b2 = y2 + h2; let r2 = x2 + w2; if (b1 < y2 || y1 > b2 || r1 < x2 || x1 > r2) return false; return true; } // render events using renderDay(arr) in console 
 html, body { margin: 0; padding: 0; font-family: sans-serif; } #container { height: 100%; width: 100%; } #header-title { text-align: center; } #calendar { width: 400px; height: 620px; margin-top: 70px; } #events { position: absolute; top: 80px; left: 100px; width: 800px; height: 620px; } .event { box-shadow: 0 0 20px black; border-radius: 5px; } .hr-block { border-top: 2px solid black; height: 58px; margin: 0; padding: 0; margin-left: 100px; min-width: 360px; opacity: .5; } .hr-header { position: relative; top: -33px; left: -68px; } 
 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="css/styles.css"> <link rel="stylesheet" href="css/responsive.css"> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script> <script charset="UTF-8" src="js/moment.js"></script> <script charset="UTF-8" src="js/script2.js"></script> <title>Thought Machine Code Challenge</title> </head> <body> <div id="container"> <div class="header"> <h1 id="header-title"></h1> </div> <div id="calendar"> <div class="hr-block"><h2 class="hr-header">09:00</h2></div> <div class="hr-block"><h2 class="hr-header">10:00</h2></div> <div class="hr-block"><h2 class="hr-header">11:00</h2></div> <div class="hr-block"><h2 class="hr-header">12:00</h2></div> <div class="hr-block"><h2 class="hr-header">13:00</h2></div> <div class="hr-block"><h2 class="hr-header">14:00</h2></div> <div class="hr-block"><h2 class="hr-header">15:00</h2></div> <div class="hr-block"><h2 class="hr-header">16:00</h2></div> <div class="hr-block"><h2 class="hr-header">17:00</h2></div> <div class="hr-block"><h2 class="hr-header">18:00</h2></div> </div> </div> <div id="events"> </div> <script> document.getElementById("header-title").innerHTML = moment().calendar(); </script> </body> </html> 

events not rendering, you need to run:

renderDay([{start:30, end:120, start: 60, end: 120}]) in console

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