javascript 其余列为空时网格列跨度为全宽

qv7cva1a  于 2023-09-29  发布在  Java
关注(0)|答案(1)|浏览(73)

我正在处理一个日历元素,其中一天的事件是利用网格行跨越其指定的时间框架。
然而,当涉及到设置事件宽度时,我似乎很挣扎。默认情况下,当它是当时唯一的事件时,它们将占据一天的整个宽度。如果它们在任何一点上同时发生事件,它们将相应地减少它们的宽度,以便它们都能适合。
当我有一组事件同时发生时,就会出现问题,这会生成多个列,然后在当天晚些时候发生一个事件,其中没有任何巧合的事件。此事件现在仅限于当天早些时候的事件所创建的列的宽度。
有关上下文,请参见图片:Image of annotated demo
下面是一个工作演示。

(function() {

  function init() {
    let calendarElement = document.getElementsByClassName('calendar')[0],
      events = calendarElement.getElementsByClassName('calendar__event');
    _positionEventsOnGrid(events);
  }

  function _positionEventsOnGrid(events) {
    Array.from(events).forEach(event => {
      let gridRow = event.getAttribute('data-grid-row');

      if (gridRow) {
        let gridRowSpan = event.getAttribute('data-grid-row-span');

        event.style.gridRow = gridRowSpan ? `${gridRow} / span ${gridRowSpan}` : gridRow;
      }
    });
  }

  init();
})();
body {
  margin: 0;
  block-size: 100vh;
  inline-size: 100vw;
}

.calendar {
  $block: &;
  inline-size: 100%;
  block-size: 100%;
  display: grid;
  grid-template-columns: 2.5rem 1fr;
  grid-template-rows: auto 1fr;
}

.calendar__dayNames {
  grid-row: 1;
  grid-column: 2;
  display: flex;
}

.calendar__dayName {
  flex-grow: 1;
  flex-basis: 0;
}

.calendar__schedule {
  grid-row: 2;
  grid-column: 1/span 2;
  display: grid;
  grid-template-columns: 2.5rem 1fr;
  border-block-start: 1px solid #000;
}

.calendar__timeline {
  display: grid;
  grid-template-rows: repeat(19, 1.375rem);
  gap: 0 0.625rem;
  grid-column: 1/ span 2;
  grid-row: 1;
  position: relative;
}

.calendar__timelineItem {
  display: flex;
  align-items: center;
  border-block-end: 1px dotted #222;
}

.calendar__timelineItem:nth-child(even) {
  border-block-end: 1px solid #000;
}

.calendar__timelineItem:nth-child(odd):after {
  display: inline;
  content: attr(data-time);
}

.calendar__dayEventsContainer {
  position: relative;
  grid-row: 1;
  grid-column: 2;
  display: flex;
}

.calendar__dayEvents {
  display: grid;
  grid-template-rows: repeat(19, 1.375rem);
  gap: 0 0.625rem;
  border-inline-start: 1px solid #000;
  flex-grow: 1;
  flex-basis: 0;
}

.calendar__event {
  background-color: #346DA8;
  border: none;
}
<div class="calendar">
  <div class="calendar__dayNames">
    <div class="calendar__dayName">Monday</div>
    <div class="calendar__dayName">Tuesday</div>
    <div class="calendar__dayName">Wednesday</div>
    <div class="calendar__dayName">Thursday</div>
    <div class="calendar__dayName">Friday</div>
  </div>
  <div class="calendar__schedule">
    <div class="calendar__timeline">
      <div class="calendar__timelineItem" data-time="09:00"></div>
      <div class="calendar__timelineItem" data-time="09:15"></div>
      <div class="calendar__timelineItem" data-time="09:30"></div>
      <div class="calendar__timelineItem" data-time="09:45"></div>
      <div class="calendar__timelineItem" data-time="10:00"></div>
      <div class="calendar__timelineItem" data-time="10:15"></div>
      <div class="calendar__timelineItem" data-time="10:30"></div>
      <div class="calendar__timelineItem" data-time="10:45"></div>
      <div class="calendar__timelineItem" data-time="11:00"></div>
      <div class="calendar__timelineItem" data-time="11:15"></div>
      <div class="calendar__timelineItem" data-time="11:30"></div>
      <div class="calendar__timelineItem" data-time="11:45"></div>
      <div class="calendar__timelineItem" data-time="12:00"></div>
      <div class="calendar__timelineItem" data-time="12:15"></div>
      <div class="calendar__timelineItem" data-time="12:30"></div>
      <div class="calendar__timelineItem" data-time="12:45"></div>
      <div class="calendar__timelineItem" data-time="13:00"></div>
      <div class="calendar__timelineItem" data-time="13:15"></div>
      <div class="calendar__timelineItem" data-time="13:30"></div>

    </div>
    <div class="calendar__dayEventsContainer">
      <div class="calendar__dayEvents">

        <button type="button" class="calendar__event" data-grid-row="1" data-grid-row-span="6"></button>
        <button type="button" class="calendar__event" data-grid-row="1" data-grid-row-span="3"></button>
        <button type="button" class="calendar__event" data-grid-row="1" data-grid-row-span="1"></button>
        <button type="button" class="calendar__event" data-grid-row="10" data-grid-row-span="1"></button>
      </div>
      <div class="calendar__dayEvents">
      </div>
      <div class="calendar__dayEvents">
        <button type="button" class="calendar__event" data-grid-row="1" data-grid-row-span="6"></button>
        <button type="button" class="calendar__event" data-grid-row="1" data-grid-row-span="1"></button>
      </div>
      <div class="calendar__dayEvents">

      </div>
      <div class="calendar__dayEvents">
        <button type="button" class="calendar__event" data-grid-row="1" data-grid-row-span="1"></button>
      </div>
    </div>
  </div>
</div>

我试过几件事:网格柱:1/-1,但这将对所有事件执行此操作,因此会错误地格式化并发事件。
网格自动流:然而,在我的例子中,这似乎并不奏效。
网格-模板-柱:repeat(auto-fit,minmax()),但作为minmax的一部分的最小值需要是固定大小。
我想知道是否需要扩展positionEventsOnGrid()函数,以便它检查'grid-column:1/-1’可以应用于特定事件。如果不是和当时的其他事件巧合的话。然而,我觉得这可能是相当复杂的,所以我想我会问周围的情况下,有一个更简单的方法。
我希望单一事件跨越他们一天的整个宽度,而不管当天早些时候或晚些时候是否有并发事件。

slsn1g29

slsn1g291#

解决方案中的网格列是隐式生成的,因为CSS没有显式设置列布局。参见@Michael Benjamin的回答here
问题是数字索引(1 / -1)只适用于显式网格。换句话说,就是具有已定义的列和行的网格。您使用的是隐式网格,其中的列和行根据需要自动生成。
如果您将grid-template-columns: repeat()设置为.calendar__dayEvents容器的CSS规则,则应用于.calendar__event元素的event.style.gridColumn = 1 / -1;将扩展到所有列。这与您的评论“作为minmax的一部分的最小值需要是固定大小”类似

重叠和非重叠事件

我想知道是否需要扩展positionEventsOnGrid()函数,以便它检查'grid-column:1/-1’可以应用于特定事件。如果不是和当时的其他事件巧合的话。然而,我觉得这可能是相当复杂的,所以我想我会问周围的情况下,有一个更简单的方法。
我还没找到解决的办法。幸运的是,在SO上存在可以group overlapping time or integer intervals的现有解决方案。在我的日历组件中,我已经应用/改编了我在链接的SO帖子上发布的GitHub功能。调整应用于下面的代码片段。
下面的代码段执行以下操作:

  • 维护您的HTML,但包括周二和周四的其他事件。
  • 维护您的CSS,但包括额外的CSS规则,以设置具有自定义data-type属性的事件的背景色。
  • 扩展你的JS。

JS执行

  • 为每个事件日容器分组重叠/非重叠事件。
  • 每天的事件项使用event.style.gridRow进行布局。
  • 每天的事件网格容器中隐式生成的网格列的数量是使用内置的window函数getComputedStyle()检索的,如本文所提供的:count columns in a responsive grid
  • 将列数值应用于每天的.calendar__dayEvents网格容器,并将非重叠事件项设置为event.style.gridColumn = 1 / -1;
  • 或者,可以直接为每个非重叠事件项event.style.gridColumn = 1 / span ${gridColumnCount};设置列数值,而不是为grid-template-columns事件网格容器设置网格列数。(请参见代码段中的注解。)
(function() {

  function init() {
    //const calendarElement = document.getElementsByClassName('calendar')[0];
    //const events = calendarElement.getElementsByClassName('calendar__event');
    //_positionEventsOnGrid(events);
    const dayEventsElems = document.querySelectorAll('.calendar__dayEvents');
    _positionEventsOnGrid2(dayEventsElems);
  }

  /*
  function _positionEventsOnGrid(events) {
      Array.from(events).forEach(event => {
          let gridRow = event.getAttribute('data-grid-row');

          if (gridRow) {
              let gridRowSpan = event.getAttribute('data-grid-row-span');

              event.style.gridRow = gridRowSpan ? `${gridRow} / span ${gridRowSpan}` : gridRow;
          }
      });
  }
  */

  function _positionEventsOnGrid2(dayEventsElems) {
    if (!dayEventsElems) {
      return;
    }

    dayEventsElems.forEach(dayEventElem => {
      const events = dayEventElem.querySelectorAll('.calendar__event');

      // if there are no event elements in the
      // '.calendar__dayEvents' element then exit function.
      if (events.length === 0) {
        return;
      }

      // Grid layout using "implicit grid, where columns and rows
      // are automatically generated, as needed."
      // Source: https://stackoverflow.com/a/75250349
      // Also see: https://stackoverflow.com/a/73624201

      const overlappingGroups = getOverlappingEventGroups(events);
      overlappingGroups.forEach(group => {
        group.forEach(event => {
          event.style.gridRow = `${event.startRow} / ${event.endRow}`;
        });
      });

      // After the grid is laid out, count the number of columns
      // created using the built-in 'gridComputedStyle()'.
      // See the function 'getGridRowColCount()' below.

      const gridElem = events[0].closest('.calendar__dayEvents');
      // https://www.javascripttutorial.net/javascript-return-multiple-values
      const [gridRowCount, gridColumnCount] = getGridRowColCount(gridElem);
      gridElem.style.gridTemplateColumns = `repeat(${gridColumnCount}, 1fr)`;
      overlappingGroups.forEach(group => {
        // If the event group does not contain one entry,
        // then exit function.
        if (group.length !== 1) {
          return;
        }
        // Otherwise, explicitly set the 'gridColumn'
        // CSS style property to '1 / -1' so that the event
        // element spans all columns in the 'gridElem' grid.
        group[0].style.gridColumn = `1 / -1`;
        // Or set the span value and remove the code above that
        // sets the CSS property 'gridTemplateColumns'.
        //group[0].style.gridColumn = `1 / span ${gridColumnCount}`;
      });
    });
  }

  // Source: https://stackoverflow.com/questions/55204205/a-way-to-count-columns-in-a-responsive-grid
  function getGridRowColCount(grid) {
    const gridComputedStyle = window.getComputedStyle(grid);

    // get number of grid rows
    const gridRowCount = gridComputedStyle.getPropertyValue("grid-template-rows").split(" ").length;

    // get number of grid columns
    const gridColumnCount = gridComputedStyle.getPropertyValue("grid-template-columns").split(" ").length;

    //console.log(gridRowCount, gridColumnCount);

    // https://www.javascripttutorial.net/javascript-return-multiple-values
    return [gridRowCount, gridColumnCount];
  }

  /*
  https://stackoverflow.com/a/75486209
  Format of intervals array from:
  https://gist.githubusercontent.com/blasten/acfaafc8247e37abf23f/raw/c71f42428e34dbb7ff2d1b5e932fdf4152fe1ad2/group-intervals.js
  const intervals = [
      [2, 5],
      [5, 6],
      [3, 4],
      [7, 8],
      [6.5, 9],
      [10, 11.5]
  ];

  The function 'getOverlappingEventGroups()' returns a
  modified intervals array format containing '.calendar__event' 
  DOM elements with custom 'startRow' and 'endRow' properties.
  */
  function getOverlappingEventGroups(events) {
    const eventIntervals = [];

    events.forEach(event => {
      const gridRow = event.getAttribute('data-grid-row');
      const gridRowSpan = event.getAttribute('data-grid-row-span');
      if (isNaN(gridRow) || isNaN(gridRowSpan)) {
        return;
      }
      const startRow = parseInt(gridRow, 10);
      const endRow = startRow + parseInt(gridRowSpan, 10);
      event.startRow = startRow;
      event.endRow = endRow;
      eventIntervals.push(event);
    });

    const eventGroups = groupOverlapingEventIntervals(eventIntervals);
    //console.log("Overlapping event groups: ", eventGroups);

    return eventGroups;
  }

  // Source: https://gist.githubusercontent.com/blasten/acfaafc8247e37abf23f/raw/c71f42428e34dbb7ff2d1b5e932fdf4152fe1ad2/group-intervals.js
  function groupOverlapingEventIntervals(eventIntervals) {
    eventIntervals.sort((a, b) => a.startRow - b.startRow);

    const groups = [
      [eventIntervals[0]]
    ];

    let j = 0;
    let end = eventIntervals[0].endRow;

    for (let i = 1; i < eventIntervals.length; i++) {
      if (eventIntervals[i].startRow <= end) {
        if (eventIntervals[i].endRow > end) {
          end = eventIntervals[i].endRow;
        }
        groups[j].push(eventIntervals[i]);
      } else {
        groups.push([eventIntervals[i]]);
        j++;
        end = eventIntervals[i].endRow;
      }
    }
    return groups;
  }

  init();
})();
body {
  margin: 0;
  block-size: 100vh;
  inline-size: 100vw;
}

.calendar {
  inline-size: 100%;
  block-size: 100%;
  display: grid;
  grid-template-columns: 2.5rem 1fr;
  grid-template-rows: auto 1fr;
}

.calendar__dayNames {
  grid-row: 1;
  grid-column: 2;
  display: flex;
}

.calendar__dayName {
  flex-grow: 1;
  flex-basis: 0;
}

.calendar__schedule {
  grid-row: 2;
  grid-column: 1/span 2;
  display: grid;
  grid-template-columns: 2.5rem 1fr;
  border-block-start: 1px solid #000;
}

.calendar__timeline {
  display: grid;
  grid-template-rows: repeat(19, 1.375rem);
  gap: 0 0.625rem;
  grid-column: 1/ span 2;
  grid-row: 1;
  position: relative;
}

.calendar__timelineItem {
  display: flex;
  align-items: center;
  border-block-end: 1px dotted #222;
}

.calendar__timelineItem:nth-child(even) {
  border-block-end: 1px solid #000;
}

.calendar__timelineItem:nth-child(odd):after {
  display: inline;
  content: attr(data-time);
}

.calendar__dayEventsContainer {
  position: relative;
  grid-row: 1;
  grid-column: 2;
  display: flex;
}

.calendar__dayEvents {
  display: grid;
  grid-template-rows: repeat(19, 1.375rem);
  gap: 0 0.625rem;
  border-inline-start: 1px solid #000;
  flex-grow: 1;
  flex-basis: 0;
}

.calendar__event {
  background-color: #346DA8;
  border: none;
}

/* Color-coded event elements */

.calendar__event[data-type=aaa] {
  background-color: rgb(243 165 97 / 80%);
}

.calendar__event[data-type=bbb] {
  background-color: rgb(80 142 77 / 80%);
}

.calendar__event[data-type=ccc] {
  background-color: rgb(228 53 53 / 80%);
}

.calendar__event[data-type=ddd] {
  background-color: rgb(96 177 218 / 80%);
}
<div class="calendar">
  <div class="calendar__dayNames">
    <div class="calendar__dayName">Monday</div>
    <div class="calendar__dayName">Tuesday</div>
    <div class="calendar__dayName">Wednesday</div>
    <div class="calendar__dayName">Thursday</div>
    <div class="calendar__dayName">Friday</div>
  </div>
  <div class="calendar__schedule">
    <div class="calendar__timeline">
      <div class="calendar__timelineItem" data-time="09:00"></div>
      <div class="calendar__timelineItem" data-time="09:15"></div>
      <div class="calendar__timelineItem" data-time="09:30"></div>
      <div class="calendar__timelineItem" data-time="09:45"></div>
      <div class="calendar__timelineItem" data-time="10:00"></div>
      <div class="calendar__timelineItem" data-time="10:15"></div>
      <div class="calendar__timelineItem" data-time="10:30"></div>
      <div class="calendar__timelineItem" data-time="10:45"></div>
      <div class="calendar__timelineItem" data-time="11:00"></div>
      <div class="calendar__timelineItem" data-time="11:15"></div>
      <div class="calendar__timelineItem" data-time="11:30"></div>
      <div class="calendar__timelineItem" data-time="11:45"></div>
      <div class="calendar__timelineItem" data-time="12:00"></div>
      <div class="calendar__timelineItem" data-time="12:15"></div>
      <div class="calendar__timelineItem" data-time="12:30"></div>
      <div class="calendar__timelineItem" data-time="12:45"></div>
      <div class="calendar__timelineItem" data-time="13:00"></div>
      <div class="calendar__timelineItem" data-time="13:15"></div>
      <div class="calendar__timelineItem" data-time="13:30"></div>

    </div>
    <div class="calendar__dayEventsContainer">
      <div class="calendar__dayEvents">
        <button type="button" class="calendar__event" data-grid-row="1" data-grid-row-span="6"></button>
        <button type="button" class="calendar__event" data-grid-row="1" data-grid-row-span="3"></button>
        <button type="button" class="calendar__event" data-grid-row="1" data-grid-row-span="1"></button>
        <button type="button" class="calendar__event" data-grid-row="10" data-grid-row-span="1"></button>
      </div>
      <div class="calendar__dayEvents">
        <button type="button" class="calendar__event" data-grid-row="3" data-grid-row-span="7" data-type="bbb"></button>
        <button type="button" class="calendar__event" data-grid-row="12" data-grid-row-span="3" data-type="aaa"></button>
        <button type="button" class="calendar__event" data-grid-row="14" data-grid-row-span="2" data-type="ccc"></button>
      </div>
      <div class="calendar__dayEvents">
        <button type="button" class="calendar__event" data-grid-row="1" data-grid-row-span="6"></button>
        <button type="button" class="calendar__event" data-grid-row="1" data-grid-row-span="1"></button>
      </div>
      <div class="calendar__dayEvents">
        <button type="button" class="calendar__event" data-grid-row="1" data-grid-row-span="6" data-type="aaa"></button>
        <button type="button" class="calendar__event" data-grid-row="1" data-grid-row-span="3" data-type="bbb"></button>
        <button type="button" class="calendar__event" data-grid-row="1" data-grid-row-span="1" data-type="aaa"></button>
        <button type="button" class="calendar__event" data-grid-row="2" data-grid-row-span="1" data-type="ccc"></button>
        <button type="button" class="calendar__event" data-grid-row="2" data-grid-row-span="4" data-type="bbb"></button>
        <button type="button" class="calendar__event" data-grid-row="10" data-grid-row-span="1" data-type="ddd"></button>
        <button type="button" class="calendar__event" data-grid-row="14" data-grid-row-span="3" data-type="ddd"></button>
      </div>
      <div class="calendar__dayEvents">
        <button type="button" class="calendar__event" data-grid-row="1" data-grid-row-span="1"></button>
      </div>
    </div>
  </div>
</div>

更新(9/26/2023)
添加了带注解的图像作为替代代码执行描述。

  • 迭代.calendar__dayEvents div容器(即,用黄色标出的每一天的列)。
  • 在每一天的一周列中,将.calendar__event元素分组为具有重叠间隔的元素(用红框标出)。
  • .calendar__dayEvents div container/day-of-week列中布局所有.calendar__event元素。
  • 检索“星期几”列中的列数。
  • 将列数值直接应用于重叠组中的.calendar__event元素,只有1个.calendar__event条目
  • 或者将列数应用于.calendar__dayEvents div容器,并在重叠组中设置.calendar__event元素,只有1个.calendar__event条目来跨越所有列(通过style.gridColumn = 1 / -1)。

相关问题