Timeline 拖拽技术概述
Timeline 拖拽技术是一种交互式可视化工具,它允许用户通过拖动时间轴上的标记来展示、编辑和分享项目的时间线。这种技术广泛应用于各种领域,包括项目管理、视频编辑、音乐制作、教育、科学研究等。本文将详细介绍 Timeline 拖拽技术的实现原理、设计步骤和最佳实践,并提供一个基于 HTML5 和 JavaScript 的简单示例。
Timeline 拖拽技术实现原理
Timeline 拖拽技术的实现主要依赖于 HTML5 中的拖放 API 和 JavaScript。在 HTML5 中,每个元素都可以被拖动,这包括文本、图像、链接等。通过使用 JavaScript,我们可以捕获用户的拖动事件,并在拖动过程中更新时间轴上的标记位置。
具体实现步骤如下:
- 创建一个 HTML 时间轴结构,包括标记和时间段。
- 使用 JavaScript 监听鼠标的拖动事件,记录鼠标位置和时间戳。
- 根据鼠标位置和时间戳计算标记的新位置,并将其更新到时间轴上。
- 如果需要,还可以在标记上添加额外的信息或交互元素。
Timeline 拖拽技术设计步骤
- 确定时间轴的用途和目标用户。这将有助于确定时间轴的外观和功能需求。
- 设计时间轴的外观和交互元素。这包括标记、时间段、颜色、字体等。
- 使用 HTML 和 CSS 创建时间轴的静态版本。这可以帮助您更好地理解时间轴的结构和布局。
- 使用 JavaScript 实现拖动功能。您需要编写代码来捕获鼠标的拖动事件,并在拖动过程中更新时间轴上的标记位置。
- 测试和优化时间轴的功能和性能。这包括在不同浏览器和设备上的测试,以确保时间轴在各种情况下都能正常工作。
- 添加其他功能和交互元素,如缩放、滚动、标记编辑等。
- 最后,根据用户反馈和需求变化进行迭代和优化。
Timeline 拖拽技术最佳实践
- 设计易于使用的时间轴界面,避免过多的复杂功能和交互元素。
- 在设计时间轴时考虑可读性和可访问性,确保用户可以轻松地阅读和理解时间轴上的信息。
- 使用标准的 HTML5 和 CSS3 特性,避免使用不兼容或过时的技术。
- 在实现时间轴时考虑性能和可扩展性,避免在处理大量数据时出现性能问题。
- 提供灵活的时间轴配置选项,允许用户自定义时间轴的外观和功能。
- 在发布时间轴之前进行充分的测试和验证,确保时间轴的功能和性能符合预期。
- 提供有用的文档和示例,以便其他开发人员可以轻松地使用和扩展您的代码。
基于 vue3 示例
1.注册依赖项
yarn add vis-timeline vis-data moment
点击查看vis-timeline文档
import "vis-timeline/styles/vis-timeline-graph2d.min.css";
import { DataSet } from 'vis-data'; // 为timeline提供双向数据绑定,加快渲染速度
import { Timeline } from "vis-timeline"; //standalone,peer不同的包装方式
import moment from 'moment';
import "moment/dist/locale/zh-cn.js";
2.初始化
构造函数接受四个参数:
container
是在其中创建时间轴的 DOM 元素。items
是一个包含项的数组。的属性 项目在 数据格式、项目 一节中描述。groups
是一个包含组的数组。的属性 组在 数据格式,组 一节中描述。options
是包含名称-值映射的可选对象 有选项。也可以使用该方法设置选项setOptions
.
state.timeline = new Timeline(timelineRef.value as unknown as HTMLElement, dataList,
{
locale: 'zh-cn', //moment.locale('zh-cn'), // 时间轴国际化
orientation: 'top',
// height: '60.8vh', // 高度
min:'2023-05-12 08:00', // 设置时间轴可见范围的最小日期
max:'2023-05-12 19:00', // 设置时间轴可见范围的最大日期
onDropObjectOnItem: function(objectData, item, callback) {
if (!item) { return; }
alert('dropped object with content: "' + objectData.content + '" to item: "' + item.content + '"');
},
editable: props.editable ? {
add: true, // 双击添加新项-add new items by double tapping
updateTime: true, // 水平拖拉项目-drag items horizontally
updateGroup: true, // 从一个分组拖拽到另一个分组-drag items from one group to another
remove: true,
} : false,
template: (sourceData:any, targetElement:any, parsedData:any)=> {
let start = moment(parsedData.start).format('HH:mm')
let end = moment(parsedData.end).format('HH:mm')
targetElement.className = 'custom-item-template-class'; // 将自定义class写在className属性中
return ` <div class="item-top">
</div>
<div class="item-content">
<span>
${start}
-${end}
</span>
</div>
<div class="item-bottom">
</div>
`;
},
timeAxis: {
scale: 'minute',
step: 30
},
autoResize:false,
type:'range',
margin: {
item: 10, // minimal margin between items
axis: 5 // minimal margin between items and the axis
},
stack: true, // ture则不重叠
zoomMax: 1000 * 60 * 60 * 28,
zoomMin: 1000 * 60 * 1,
verticalScroll: props.editable, // 竖向滚动
moveable: props.editable, // 禁止缩放
// zoomFriction:500,
horizontalScroll: props.editable,
moment: function(date:any) {
return moment(date).locale('zh-cn'); //moment(date).utcOffset('+08:00');
},
// 显式将此选项设置为true以完全禁用Timeline的XSS保护
xss: {
disabled: true,
},
//可以提供模板处理程序。(或许可以直接放插槽?待测试)
//此处理程序是一个函数,接受项的数据作为第一个参数,项元素作为第二个参数,编辑后的数据作为第三个参数,并输出格式化的HTML:
tooltipOnItemUpdateTime: {
template: (originalItemData:any) => {
return `<div>
<p>
<span>开始时间:</span>
<span>${moment(originalItemData.start).format('HH:mm')}</span>
</p>
<p>
<span>结束时间:</span>
<span>${moment(originalItemData.end).format('HH:mm')}</span>
</p>
</div>`
}
},
// onAdd(item, callback)在将要添加新项时触发。如果未实现,将使用默认文本内容添加该项。
onAdd: (originalItemData:any, callback:any) => {
// console.log('新增originalItemData: ', originalItemData);
if (originalItemData.id) {
originalItemData.customClassName = 'un-submit'; // 未提交状态的样式
callback(originalItemData); // 成功返回 这行相当于调用了dataList.add(originalItemData)
}
else {
callback(null); // 失败取消
}
},
onUpdate: function (item:any, callback:any) {
if (item.id) {
dblclickBar(item); // 打开弹窗
}
else {
callback(null); // cancel updating the item
}
},
onMove: function (item:any, callback:any) {
// item.moving = true;
item.start = fomartTime( item.start,5)
item.end = fomartTime( item.end,5)
callback(item);
},
// 当项目被移动时重复触发的回调函数。仅在selectable和editable.updateTime或editable.updateGroup选项都设置为true时才适用
onMoving: function (item:any, callback:any) {
// console.log(item,'移动中')
// state.timeline.setItems(...item);
item.moving = true;
callback(item);
},
}
nextTick(async () => {
// if(state.timeline){
// state.timeline.setItems([], { clearNetwork: false });
// state.timeline.destroy(); // 销毁时间轴
// }
// // await getOperationRoom();
await renderTimeLine(); // 渲染时间轴
state.timeline.redraw();
});
3.更新数据
const data = await getAccountList({page:1,pageSize:16})
// 开始
const min = data.records[0].startTime
// 结束
const max = data.records[0].endTime
data.records[0].timeline.forEach(item=>{
dataList.add({
start: item.myBeginDate,
end:item.myEndDate,
id:item.ganttBarConfig.id,
group:item.group,
info:item.info,
editable:item.editable,
})
})
let groups = new DataSet()
const gourpList = ['0','31001','31002','31003','9','1','2','3']
for (var i = 0; i < gourpList.length; i++) {
const item = gourpList[i]
groups.add({
id:item,
value: i + 1,
content: `<div style="width:80px;">
${item.length > 2 ? item : ' '}
</div>`
})
}
// 更新配置选项
state.timeline.setOptions({
// min, // 设置时间轴可见范围的最小日期
// max, // 设置时间轴可见范围的最大日期
groupEditable: props.editable,
groupOrder: function (a, b) {
return a.value - b.value;
},
groupOrderSwap: function (a, b, groups) {
var v = a.value;
a.value = b.value;
b.value = v;
},
groupTemplate: (groupData:any, element:any) => {
// console.log(groupData)
element.className = 'custom-group-template-class'; // 将自定义class写在className属性中
return `<div class="group" style="width:80px;" >
${groupData.content}
</div>`;
},
});
// 设置分组
state.timeline.setGroups(groups);
state.timeline.setItems(dataList);