@andymcloid/trakk
v1.0.5
Published
Lightweight timeline editor web component for animations, video editing, and sequencing. Zero dependencies.
Maintainers
Readme
What is it?
A timeline editor for building animation tools, video editors, audio sequencers, or anything that needs time-based sequencing. Built as a native Web Component with zero dependencies.
Installation
npm install @andymcloid/trakkOr use directly via CDN:
<script type="module" src="https://unpkg.com/@andymcloid/trakk/dist/trakk.esm.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@andymcloid/trakk/dist/trakk.css">Usage
<trakk-editor id="timeline"></trakk-editor>
<script type="module">
import { Trakk } from '@andymcloid/trakk';
import '@andymcloid/trakk/css';
const timeline = document.getElementById('timeline');
timeline.setData([
{
id: 'track-1',
name: 'Audio',
blocks: [
{ id: 'block-1', name: 'Intro', start: 0, end: 5 },
{ id: 'block-2', name: 'Main', start: 6, end: 15 }
]
},
{
id: 'track-2',
name: 'Video',
blocks: [
{ id: 'block-3', name: 'Scene 1', start: 0, end: 10 }
]
}
]);
// Play/pause
timeline.play({ autoEnd: true });
timeline.pause();
// Listen for changes
timeline.addEventListener('change', (e) => {
console.log('Updated:', e.detail.tracks);
});
</script>API
Methods
| Method | Description |
|--------|-------------|
| setData(tracks) | Set timeline data |
| play(options?) | Start playback. Options: { autoEnd: boolean, toTime: number } |
| pause() | Pause playback |
| setTime(time) | Set current time position |
| getTime() | Get current time |
| getTotalTime() | Get total duration (end of last block) |
| setConfig(config) | Update configuration |
| selectAction(action, row?) | Select a block programmatically |
| deselectAction() | Clear the current selection |
| getSelectedAction() | Get currently selected block { action, row } or null |
| saveToLocalStorage(key?) | Save to localStorage |
| loadFromLocalStorage(key?) | Load from localStorage |
Configuration
timeline.setConfig({
scale: 1, // Seconds per scale unit
scaleWidth: 160, // Pixels per scale unit
scaleCount: 20, // Number of scale units
startLeft: 100, // Left margin (label column width)
rowHeight: 32, // Track height in pixels
autoScroll: true, // Auto-scroll during playback
hideCursor: false, // Hide playhead cursor
disableDrag: false, // Disable all dragging
allowOverlap: false // Allow blocks to overlap (default: false)
});Data Structure
interface Track {
id: string;
name?: string;
locked?: boolean; // Prevent editing
blocks: Block[];
}
interface Block {
id: string;
name?: string;
start: number; // Start time in seconds
end: number; // End time in seconds
locked?: boolean; // Prevent editing this block
}Events
// Data changed (drag, resize, create, delete)
timeline.addEventListener('change', (e) => {
console.log(e.detail.tracks);
});
// New block created via drag
timeline.addEventListener('itemcreated', (e) => {
console.log(e.detail.item, e.detail.row);
});
// Block deleted
timeline.addEventListener('blockdeleted', (e) => {
console.log(e.detail.block, e.detail.track);
});
// Track deleted
timeline.addEventListener('trackdeleted', (e) => {
console.log(e.detail.track);
});
// Block selected/deselected
timeline.addEventListener('select', (e) => {
if (e.detail) {
console.log('Selected:', e.detail.action, 'in track:', e.detail.row);
} else {
console.log('Selection cleared');
}
});
// Track/block renamed (double-click to edit)
timeline.addEventListener('trackrenamed', (e) => {
console.log(e.detail.track, e.detail.name);
});
timeline.addEventListener('blockrenamed', (e) => {
console.log(e.detail.block, e.detail.name);
});Engine Events
Access the playback engine directly:
timeline.engine.on('play', () => console.log('Playing'));
timeline.engine.on('paused', () => console.log('Paused'));
timeline.engine.on('ended', () => console.log('Ended'));
timeline.engine.on('setTimeByTick', ({ time }) => {
// Called every frame during playback
console.log('Current time:', time);
});Callbacks
For advanced control over interactions:
timeline.setCallbacks({
// Custom rendering
getActionRender: (block, track) => `<b>${block.name}</b>`,
getScaleRender: (time) => `${time.toFixed(1)}s`,
// Interaction hooks (return false to cancel)
onActionMoving: ({ action, start, end }) => {
if (start < 0) return false; // Prevent moving before 0
},
onActionResizing: ({ action, start, end }) => {
if (end - start < 0.5) return false; // Minimum 0.5s duration
},
// Click handlers (button: 0=left, 1=middle, 2=right)
onClickAction: (e, { action, row, time, button }) => {},
onDoubleClickAction: (e, { action, row, time }) => {},
onContextMenuAction: (e, { action, row, time, button }) => {
// Right-click on block - show custom context menu
},
onClickRow: (e, { row, time }) => {},
onClickTimeArea: (e, { time }) => {}
});Styling
Customize Trakk's appearance using CSS custom properties:
trakk-editor {
/* Core colors */
--trakk-bg: #191b1d; /* Background color */
--trakk-text: #ffffff; /* Text color */
--trakk-accent: #5297FF; /* Accent color (cursor, selection border) */
/* Block colors */
--trakk-block-bg: #2f3134; /* Block background */
--trakk-block-bg-hover: #3a3d40; /* Block hover state */
--trakk-block-bg-selected: #4a7ba7; /* Selected block background */
/* Borders */
--trakk-border: rgba(255, 255, 255, 0.1); /* Subtle borders */
--trakk-border-strong: rgba(255, 255, 255, 0.3); /* Prominent borders */
/* Text variations */
--trakk-text-muted: rgba(255, 255, 255, 0.6); /* Secondary text */
--trakk-text-subtle: rgba(255, 255, 255, 0.4); /* Subtle text/icons */
/* State */
--trakk-danger: #ff6464; /* Delete button hover */
--trakk-disabled-opacity: 0.6; /* Disabled elements */
--trakk-locked-opacity: 0.7; /* Locked elements */
}Light Theme Example
trakk-editor.light {
--trakk-bg: #f5f5f5;
--trakk-text: #1a1a1a;
--trakk-accent: #0066cc;
--trakk-block-bg: #e0e0e0;
--trakk-block-bg-hover: #d0d0d0;
--trakk-block-bg-selected: #b3d4fc;
--trakk-border: rgba(0, 0, 0, 0.1);
--trakk-border-strong: rgba(0, 0, 0, 0.2);
--trakk-text-muted: rgba(0, 0, 0, 0.6);
--trakk-text-subtle: rgba(0, 0, 0, 0.4);
}Demo
Open demo.html in a browser or check out the live demo.
License
MIT
