npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@linker-design-plus/timeline-track

v1.0.10

Published

A TypeScript-based video editing library with timeline and track functionality

Readme

@linker-design-plus/timeline-track

基于 TypeScript 和 Konva.js 的高性能视频编辑时间线库,支持拖拽、缩放、分割等核心功能,集成了完整的操作历史记录和日志系统。

核心功能

  • ✅ 交互式时间线,支持水平缩放(鼠标滚轮/触摸手势)
  • ✅ 视频轨道系统,支持多个片段的拖拽、调整大小、分割功能
  • ✅ 播放头指针,用于精确时间指示
  • ✅ 操作历史记录,支持撤销/重做功能
  • ✅ Canvas-based 渲染,确保高性能
  • ✅ 外部 API,用于片段管理、时间同步和播放控制
  • ✅ 片段碰撞检测,防止重叠
  • ✅ 片段边界限制,确保在轨道范围内
  • ✅ 原视频长度边界限制,确保剪辑不超出原视频范围
  • ✅ 日志系统,支持调试模式开关
  • ✅ 间隙移除功能,自动删除片段之间的间隙
  • ✅ 批量片段更新,提高性能
  • ✅ 历史记录变更通知事件,用于外部应用调整撤销/重做按钮状态
  • ✅ 播放倍速控制,支持 0.1x 到 10x 的播放速度
  • ✅ 轨道总时长计算,包含片段间隙
  • ✅ 封面系统,支持自定义缩略图提供器
  • ✅ 异步封面加载,支持 Promise 形式的封面获取

安装

npm install @linker-design-plus/timeline-track

快速开始

基本用法

import { TimelineManager } from '@linker-design-plus/timeline-track';

// 获取容器元素
const timelineContainer = document.getElementById('timeline-container');
const videoElement = document.getElementById('video-element') as HTMLVideoElement;

// 创建 TimelineManager 实例
const timelineManager = new TimelineManager({
  container: timelineContainer,
  debug: true, // 开启调试模式
});

// 连接到视频元素(可选)
timelineManager.connectTo(videoElement);

// 添加片段
const clipId = await timelineManager.addClip({
  src: 'sample-video.mp4',
  name: 'Clip 1',
  startTimeAtSource: 0, // 源视频中的开始时间(毫秒)
  duration: 5000, // 片段持续时间(毫秒)
  thumbnail: 'https://example.com/thumbnail1.jpg' // 可选:直接提供封面图片
});

// 开始播放
timelineManager.play();

// 设置播放倍速
timelineManager.setSpeed(2); // 2 倍速

// 适合缩放(自动调整缩放比例以适应所有片段)
timelineManager.fitZoom();

// 分割当前时间点的片段
timelineManager.splitCurrentClip();

// 移除片段之间的间隙
timelineManager.removeClipGaps();

// 监听历史记录变更事件
timelineManager.on('history_change', (event, data) => {
  console.log('History changed:', data);
  // 更新撤销/重做按钮状态
  updateUndoRedoButtons(data.canUndo, data.canRedo);
});

// 销毁时间轴管理器
// timelineManager.destroy();

封面系统用法

import { TimelineManager, ThumbnailProvider } from '@linker-design-plus/timeline-track';

// 创建缩略图提供器
const thumbnailProvider: ThumbnailProvider = {
  getThumbnail(clip) {
    // 同步获取封面
    return `https://example.com/thumbnails/${clip.id}.jpg`;
    
    // 或异步获取封面
    // return new Promise((resolve) => {
    //   // 模拟异步获取封面
    //   setTimeout(() => {
    //     resolve(`https://example.com/thumbnails/${clip.id}.jpg`);
    //   }, 100);
    // });
  }
};

// 创建 TimelineManager 实例时设置缩略图提供器
const timelineManager = new TimelineManager({
  container: timelineContainer,
  thumbnailProvider: thumbnailProvider
});

// 或动态设置缩略图提供器
timelineManager.setThumbnailProvider(thumbnailProvider);

// 添加片段时,会自动通过提供器获取封面
const clipId = await timelineManager.addClip({
  src: 'sample-video.mp4',
  name: 'Clip 1',
  duration: 5000
});

Vue 3 集成

<template>
  <div class="app">
    <div class="video-container">
      <video ref="videoElement" class="video" controls playsinline></video>
    </div>
    
    <div class="controls">
      <button @click="togglePlay">{{ isPlaying ? 'Pause' : 'Play' }}</button>
      <button @click="fitZoom">适合缩放</button>
      <button @click="removeClipGaps">移除间隙</button>
      <button @click="undo" :disabled="!canUndo">Undo</button>
      <button @click="redo" :disabled="!canRedo">Redo</button>
      <button @click="addClip">Add Clip</button>
      <button @click="splitClip">Split Clip</button>
    </div>

    <div class="timeline-container" ref="timelineContainer"></div>

    <div class="info-panel">
      <div>Current Time: {{ formattedTime }}</div>
      <div>Zoom: {{ zoom }} px/s</div>
      <div>Clips: {{ clipCount }}</div>
      <div>Status: {{ isPlaying ? 'Playing' : 'Paused' }}</div>
      <div>Speed: {{ speed }}x</div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from 'vue';
import { TimelineManager } from '@linker-design-plus/timeline-track';

// 容器引用
const timelineContainer = ref<HTMLDivElement | null>(null);
const videoElement = ref<HTMLVideoElement | null>(null);

// 状态
const isPlaying = ref(false);
const currentTime = ref(0);
const zoom = ref(100);
const clipCount = ref(0);
const canUndo = ref(false);
const canRedo = ref(false);
const speed = ref(1.0);

// TimelineManager 实例
let timelineManager: TimelineManager | null = null;

// 格式化时间显示
const formattedTime = computed(() => {
  const totalSeconds = Math.floor(currentTime.value / 1000);
  const hours = Math.floor(totalSeconds / 3600);
  const minutes = Math.floor((totalSeconds % 3600) / 60);
  const seconds = totalSeconds % 60;
  return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
});

// 初始化
onMounted(async () => {
  if (!timelineContainer.value) return;

  // 创建 TimelineManager 实例
  timelineManager = new TimelineManager({
    container: timelineContainer.value,
    debug: true,
  });

  // 连接到视频元素
  if (videoElement.value) {
    timelineManager.connectTo(videoElement.value);
  }

  // 添加事件监听器
  timelineManager.on('time_change', (event, data) => {
    currentTime.value = data.time;
  });

  timelineManager.on('play_state_change', (event, data) => {
    isPlaying.value = data.playState === 'playing';
  });

  timelineManager.on('zoom_change', (event, data) => {
    zoom.value = data.zoom;
  });

  timelineManager.on('history_change', (event, data) => {
    canUndo.value = data.canUndo;
    canRedo.value = data.canRedo;
  });

  timelineManager.on('speed_change', (event, data) => {
    speed.value = data.speed;
  });

  // 添加示例片段
  await addSampleClips();
  updateClipCount();

  // 初始设置
  zoom.value = timelineManager.getZoom();
  speed.value = timelineManager.getSpeed();
});

// 清理
onUnmounted(() => {
  timelineManager?.destroy();
});

// 控制方法
const togglePlay = () => timelineManager?.togglePlay();
const undo = () => timelineManager?.undo();
const redo = () => timelineManager?.redo();
const fitZoom = () => timelineManager?.fitZoom();
const removeClipGaps = () => timelineManager?.removeClipGaps();

// 添加片段
const addClip = async () => {
  if (!timelineManager) return;
  
  const clipId = await timelineManager.addClip({
    src: 'sample-video.mp4',
    name: `Clip ${Date.now()}`,
    duration: 5000,
    startTimeAtSource: 0
  });
  updateClipCount();
};

// 分割片段
const splitClip = () => timelineManager?.splitCurrentClip();

// 更新片段计数
const updateClipCount = () => {
  if (timelineManager) {
    clipCount.value = timelineManager.getClips().length;
  }
};

// 添加示例片段
const addSampleClips = async () => {
  if (!timelineManager) return;
  
  const clips = [
    {
      src: 'sample1.mp4',
      name: 'Clip 1',
      startTimeAtSource: 0,
      duration: 5000
    },
    {
      src: 'sample2.mp4',
      name: 'Clip 2',
      startTimeAtSource: 0,
      duration: 8000
    }
  ];

  for (const clip of clips) {
    await timelineManager.addClip(clip);
  }
  
  timelineManager.clearHistory();
};
</script>

<style scoped>
/* 样式省略 */
</style>

API 文档

TimelineManager

构造函数

new TimelineManager(config?: Partial<TimelineConfig>)

参数:

  • config:配置选项
    • container:容器元素
    • debug:是否开启调试模式,默认 false
    • duration:总时长(毫秒),默认 3600000
    • zoom:缩放比例(像素/秒),默认 100
    • currentTime:初始当前时间(毫秒),默认 0
    • playState:初始播放状态,默认 'paused'
    • speed:初始播放倍速,默认 1.0
    • thumbnailProvider:缩略图提供器,用于获取片段封面

核心方法

| 方法名 | 描述 | 参数 | 返回值 | |--------|------|------|--------| | play() | 开始播放 | 无 | 无 | | pause() | 暂停播放 | 无 | 无 | | togglePlay() | 切换播放状态 | 无 | 无 | | setCurrentTime(time) | 设置当前时间 | time:时间(毫秒) | 无 | | getCurrentTime() | 获取当前时间 | 无 | number | | setZoom(zoom) | 设置缩放比例 | zoom:缩放比例(像素/秒) | 无 | | getZoom() | 获取缩放比例 | 无 | number | | setSpeed(speed) | 设置播放倍速 | speed:播放倍速 | 无 | | getSpeed() | 获取当前播放倍速 | 无 | number | | setThumbnailProvider(provider) | 设置缩略图提供器 | provider:缩略图提供器 | 无 | | addClip(clipConfig) | 添加片段 | clipConfig:片段配置 | Promise<string>(片段 ID) | | removeClip(clipId) | 移除片段 | clipId:片段 ID | 无 | | removeSelectedClip() | 移除当前选中的片段 | 无 | boolean(是否成功) | | splitCurrentClip() | 分割当前时间点的片段 | 无 | 无 | | removeClipGaps() | 移除片段之间的间隙 | 无 | 无 | | fitZoom() | 自动调整缩放比例以适应所有片段 | 无 | 无 | | getClips() | 获取所有片段 | 无 | Clip[] | | getCurrentClip() | 获取当前时间点所在的片段 | 无 | Clipnull | | getSelectedClip() | 获取当前选中的片段 | 无 | Clipnull | | getTrackTotalDuration() | 获取轨道总时长(包含间隙) | 无 | number | | exportTimeline() | 导出时间轴数据 | 无 | TimelineExportData | | undo() | 撤销操作 | 无 | boolean(是否成功) | | redo() | 重做操作 | 无 | boolean(是否成功) | | clearHistory() | 清空历史记录 | 无 | 无 | | connectTo(video) | 连接到视频元素 | video:视频元素 | 无 | | on(event, listener) | 添加事件监听器 | event:事件类型,listener:事件监听器 | 无 | | off(event, listener) | 移除事件监听器 | event:事件类型,listener:事件监听器 | 无 | | destroy() | 销毁时间轴管理器 | 无 | 无 |

交互事件分层规范

  • 鼠标交互(时间轴拖拽、底部滑块拖拽、片段拖拽)采用统一分层策略,详见:
    • docs/interaction-model.md
  • 该文档用于约束后续重构,避免出现“移出画布中断拖拽”或“回到画布瞬移”等回归问题。

事件

| 事件名 | 描述 | 数据 | |--------|------|------| | time_change | 当前时间变化 | { time: number } | | play_state_change | 播放状态变化 | { playState: 'playing' \| 'paused' } | | speed_change | 播放倍速变化 | { speed: number } | | clip_added | 添加片段 | { clip: Clip } | | clip_removed | 移除片段 | { clipId: string } | | clip_updated | 更新片段 | { clip: Clip } | | clip_selected | 选择片段 | { clip: Clip } | | selected_clip_change | 选中片段变化(订阅后会立即回调当前状态) | { clip: Clip \| null, hasSelectedClip: boolean } | | zoom_change | 缩放比例变化 | { zoom: number } | | history_change | 历史记录变更 | { canUndo: boolean, canRedo: boolean } | | track_duration_change | 轨道总时长变化 | { duration: number } | | buffering_state_change | 视频缓冲状态变化 | { isBuffering: boolean } | | can_play_change | 是否可播放状态变化 | { canPlay: boolean } | | source_loading_change | 视频源加载状态变化 | { isLoading: boolean, pending: number } |

ClipConfig 接口

interface ClipConfig {
  src: string; // 视频源 URL
  name?: string; // 片段名称
  duration: number; // 片段持续时间(毫秒)
  startTimeAtSource?: number; // 源视频中的开始时间(毫秒)
  startTime?: number; // 片段在轨道上的起始时间(可选,自动计算)
  thumbnail?: string; // 缩略图 URL
}

开发指南

安装依赖

npm install

开发模式

npm run dev

构建

npm run build

预览构建结果

npm run preview

浏览器兼容性

  • Chrome (推荐)
  • Firefox
  • Safari
  • Edge

许可证

MIT

贡献

欢迎提交 Issue 和 Pull Request!

更新日志

v1.0.0-beta.1

  • 首次发布
  • 基于 TypeScript 和 Konva.js 构建
  • 实现核心视频编辑时间线功能
  • 支持片段拖拽、调整大小、分割
  • 集成操作历史记录系统
  • 支持播放倍速控制
  • 提供完整的外部 API
  • 包含 TypeScript 类型声明