我正在开发一个日历元素,其中当天的事件利用网格行跨越其指定的时间范围。
但是,在设置事件宽度时我似乎遇到了困难。默认情况下,当它是当时唯一的事件时,它们将占据一天的整个宽度。如果它们在任何时候同时发生事件,它们都会相应地减小宽度,以便它们都能适合。
当我同时发生一组事件时,就会出现问题,这会生成多个列,然后在当天晚些时候发生一个没有任何同时发生的事件的事件。此事件现在仅限于根据当天早些时候的事件创建的列的宽度。
请参阅下面的工作演示。
(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>
我尝试过一些事情: grid-column: 1/-1 但这将对所有事件执行此操作,因此会错误地格式化并发事件。
grid-auto-flow:密集但是,这在我的示例中似乎并没有起到作用。
grid-template-columns:repeat(auto-fit, minmax()),但作为minmax一部分的最小值需要是固定大小。
我确实想知道是否需要扩展positionEventsOnGrid()函数,以便它检查“grid-column:1/-1”是否可以应用于特定事件。如果当时没有发生其他事件的话。然而,我觉得这可能相当复杂,所以我想问问周围是否有更简单的方法。
我希望单一事件能够跨越一天的整个宽度,无论当天早些时候或晚些时候是否有并发事件。
解决方案中的网格列是隐式生成的,因为您的 CSS 没有显式设置列布局。请参阅@Michael Benjamin 的回答此处。
如果您将问题在于数字索引 (1 / -1) 仅适用于显式网格。换句话说,具有定义的列和行的网格。您正在使用隐式网格,其中会根据需要自动生成列和行。
event.style.gridColumn = 1 / -1;
设置为
.calendar__event
容器的 CSS 规则,则应用于
grid-template-columns: repeat()
元素的
.calendar__dayEvents
会延伸到所有列。这类似于您的评论“作为 minmax 一部分的最小值需要是固定大小。”
我确实想知道是否需要扩展positionEventsOnGrid()函数,以便它检查“grid-column:1/-1”是否可以应用于特定事件。如果当时没有发生其他事件的话。然而,我觉得这可能相当复杂,所以我想问问周围是否有更简单的方法。
我还没有找到解决这个问题的方法。幸运的是,SO 上现有的解决方案可以“对重叠时间或整数间隔进行分组”。在我的日历组件中,我应用/改编了我在链接的 SO 帖子上发布的 GitHub 功能。该调整应用于下面的代码片段。 以下代码片段执行以下操作:
维护您的 HTML,但包括周二和周四的其他活动。
data-type
对每个事件日容器的重叠/非重叠事件进行分组。
event.style.gridRow
window
getComputedStyle()
检索的,如本 SO 帖子所提供:count columns in a Responsible grid。 将列数值应用到每天的
.calendar__dayEvents
event.style.gridColumn = 1 / -1;
。或者,可以将列数值设置为每个不重叠的事件项event.style.gridColumn = 1 / ${};
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>