如何在不互相重叠的情况下分散div

罗德里戈·佩雷拉(Rodrigo Pereira)

我正在构建一个事件调度程序,但我已经意识到我无法找到一种在彼此不重叠的情况下传播事件的方法。(它可能同时具有事件且没有限制。只要有可能,请使用可用事件的100%width

这是这种情况的图片。 在此处输入图片说明

一些注意事项:

  • 这些事件包含在div中position: relative,所有事件都有position:absolute

  • 使用javascript,我必须弄清楚每个“ div事件”的值top left width和动态height

  • 事件是一个对象数组,例如以下代码:

    { startAt: "12:00:30", endsAt: "13:00:00", description: "evt1", id: '00001' }

  • I'm using Vue.js to develop this project. But this is not an issue if you don't know Vue. I've build a small project using jsbin so you can just play around with a javascript function.

Live Code: https://jsbin.com/bipesoy/

Where I'm having problem?

I can't find a algorithm to calculate the top left width and height on the fly based on an array of events.

Some considerations about the jsbin code:

  • All the code to find the 4 properties above is inside the function parsedEvents
  • Inside parsedEvents you can access the array of events using: this.events
  • The job of parsedEvents is loop through the array of events and add the style propertie to each one and then return a new array of events with the style object.
  • Each 30 minutes has a height of 40px;

Any ideas how to accomplish it or a better solution?

Mirko Vukušić

After some time playing with this challenge I think it's time to give up from the idea. It is possible to program many possible scenarios of arranging events, but when you dig deep you realize it's very difficult to even write what exactly you want to be done and even if you manage, some of your decisions just don't look good on the screen. This is only with expanding events in width. Positioning and even re-positioning them to fill gaps is solved and not too difficult.

Snippet is here (or JSBin if you prefer: http://jsbin.com/humiyi/99/edit?html,js,console,output).

Green events are detected as "expandable". Grey o nes cannot expand. To clarify problems with logic of expanding, some examples:

  • evt 1 and evt3? it can be like this or evt3 goes right and they both expand
  • evt7 and evt12? Many ways to expand this... how to define a rule?
  • imagine evt7 and evt11 ar merged into one big event. How to expand evt10, evt7/11 and evt 12? ... now try to write rules to consistently answer on above 3 (and many many more possible scenarios not in this example)

My conclusion is that writing rules and developing this is not worth the effort. UI usability will not get much. They will even loose in some scenarios, i.e. some events would be visually bigger just because they have space and not because they're more important.

I'd suggest layout similar or exactly the same as the one in example. Events just don't expand. I don't know how much daily events you expect, what's real life scenario, but only upgrade I'd potentially do is to split vertically calendar in separated regions - point in time which is not overlapping with any event, like line between evt7 and evt11 in example. Then run this same script per region independently. That will recalculate vertical slots per region so region with evt10 i evt11 will have only 2 vertical slots filling space 50% each. This maybe be worth it if your calendar has few crowded hours and only a few events later/before. This would fix an issue of too narrow events later in the day without spending much time. But if events are all over the day and overlapping alot I don't think it's worth it.

let events = [
  { startAt: "00:00", endsAt: "01:00", description: "evt1", id: '00001' },
  { startAt: "01:30", endsAt: "08:00", description: "evt2", id: '00002' },
  { startAt: "01:30", endsAt: "04:00", description: "evt3", id: '00003' },
  { startAt: "00:30", endsAt: "02:30", description: "evt3", id: '00013' },
  { startAt: "00:00", endsAt: "01:00", description: "evt3", id: '00014' },
  { startAt: "03:00", endsAt: "06:00", description: "evt4", id: '00004' },
  { startAt: "01:30", endsAt: "04:30", description: "evt5", id: '00005' },
  { startAt: "01:30", endsAt: "07:00", description: "evt6", id: '00006' },
  { startAt: "06:30", endsAt: "09:00", description: "evt7", id: '00007' },
  { startAt: "04:30", endsAt: "06:00", description: "evt8", id: '00008' },
  { startAt: "05:00", endsAt: "06:00", description: "evt9", id: '00009' },
  { startAt: "09:00", endsAt: "10:00", description: "evt10", id: '00010' },
  { startAt: "09:00", endsAt: "10:30", description: "evt11", id: '00011' },
  { startAt: "07:00", endsAt: "08:00", description: "evt12", id: '00012' }
]

console.time()

// will store counts of events in each 30-min chunk
// each element represents 30 min chunk starting from midnight
// ... so indexOf * 30 minutes = start time
// it will also store references to events for each chunk
// each element format will be: { count: <int>, eventIds: <array_of_ids> }
let counter = []

// helper to convert time to counter index
time2index = (time) => {
  let splitTime = time.split(":")
  return parseInt(splitTime[0]) * 2 + parseInt(splitTime[1])/30
}

// loop through events and fill up counter with data
events.map(event => {
  for (let i = time2index(event.startAt); i < time2index(event.endsAt); i++) {
    if (counter[i] && counter[i].count) {
      counter[i].count++
      counter[i].eventIds.push(event.id)
    } else {
      counter[i] = { count: 1, eventIds: [event.id] }
    }
  }
})

//find chunk with most items. This will become number of slots (vertical spaces) for our calendar grid
let calSlots = Math.max( ...counter.filter(c=>c).map(c=>c.count) ) // filtering out undefined elements
console.log("number of calendar slots: " + calSlots)

// loop through events and add some more props to each:
// - overlaps: all overlapped events (by ref)
// - maxOverlapsInChunk: number of overlapped events in the most crowded chunk
//   (1/this is maximum number of slots event can occupy)
// - pos: position of event from left (in which slot it starts)
// - expandable: if maxOverlapsInChunk = calSlot, this event is not expandable for sure
events.map(event => {
  let overlappedEvents = events.filter(comp => {
    return !(comp.endsAt <= event.startAt || comp.startAt >= event.endsAt || comp.id === event.id)
  })
  event.overlaps = overlappedEvents //stores overlapped events by reference!
  event.maxOverlapsInChunk = Math.max( ...counter.filter(c=>c).map(c=>c.eventIds.indexOf(event.id) > -1 ? c.count : 0))
  event.expandable = event.maxOverlapsInChunk !== calSlots
  event.pos = Math.max( ...counter.filter(c=>c).map( c => {
    let p = c.eventIds.indexOf(event.id)
    return p > -1 ? p+1 : 1
  }))
})

// loop to move events leftmost possible and fill gaps if any
// some expandable events will stop being expandable if they fit gap perfectly - we will recheck those later
events.map(event => {
  if (event.pos > 1) {
    //find positions of overlapped events on the left side
    let vertSlotsTakenLeft = event.overlaps.reduce((result, cur) => {
      if (result.indexOf(cur.pos) < 0 && cur.pos < event.pos) result.push(cur.pos)
      return result
    }, [])
    
    // check if empty space on the left
    for (i = 1; i < event.pos; i++) {
      if (vertSlotsTakenLeft.indexOf(i) < 0) {
        event.pos = i
        console.log("moving " + event.description + " left to pos " + i)
        break
      }
    }
  }
})

// fix moved events if they became non-expandable because of moving
events.filter(event=>event.expandable).map(event => {
  let leftFixed = event.overlaps.filter(comp => {
    return event.pos - 1 === comp.pos && comp.maxOverlapsInChunk === calSlots
  })
  let rightFixed = event.overlaps.filter(comp => {
    return event.pos + 1 === comp.pos && comp.maxOverlapsInChunk === calSlots
  })
  event.expandable = (!leftFixed.length || !rightFixed.length)
})

//settings for calendar (positioning events)
let calendar = {width: 300, chunkHeight: 30}

// one more loop through events to calculate top, left, width and height
events.map(event => {
  event.top = time2index(event.startAt) * calendar.chunkHeight
  event.height = time2index(event.endsAt) * calendar.chunkHeight - event.top
  //event.width = 1/event.maxOverlapsInChunk * calendar.width
  event.width = calendar.width/calSlots // TODO: temporary width is 1 slot 
  event.left = (event.pos - 1) * calendar.width/calSlots 
})

console.timeEnd()

// TEST drawing divs
events.map(event => {
  $("body").append(`<div style="position: absolute;
    top: ${event.top}px;
    left: ${event.left}px;
    width: ${event.width}px;
    height: ${event.height}px;
    background-color: ${event.expandable ? "green" : "grey"};
    border: solid black 1px;
    ">${event.description}</div>`)
})

//console.log(events)
<!DOCTYPE html>
<html>
<head>
  <script src="https://code.jquery.com/jquery-3.1.0.js"></script>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>

</body>
</html>

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

如何在不互相抵消的情况下结合2个3D旋转?

如何在不更改其位置的情况下将div与另一个div重叠-CSS

如何在不双击的情况下“刷新” div?

如何在不互相引用的情况下将对象复制到另一个对象?

如何在不破坏 <div> 的宽度或布局的情况下将 <div> 包裹在 <a> 中?

如何在不更改 HTML 的情况下通过 div 中的链接使 div 可点击

如何在没有位置的情况下将一个div与另一个div重叠

在Rust中,如何在不互相引用的情况下将两个对象的生存期明确地捆绑在一起?

如何在不弄乱边框的情况下将<div>保留在标题中?

如何在不返回div标签的情况下使用Cleaner,lxml.html?

如何在不添加内联样式的情况下从 div 中删除特定的 css?

如何在不固定高度的情况下使 div 可滚动?

如何在不指定宽度的情况下使两个DIV相邻浮动?

我如何在不更改主体的情况下更改div元素的方向

如何在不滚动的情况下将长文本包装在div中

如何在不损失价值的情况下复制div元素

如何在不指定高度的情况下使多个div显示在偶数行中?

如何在不拉伸或挤压它们的情况下重新调整 Div 的大小?

如何在不覆盖身体的情况下将 div 放在动画身体上方

如何在不增加边距的情况下更改换行的div文本的行高?

如何在不滚动窗口的情况下滚动到子div的底部?

如何在不覆盖垂直滚动的情况下使固定div遍历父元素?

如何在不刷新的情况下从外部页面更新内容div-GAE

如何在不创建新行的情况下显示 DIV 元素?

如何在不刷新整个页面的情况下使用 AJAX 刷新 div?

如何在不更改变量的情况下递归

如何在不丢失数据的情况下安装Ubuntu?

如何在不抛弃const的情况下实现strstr()?

如何在不递归的情况下进行ls