在 HTML/CSS/JS 中制作修复缩放时间轴
Making a fix scaled timeline in HTML/CSS/JS
抱歉,这会有点长...
一些上下文
作为一个更大项目的一部分,我正在尝试制作一个项目时间表,以包含在用于远程控制音乐制作程序(有兴趣的人称为 Reaper)的网页中。我试图让它显示当前播放位置、项目标记和项目区域。这些都是直接从程序的 API 提供的,获取信息没有问题。对于初学者,我只是想显示项目标记,但是我已经花了一个多星期的时间努力让它发挥作用。
这是软件内部的一个快速屏幕截图,用于说明我要模拟的内容:Reaper ruler screencap
通常我会为这样的事情或来自网络的数千个示例之一使用进度条,但是我无法知道项目的长度,因为软件没有限制它。结果,我转而使用每条 10 像素的固定比例。有点武断,我选择它是因为它最适合 120 bpm 的 5 分钟歌曲。暂时不担心外观,我只想让它工作哈哈。
我遇到的问题(代码包含在底部)是因为我对标记使用绝对定位以便将它们全部从屏幕左侧对齐,所以它们是从文档流中提取的,并且所以我不能将它们包装在父 div 中。最后,我打算将父 div 设置为 80% 宽度,并带有滚动条以查看其余标记,所以显然我做错了。但是,我似乎找不到与我想要实现的目标类似的任何代码片段。
所以这里是真正的问题:
我应该使用哪种 display/position/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
我们得到了一组定期轮询服务器并解析(明文)响应的函数。典型的响应如下所示:
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 语句来确定如何处理每一行。然后我构建了一个全局数组,其中包含项目中的所有标记以及我需要的信息。
您可以将 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;
}
或者您可以使用 <canvas>
to draw the timeline from Javascript (this is what I use for Song Switcher 的网络界面)。您还可以使用 GetProjectLength
API 函数获取项目长度(例如,通过调用脚本将长度放入临时 extstate,然后从 Web 界面读取它)。
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>
抱歉,这会有点长...
一些上下文
作为一个更大项目的一部分,我正在尝试制作一个项目时间表,以包含在用于远程控制音乐制作程序(有兴趣的人称为 Reaper)的网页中。我试图让它显示当前播放位置、项目标记和项目区域。这些都是直接从程序的 API 提供的,获取信息没有问题。对于初学者,我只是想显示项目标记,但是我已经花了一个多星期的时间努力让它发挥作用。
这是软件内部的一个快速屏幕截图,用于说明我要模拟的内容:Reaper ruler screencap
通常我会为这样的事情或来自网络的数千个示例之一使用进度条,但是我无法知道项目的长度,因为软件没有限制它。结果,我转而使用每条 10 像素的固定比例。有点武断,我选择它是因为它最适合 120 bpm 的 5 分钟歌曲。暂时不担心外观,我只想让它工作哈哈。
我遇到的问题(代码包含在底部)是因为我对标记使用绝对定位以便将它们全部从屏幕左侧对齐,所以它们是从文档流中提取的,并且所以我不能将它们包装在父 div 中。最后,我打算将父 div 设置为 80% 宽度,并带有滚动条以查看其余标记,所以显然我做错了。但是,我似乎找不到与我想要实现的目标类似的任何代码片段。
所以这里是真正的问题:
我应该使用哪种 display/position/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
我们得到了一组定期轮询服务器并解析(明文)响应的函数。典型的响应如下所示:
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 语句来确定如何处理每一行。然后我构建了一个全局数组,其中包含项目中的所有标记以及我需要的信息。
您可以将 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;
}
或者您可以使用 <canvas>
to draw the timeline from Javascript (this is what I use for Song Switcher 的网络界面)。您还可以使用 GetProjectLength
API 函数获取项目长度(例如,通过调用脚本将长度放入临时 extstate,然后从 Web 界面读取它)。
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>