Диапазон столбцов сетки занимает всю ширину, когда остальные столбцы пусты

Я работаю над элементом календаря, в котором дневные события используют строку сетки для охвата указанного периода времени.

Тем не менее, мне кажется, что я борюсь с установкой ширины событий. По умолчанию они занимают всю ширину дня, если это единственное событие в это время. И если в какой-то момент они являются совпадающими событиями, они соответственно уменьшат свою ширину, чтобы все они могли поместиться.

Моя проблема возникает, когда у меня одновременно происходит группа событий, которая генерирует несколько столбцов, а затем в тот же день происходит событие, не имеющее совпадающих событий. Это событие теперь ограничено шириной столбцов, созданных на основе событий, произошедших ранее в тот же день.

См. изображение для контекста: Изображение аннотированной демонстрации

См. ниже рабочую демонстрацию.

(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: повторение(auto-fit, minmax()), но минимальное значение как часть minmax должно быть фиксированного размера.

Мне интересно, нужно ли мне расширить функцию PositionEventsOnGrid(), чтобы она проверяла, можно ли применить «grid-column: 1/-1» к конкретным событиям. Если это не совпадает с другими событиями того времени. Однако я чувствую, что это может быть довольно сложно, поэтому решил спросить, есть ли более простой способ.

Я хочу, чтобы отдельные события охватывали всю продолжительность дня, независимо от того, происходят ли одновременные события раньше или позже в течение дня.

Пожалуйста, добавьте Минимально воспроизводимый пример прямо в свой вопрос. Пожалуйста, не указывайте нам код, связанный с другим веб-сайтом. Пожалуйста, отредактируйте свой вопрос и нажмите кнопку на панели инструментов, которая выглядит как [<>], чтобы добавить к вашему вопросу фрагмент кода, а не только блоки кода. Также измените SCSS на скомпилированный вывод CSS, чтобы фрагмент мог работать здесь.

Chris Barr 22.09.2023 14:30
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
2
1
90
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Столбцы сетки в вашем решении генерируются неявно, поскольку ваш CSS явно не задает макет столбца. См. ответ @Michael Benjamin здесь.

Проблема в том, что числовые индексы (1/-1) работают только с явными сетками. Другими словами, сетки с определенными столбцами и строками. Вы используете неявную сетку, в которой столбцы и строки генерируются автоматически по мере необходимости.

event.style.gridColumn = 1 / -1;, примененный к элементу .calendar__event, растягивается по всем столбцам, если вы установили grid-template-columns: repeat() в правило CSS для .calendar__dayEvents контейнера. Это похоже на ваш комментарий «минимальное значение как часть минимального максимума должно быть фиксированного размера».

Перекрывающиеся и непересекающиеся события

Мне интересно, нужно ли мне расширить функцию PositionEventsOnGrid(), чтобы она проверяла, можно ли применить «grid-column: 1/-1» к конкретным событиям. Если это не совпадает с другими событиями того времени. Однако я чувствую, что это может быть довольно сложно, поэтому решил спросить, есть ли более простой способ.

Я не нашел способа обойти это. К счастью, существуют решения SO, которые могут группировать перекрывающиеся временные или целочисленные интервалы. В своих компонентах календаря я применил/адаптировал функцию из GitHub, которую опубликовал в связанном сообщении SO. Адаптация применяется к приведенному ниже фрагменту кода.

Следующий фрагмент кода выполняет следующее:

  • Сохраняет ваш HTML, но включает дополнительные события на вторник и четверг.
  • Сохраняет ваш CSS, но включает дополнительные правила CSS для установки цвета фона для событий с пользовательским атрибутом data-type.
  • Расширяет ваш JS.

JS-исполнение

  • Группируйте перекрывающиеся/непересекающиеся события для каждого контейнера дней событий.
  • Элементы событий на каждый день располагаются с помощью event.style.gridRow.
  • Количество неявно сгенерированных столбцов сетки в контейнере сетки событий каждого дня извлекается с помощью встроенной window функции getComputedStyle(), как предусмотрено в этом посте SO: подсчет столбцов в адаптивной сетке.
  • Примените значение количества столбцов к контейнеру сетки .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.info(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.info("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>

Обновление (26.09.2023)

Добавлено изображение с аннотациями в качестве альтернативного описания выполнения кода.

  • Переберите .calendar__dayEvents div-контейнеры (то есть каждый столбец дня недели, выделенный желтым).
  • В каждом столбце дней недели сгруппируйте .calendar__event элементы с перекрывающимися интервалами (обведены красными рамками).
  • Разместите все элементы .calendar__event в контейнере .calendar__dayEvents div/столбце дня недели.
  • Получить количество столбцов в столбце дня недели.
  • Примените значение количества столбцов непосредственно к элементу .calendar__event в перекрывающихся группах только с 1 записью .calendar__event.
  • ИЛИ Примените количество столбцов к контейнеру div .calendar__dayEvents и установите элемент .calendar__event в перекрывающихся группах только с одной записью .calendar__event, чтобы охватить все столбцы (через style.gridColumn = 1 / -1).

Другие вопросы по теме