检查两个或多个DOM元素是否重叠。

4

大家好!

我在我的简单单日日历脚本中遇到了问题。

我被要求创建一个单日日历,显示从上午9点到下午6点的每个小时块。如果一个事件重叠另一个事件,它们应该具有相同的宽度而不互相重叠。我已经成功地为两个事件实现了这一点,但是如果超过两个事件重叠,情况就会变得有些复杂。我需要帮助找出一种方法来解决这个问题,使得任何数量的事件重叠时,它们的宽度都相等。

使用全局函数在日历上呈现事件:

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

该函数以对象数组作为参数,其中整数表示从上午9点开始的分钟数。例如,30 表示上午9:30,120 表示上午11点。

这里是我从stackoverflow上获取的碰撞函数:

// collision function to return boolean
// attribute: https://dev59.com/sGzXa4cB1Zd3GeqPTnBU
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;
}

我在所有需要检查重叠的事件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());

        if(collision)
      }
    })
  }
})

提供截图以帮助更好地理解 :)

当两个重叠时,它们的宽度相等

当三个或更多重叠时,问题就出现了

非常感谢您的帮助!DW


1
当两个事件元素相撞时,您要将其宽度和下一个事件元素的宽度减半。因此,在您的第二个屏幕截图中,您有三个事件元素,前两个被减半,而第三个是减半后的一半,即四分之一。您需要计算相撞的元素数量,然后将所有元素设置为相同的宽度,这是父元素宽度的相等百分比。您能提供代码的实际例子吗? - Jaydo
2个回答

1
在查看代码后,似乎检查呈现元素是否发生碰撞过于复杂,可以从开始和结束时间计算出结果。
我的方法是将发生碰撞的事件分组到数组中:
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
      }]
    ];

然后我们遍历每组碰撞,创建具有适当宽度和定位的元素。
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>


1
Jaydo,感谢您抽出时间回答我的问题 :) 使用事件对象的时间来计算是否发生了重叠似乎是一种更加可靠的方法来解决这个问题。谢谢! - douglaswissett
@DWW 很高兴能帮忙。我喜欢挑战。此外,当我写这篇文章时有些累了,出现了一些错误,现在已经修复了。为div生成的id不是唯一的(不知道我怎么想到i + j会起作用)。如果collidingEvent在被检查的event之前开始并在其之后结束,则事件碰撞检查将无法捕获碰撞。 - Jaydo

0

工作索引、脚本和CSS文件

//**********************************************************************
//
//  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: https://dev59.com/sGzXa4cB1Zd3GeqPTnBU
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>

事件未渲染,您需要在控制台中运行:

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


1
谢谢您包含这个内容。您应该将其移动到问题中,而不是作为答案,因为它并不真正回答问题。如果您认为代码段太长,您可以随时隐藏它。 - Jaydo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接