Создание фиксированной шкалы времени в HTML / CSS / JS

dylan robins спросил: 12 мая 2018 в 04:48 в: javascript

Извините, это будет немного длинным ...

Немного контекста

Как часть более крупного проекта, я пытаюсь создать временную шкалу проекта включить в веб-страницу, используемую для дистанционного управления программой создания музыки (называемой Reaper для тех, кто интересуется). Я пытаюсь заставить его отображать текущую позицию игры, маркеры проекта и области проекта. Все они доступны прямо из API программы, без проблем получить информацию. Для начала я просто пытаюсь отобразить маркеры проекта, однако я уже больше недели вытягиваю свои волосы, пытаясь заставить его работать.

Вот быстрый скринкап изнутри программного обеспечения проиллюстрировать то, что я пытаюсь подражать: Reaper ruler screencap

Обычно я использовал индикатор прогресса для чего-то подобного или один из тысяч примеров из Интернета, однако у меня нет возможности узнать длина проекта, поскольку программное обеспечение не ограничивает его. В результате я вернулся к использованию фиксированной шкалы 10px за бар. Какой-то произвольный, я выбрал это как оптимальное для 5-минутной песни со 120 уд / мин. Не слишком беспокоиться о том, как искать момент, я просто хочу заставить его работать ха-ха.

Проблема, которую я имею (код включен внизу), заключается в том, что, поскольку я использую абсолютное позиционирование для маркеров чтобы выровнять их все слева от экрана, они вытягиваются из потока документов, и поэтому я не могу их обернуть в родительский div. В конце концов, я намерен установить родительский div на 80% ширину с помощью полосы прокрутки, чтобы увидеть остальную часть маркеров, поэтому, очевидно, я делаю это неправильно. Однако я не могу найти каких-либо закодированных фрагментов чего-либо, похожего на то, что я пытаюсь достичь.

Итак, вот вопрос:

Какой вид отображения / позиции / float CSS следует использовать для этого вместо position: absolute и float: left? Если мне нужно JS, чтобы сделать это, то как мне это сделать?

Спасибо за любую помощь, которую вы можете мне принести, будь то реальный код или просто подталкивание в правильном направлении!

Вот мой (релевантный) код:

index.html

<html>
    <body>
        <div id="timeline">
            <div id="labels"></div>
            <div id="markers"></div>
        </div>
    </body>
</html>

script.js:

// hardcoded for debugging purposes
// See section below about the API for info about how I get this data
var markers = [
    {label: "Start", pos: "20.00000000000000"},
    {label: "First", pos: "50.00000000000000"},
    {label: "Second", pos: "200.00000000000000"},
    {label: "Last", pos: "576.845412000000000"}
];function draw_markers(marker_list) {
    var label_html = "";
    var marker_html = "";    $.each(marker_list, function(index, obj) {
        var label = obj.label;
        var offset = parseFloat(obj.pos) * 7; // obj.pos is mesured in bars, not px        label_html = label_html +
                    "<span class='label' style='margin-left:"+offset+"px'>" +
                    label + "</span>";        marker_html = marker_html +
                    "<span class='marker' style='margin-left:"+offset+"px'>|</span>";
    });    document.getElementById("labels").innerHTML = label_html;
    document.getElementById("markers").innerHTML = marker_html;
}draw_markers(markers);

style.css:

    html, body {
    background: #eeeeee;
}#timeline {
    height: 4em;
    width: 100%;
    background-color: #9E9E9E;
}#labels {
    border-bottom: 1px solid black;
    height: 50%;
    width: 100%;
}#markers {
    height: 50%;
    width: 100%;
}.label {
    position: absolute;
    float: left;
}
.marker {
    position: absolute;
    float: left;
}

Об API

Нам предоставляется куча функций, которые регулярно проверяют сервер и анализируют (cleartext). Типичный ответ выглядит примерно так:

MARKERLIST_BEGINMARKER_LIST
MARKER \t label \t ID \t position
...
MARKER_LIST_END
TRANSPORT \t playstate \t position_seconds \t isRepeatOn \t position_string \t position_string_beats
...

Используя JS, я разделяю каждую строку и использую оператор switch, чтобы выяснить, что делать с каждой строкой. Затем я создаю глобальный массив, содержащий весь маркер в проекте, только с информацией, которая мне нужна.

2 ответа

Есть решение
cfillion ответил: 23 мая 2018 в 05:31

В качестве альтернативы вы можете использовать <canvas>, чтобы нарисовать временную шкалу из Javascript (это то, что я использую для веб-интерфейса Song Switcher). Также вы можете получить длину проекта с помощью функции API GetProjectLength (например, вызывая скрипт, чтобы поместить длину во временную extstate, а затем прочитать ее из веб-интерфейса).

function Timeline(canvas) {
  this.canvas  = canvas;
  this.ctx     = canvas.getContext('2d');
  this.length  = 0;
  this.markers = [];
}

Timeline.prototype.resize = function() {
  this.canvas.width  = this.canvas.clientWidth;
  this.canvas.height = this.canvas.clientHeight;
  
  this.scale = this.length / this.canvas.width;
}

Timeline.prototype.update = function() {
  this.resize();
  
  this.ctx.fillStyle = '#414141';
  this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
  
  this.ctx.textBaseline = 'hanging';
  
  for(var marker of this.markers)
    this.drawMarker(marker);
}

Timeline.prototype.drawMarker = function(marker) {
  const MARKER_WIDTH = 2;
  const FONT_SIZE    = 14;
  const PADDING      = MARKER_WIDTH * 2;
 
  var xpos = this.timeToPx(marker.pos);

  this.ctx.strokeStyle = this.ctx.fillStyle = 'red';
  this.ctx.lineWidth   = MARKER_WIDTH;

  this.ctx.beginPath();
  this.ctx.moveTo(xpos, 0);
  this.ctx.lineTo(xpos, this.canvas.height);
  this.ctx.stroke();

  if(marker.name.length > 0) {
    this.ctx.font = `bold ${FONT_SIZE}px sans-serif`;

    var boxWidth = this.ctx.measureText(marker.name).width + PADDING;
    this.ctx.fillRect(xpos, 0, boxWidth, FONT_SIZE + PADDING);

    this.ctx.fillStyle = 'white';
    this.ctx.fillText(marker.name, xpos + MARKER_WIDTH, PADDING);
  }
}

Timeline.prototype.timeToPx = function(time) {
  return time / this.scale;
}

var timeline = new Timeline(document.getElementById('timeline'));
timeline.length  = 30; // TODO: Fetch using GetProjectLength
timeline.markers = [
  {pos:  3, name: "Hello"},
  {pos: 15, name: "World!"},
  {pos: 29, name: ""},
];
timeline.update();

window.addEventListener('resize', timeline.update.bind(timeline));
#timeline {
  height: 50px;
  line-height: 50px;
  image-rendering: pixelated;
  width: 100%;
}
<canvas id="timeline"></canvas>
dylan robins ответил: 25 мая 2018 в 08:02
Вау, это именно то, что я искал, спасибо. Не могу поверить, что я не нашел ваш проект на форумах по жукам, я потратил достаточно много времени на поиск вещей! Я понятия не имею, как получить доступ к функции GetProjectLength из Интернета, хотя мне придется делать рытье. Спасибо хоть!
ответил: 12 мая 2018 в 06:41

вы можете использовать div s как display: inline-block и установить их ширину в процентах дельтах перекрывающихся абсолютных позиций временной шкалы:

function draw_markers(marker_list) {
    var label_html = "";
    var marker_html = "";
    var total = null;
    var prev = 0;    $.each(marker_list, function(index, obj) {
        var delta = parseFloat(obj.pos) - prev;
        obj.delta = delta;
        prev += parseFloat(obj.pos);
        total += delta;    })    $.each(marker_list, function(index, obj) {
        var label = obj.label;        var offset = parseFloat(obj.delta) / (total / 100); // obj.pos is mesured in bars, not px        label_html = label_html +
                    "<div class='label' style='width:"+offset+"%'>" +
                    label + "</div>";        marker_html = marker_html +
                    "<div class='marker' style='width:"+offset+"%'>|</div>";
    });    document.getElementById("labels").innerHTML = label_html;
    document.getElementById("markers").innerHTML = marker_html;
}draw_markers(markers);

CSS

html, body {
    background: #eeeeee;
}#timeline {
    height: 4em;
    width: 100%;
    background-color: #9E9E9E;
}#labels {
    border-bottom: 1px solid black;
    height: 50%;
    width: 100%;
}#markers {
    height: 50%;
    width: 100%;
}.label {
    position: relative;
    display: inline-block;
}
.marker {
    position: relative;
    display: inline-block;
}
dylan robins ответил: 13 мая 2018 в 10:39
Хорошая идея, однако есть несколько вопросов, о которых я могу думать. Во-первых, есть факт, что мой массив маркеров никоим образом не упорядочен, так что это испортит масштаб, а во-вторых, есть тот факт, что моя (будущая) игра головой может (и определенно будет) проходить мимо последнего маркера, так что здесь это закончится тем, что выкинет конец родительского div, которому я верю. Я изучаю использование холста, чтобы сделать весь рисунок, вместо того, чтобы бороться с позиционированием CSS. Я могу вернуться к этому позже, если не пойму, хотя, спасибо!