Подтвердить что ты не робот

Создание вертикальной временной шкалы, такой как SO Developer Story

В настоящее время я пытаюсь создать вертикальную временную шкалу (как в истории разработчиков stackoverflow).

Требования следующие:

  • Временная шкала не должна просто изменять элементы слева и справа и не оставлять пространство на противоположной стороне (не нравится это или this)
  • Левый и правый элементы должны заполнить оставшееся пространство, если необходимо (как здесь, к сожалению, порядок элементов не сохраняется после содержимого элемент становится длиннее)
  • Порядок элементов должен быть таким же, как и в предоставленной структуре DOM, поэтому пункты, которые должны быть первыми, должны быть первыми на временной шкале

Итак, на первом этапе я просто хотел проверить, как выравнивать элементы соответственно слева или справа.

Это то, что у меня есть:

.timeline {
  width: 100%;
}

.timeline-item {
  box-sizing: border-box;
  width: 50%;
  position: relative;
  float: left;
  clear: left;
  padding-left: 20px;
  margin: 1px 0 1px;
  background: tomato;
}

.timeline-item.inverse {
  float: right;
  clear: right;
  background: grey;
}


/* Just used to show the middle point alignment */

.timeline-item::after {
  content: '';
  height: 2px;
  width: 10px;
  background: DarkGrey;
  position: absolute;
  top: 50%;
  right: 0;
}

.timeline-item.inverse::after {
  left: 0;
  background: DarkRed;
}
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://code.jquery.com/jquery-1.11.1.min.js" type="text/javascript" ></script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js" type="text/javascript" ></script>

<div class="timeline">
  <div class="timeline-item">
    <p>
      1 This is some sample text (multiline)
      <br>This is some sample text
      <br>This is some sample text
      <br>This is some sample text
      <br>This is some sample text
      <br>This is some sample text
      <br>This is some sample text
      <br>This is some sample text
      <br>This is some sample text
    </p>
  </div>
  <div class="timeline-item inverse">
    <p>
      2 This is some sample text
    </p>
  </div>
  <div class="timeline-item inverse">
    <p>
      3 This is some sample text
    </p>
  </div>
  <div class="timeline-item">
    <p>
      4 This is some sample text
    </p>
  </div>
</div>
4b9b3361

Ответ 1

В настоящее время невозможно использовать простой CSS. Однако это можно сделать с помощью JavaScript.
Не стесняйтесь пропустить снизу для демонстрации или прочитать подробное объяснение.

Снимок экрана демонстрации.

Подход. Предлагаемое решение работает следующим образом. & Бык; События на временной шкале расположены абсолютно в контейнере временной шкалы.
& Бык; JavaScript определяет положение событий на временной шкале и, в конце концов, высоту контейнера временной шкалы. Это устанавливается с использованием встроенных стилей и класса, указывающего, осталось ли событие или справа от временной шкалы. Класс можно использовать для добавления стрелок, например.
& Бык; CSS определяет размер и внешний вид событий и временную шкалу.

Требования. В этом вопросе указаны следующие требования.
R1. Временная шкала не должна просто изменять события слева и справа.
R2. Если необходимо, левое и правое события должны заполнить оставшееся пространство.
R3. Порядок элементов должен быть таким же, как в предоставленной структуре DOM.

Алгоритм позиционирования. События располагаются сверху вниз по временной шкале в порядке их появления в DOM (R3). Алгоритм отслеживает нижнюю часть последнего события с каждой стороны, например B L и B R. Чем дальше будет размещено событие, тем выше будут эти цифры, поскольку они фиксируют смещение от вершины временной шкалы.

Предположим, что мы хотим позиционировать событие E. Мы решили разместить его на стороне, где дно минимально. Таким образом, он определяется с помощью min (B L, B R). Это реализовано в функции min в демо. Затем мы поместим E как можно ближе к верхней части временной шкалы, оставив ее в правильном положении. Правильная позиция такова, что никакое другое событие не перекрывается, а центр события (точка, где он привязан к временной шкале) должен наступить после предыдущей точки на временной шкале. Пусть E '- последнее событие на противоположной стороне от места размещения E. Пусть P - последнее событие на той же стороне, что и E.

Достаточно проверить три позиции, а именно следующее.
1. Совместите верхнюю часть E с нижней частью E '. Это всегда действительная позиция, но не очень хорошая для R2.
2. Совместите центр E как можно ближе к центру E '. Это лучше для R2, ​​но не является допустимым положением, когда верхняя часть E теперь перекрывает P (верхняя часть E меньше, чем нижняя часть P).
3. Совместите верхнюю часть E как можно ближе к нижней части P. Это лучшее для R2, ​​но не действительное положение, когда центр E теперь находится перед центром E '.

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

Стилизация, подробности и замечания. Я добавил некоторую CSS-разметку, чтобы проиллюстрировать, что можно сделать с временной шкалой. Эта разметка и код JavaScript должны быть совместимы с несколькими браузерами. Я не воздерживался от использования функций ES6 для максимальной совместимости, кроме Array.from, который по-прежнему имеет хорошую поддержку и polyfill для Internet Explorer на связанная страница, если необходимо. Код тестируется в последних версиях Firefox и Chrome.

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

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

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

// wrap in anonymous function to not pollute global namespace
(function() {

  // find the minimium element in an array, and the index of it
  function min(arr) {
    var ind = -1, min = false, i;
    for (i = 0; i < arr.length; ++i) {
      if (min === false || arr[i] < min) {
        ind = i;
        min = arr[i];
      }
    }
    return {
      index: ind,
      value: min
    };
  }

  // position events within a single timeline container
  function updateTimeline(timeline, minSpace = 10) {
    var events = Array.from(timeline.querySelectorAll('.event')),
        tops = [0, 0],
        bottoms = [0, 0],
        minTops = [0, 0];
    // determine height of container, upper bound
    timeline.style.height = events.reduce(function(sum, event) {
      return sum + event.offsetHeight;
    }, 0) + 'px';

    // position events in timeline
    events.forEach(function(event) {
      // find highest point to put event at
      // first put it with its top aligned to the lowest bottom - this will
      // always yield a good solution, we check better ones later
      var h = event.offsetHeight,
          y = min(bottoms),
          x = (y.index === 0 ? 0 : 50),
          b = y.value + h,
          m = (tops[1 - y.index] + bottoms[1 - y.index]) / 2;
      event.className = 'event';
      event.classList.add('event--' + (y.index === 0 ? 'left' : 'right'));
      // try to squeeze element up as high as possible
      // first try to put midpoint of new event just below the midpoint of
      // the last placed event on the other side
      if (m + minSpace - h / 2 > minTops[y.index]) {
        y.value = m + minSpace - h / 2;
        b = y.value + h;
      }
      // it would be even better if the top of the new event could move
      // all the way up - this can be done if its midpoint is below the
      // midpoint of the last event on the other side
      if (minTops[y.index] + h / 2 > m + minSpace) {
        y.value = minTops[y.index];
        b = y.value + h;
      }

      // update tops and bottoms for current event
      tops[y.index] = Math.ceil(y.value);
      bottoms[y.index] = Math.ceil(b + minSpace);
      minTops[y.index] = bottoms[y.index];

      // update tops and bottoms for other side, as applicable
      if (y.value + (h / 2) + minSpace > bottoms[1 - y.index]) {
        tops[1 - y.index] = bottoms[1 - y.index];
        minTops[1 - y.index] = bottoms[1 - y.index];
      }
      bottoms[1 - y.index] = Math.ceil(Math.max(
        bottoms[1 - y.index],
        y.value + (h / 2) + minSpace
      ));

      // put event at correct position
      event.style.top = y.value + 'px';
      event.style.left = x + '%';
    });

    // set actual height of container
    timeline.style.height = (Math.max.apply(null, bottoms) - minSpace) + 'px';
  }

  // position events within all timeline containers on the page
  function updateAllTimelines(minSpace = 10) {
    Array.from(document.querySelectorAll('.timeline'))
      .forEach(function(timeline) {
        updateTimeline(timeline, minSpace);
      });
  }

  // initialize timeline by calling above functions
  var space = 10;
  updateAllTimelines(space);
  window.addEventListener('resize', function() {
    updateAllTimelines(space);
  });

}());
/* line in center */
.timeline {
  position: relative;
  width: 100%;
}

.timeline:before {
  content: " ";
  display: block;
  position: absolute;
  top: 0;
  left: calc(50% - 2px);
  width: 4px;
  height: 100%;
  background: red;
}

/* events */
.event {
  position: absolute;
  top: 0;
  left: 0;
  box-sizing: border-box;
  width: 50%;
  height: auto;
  padding: 0 20px 0 0;
}
.event--right {
  /* Move padding to other side.
   * It is important that the padding does not change, because
   * changing the height of an element (which padding can do)
   * while layouting is not handled by the JavaScript. */
  padding: 0 0 0 20px;
}

/* discs on timeline */
.event:after {
  content: "";
  display: block;
  position: absolute;
  top: calc(50% - 5px);
  width: 0;
  height: 0;
  border-style: solid;
}
.event--left:after {
  right: 0;
  border-width: 10px 0 10px 20px;
  border-color: transparent transparent transparent #de2d26;
}
.event--right:after {
  left: 0;
  border-width: 10px 20px 10px 0;
  border-color: transparent #de2d26 transparent transparent;
}

/* event styling */
.event__body {
  padding: 20px;
  background: #de2d26;
  color: rgba(255, 255, 255, 0.9);
}
<div class="timeline">
  <div class="event">
    <div class="event__body">
      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus varius sodales purus, id gravida ipsum accumsan quis. Donec ultrices orci quis ex consequat mollis. Etiam in gravida enim. Etiam efficitur lorem id turpis auctor gravida. Ut condimentum dolor nibh, in hendrerit leo egestas at.
    </div>
  </div>
  <div class="event">
    <div class="event__body">
      Nam magna felis, malesuada vitae elementum sit amet, tempus sed eros. Etiam ullamcorper elementum viverra. Morbi dictum metus id nibh congue, at lacinia felis iaculis. Etiam pretium augue in erat lobortis viverra. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Suspendisse facilisis, velit vel placerat faucibus, massa est eleifend turpis, ac tempor dui turpis at mi. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.
    </div>
  </div>
  <div class="event">
    <div class="event__body">
      Mauris malesuada arcu sed lacus tristique, a eleifend sem egestas. Pellentesque at malesuada nisi.
    </div>
  </div>
  <div class="event">
    <div class="event__body">
      Nam et purus at elit vulputate molestie ac non eros. Proin mattis ligula velit, ut semper eros venenatis a. Vestibulum sagittis consectetur diam, molestie dapibus sem viverra a. Fusce felis augue, mollis id massa molestie, sagittis rutrum risus. In vel molestie elit, eget fringilla ante. Sed et elit blandit, tincidunt leo non, vehicula mi. Aenean tempus tincidunt eros vel tincidunt. Integer orci orci, gravida sit amet elit nec, luctus condimentum est.
    </div>
  </div>
  <div class="event">
    <div class="event__body">
      Pellentesque sodales ultrices sem, eget convallis ante condimentum id. Fusce ac turpis ac ex tincidunt malesuada a ac est.
    </div>
  </div>
</div>