SO Developer Storyのような垂直タイムラインを作成する質問する

SO Developer Storyのような垂直タイムラインを作成する質問する

現在、垂直タイムラインを作成しようとしています (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;
}
<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>

これは、アイテムの単純でシンプルな配置を作成するのに適しています。


次に必要なステップは、アイテムの順序を維持することです。

フィドルを見るとわかるように、アイテムはただくっついて上に浮かんでいるだけです。残念ながら、これをどうやって行うのか私にはわかりません。

プレーン CSS では不可能なので、アイテムの位置を変更するには JavaScript が必要になる可能性があると思いますが、その方法や測定対象などがまったくわかりません (私は JavaScript の専門家ではありません)。


すでにこれを実行した人で、経験を共有できる人はいますか? それとも、私が見つけられなかったライブラリもあるのでしょうか?

もうありがとうございます!

ベストアンサー1

現時点では、プレーン CSS ではこれは不可能です。ただし、JavaScript を使用すれば可能です。
デモをご覧になるには、一番下までスキップしてください。詳細な説明については、このまま読み進めてください。

デモのスクリーンショット。

アプローチ。私が提案する解決策は、次のように機能します。
• タイムライン上のイベントは、タイムライン コンテナー内に絶対的に配置されます。
• JavaScript は、タイムライン上のイベントの位置と、最終的にはタイムライン コンテナーの高さを決定します。これは、インライン スタイルと、イベントがタイムラインの左側にあるか右側にあるかを示すクラスを使用して設定されます。このクラスを使用して、たとえば矢印を追加できます。
• CSS は、イベントとタイムラインのサイズと外観を決定します。

要件。質問には次の要件が示されています。R1
. タイムラインは、左右のイベントを変更するだけではいけません。R2
. 必要に応じて、左右のイベントが残りのスペースを埋める必要があります。R3
. 項目の順序は、提供された DOM 構造と同じである必要があります。

位置決めアルゴリズム。イベントは、DOM (R3) に表示される順序で、タイムラインに沿って上から下に配置されます。アルゴリズムは、各側の最後のイベントの下部を追跡します。B LそしてB Rイベントが下の方に位置するほど、タイムラインの上からのオフセットが記録されるため、これらの数字は大きくなります。

イベントを配置したいとします底部が最小となる側に配置することを選択しました。最小値(B L , B R )これは関数で実装されていますデモでは、タイムラインのできるだけ上部に、有効な位置にあるように配置します。有効な位置とは、他のイベントと重ならず、イベントの中心(タイムラインに取り付けられるポイント)がタイムラインの前のポイントの後になる位置です。私たちが配置した場所の反対側の最後のイベントになります。 させて同じ側​​での最後のイベントとなる

次の3つの位置をテストするだけで十分です。1
.底部のこれは常に有効な位置ですが、R2にはあまり適していません。2
.の中心を揃えますできるだけ中心に近いこれはR2には適していますが、重複する(のトップ下より小さい3.
上部を揃えるできるだけ底に近いこれはR2にとって最適だが、今では中心の前に来る

アルゴリズムは、これらの位置を指定された順序でテストし、現在テストされている位置が有効な場合。この方法では、最も有効な位置が選択されます。

スタイリング、詳細、コメント。タイムラインで何ができるかを説明するために、CSSマークアップをいくつか追加しました。このマークアップとJavaScriptコードは、かなりクロスブラウザ互換性があるはずです。互換性を最大化するために、ES6の機能の使用は控えましたが、Array.fromは、引き続き十分なサポートが提供されており、必要に応じてリンク先のページに Internet Explorer 用のポリフィルが用意されています。コードは、最新バージョンの Firefox と Chrome で私自身がテストしました。

このコードはパフォーマンスに最適化されていませんが、アルゴリズムは線形時間で実行されるため、タイムラインに多くのイベントがある場合でも問題なく実行できます。デモでは、レイアウトが毎回再計算されます。サイズ変更イベントライブ アプリケーションではこれを調整する必要がある場合があります。

以下のデモでは、パラメータ があることがわかりますminSpace。これは、タイムライン上の 2 つの連続するポイント間、およびタイムラインの同じ側にある 2 つのイベント間に残されるピクセル単位のスペースです。デモでは、このパラメータにデフォルト値 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>

ついに。何か問題が発生した場合はお知らせください。

おすすめ記事