创建像SO Developer Story这样的垂直时间轴

JDC*_*JDC 18 html javascript css

我目前正在尝试制作一个垂直时间轴(就像在stackoverflow开发人员的故事中一样).

要求如下:

  • 时间线不应该只是改变左右项目而不是在对面留下空间(不是这样或者这样)
  • 如果需要,左右项应该填满剩余空间(就像这里一样,遗憾的是,一旦项目的内容变得更长,项目的顺序就不会被保留)
  • 项目的顺序应与提供的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;
}
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

这样可以很好地创建项目的简单对齐.


我需要的下一步是保持物品的顺序.

正如你可以在小提琴中看到的那样,物品只是粘在一起浮动顶部.不幸的是我不知道如何做到这一点.

我认为使用普通的CSS是不可能的,因此可能需要JavaScript来重新定位项目,但我不知道如何做,测量什么等等(我不是一个JavaScript人......).


是否有人已经这样做并可以分享他的经验?或者甚至有我找不到的图书馆?

谢谢你!

Jus*_*ent 32

目前使用普通CSS是不可能的.然而,它可以使用JavaScript完成.
您可以随意跳到底部进行演示,或继续阅读以获取详细说明.

演示的屏幕截图.

方法.我提出的解决方案的工作原理如下.
•时间轴上的事件绝对位于时间轴容器中.
•JavaScript确定事件在时间轴上的位置,最后确定时间轴容器的高度.这是使用内联样式设置的,以及指示事件是在时间轴的左侧还是右侧的类.例如,该类可用于添加箭头.
•CSS确定事件的大小和外观以及时间线.

要求.该问题陈述了以下要求.
R1.时间表不应仅仅改变左右事件.
R2.如果需要,左右事件应填满剩余空间.
R3.项目的顺序应与提供的DOM结构中的顺序相同.

定位算法.事件沿着时间轴从上到下定位,按它们在DOM(R3)中出现的顺序排列.该算法保持跟踪在每一侧上的最后一个事件的底部的,表示大号- [R .定位事件越往下,这些数字越高,因为它们记录了时间线顶部的偏移量.

假设我们希望事件位置Ë.我们选择将它放在底部最小的一侧.因此,由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,如果需要,它仍然有良好的支持和链接页面上的Internet Explorer的polyfill.该代码在我自己的最新版本的Firefox和Chrome中进行了测试.

代码未针对性能进行优化,但由于算法以线性时间运行,因此即使时间轴中存在许多事件,也不应运行此问题.该演示重新计算每个resize事件的布局,您可能希望在实时应用程序中限制它.

在下面的演示中,您将看到有一个参数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);
  });

}());
Run Code Online (Sandbox Code Playgroud)
/* 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);
}
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

最后.如果您遇到任何问题,请告诉我.

  • 哇,这就是我所说的真正好的答案!感谢您花时间和精力!这正是我所需要的! (5认同)