@elah/ 时间线
Elah视频引擎的反应时间轴 UI. 轨迹、剪辑、尺子和弹头——并用电线将拖放、修剪、擦除、放大和手势投入 @elah/core 发动机呼叫.
消费者 @elah/core没有项目状态——所有州都住在核心商店.
安装
npm install @elah/timeline @elah/core @elah/react lucide-react
同伴依赖性 : react, (中文(简体) ), react-dom 18岁, lucide-react 0.400.0 (直接使用于
TrackRow编号 :ClipBlock 用于剪辑图标 — 即使应用程序中没有其它内容, 也要安装它
需要它吗? 引擎/回放上下文( E)EditorContext, (中文(简体) ), useTimelineEngine)来自
@elah/react,自动拖入的运行时间依赖(@elah/timeline 取决于
@elah/core@^1.0.0 财务报告和审定财务报表 @elah/react@^1.0.0) (中文(简体) ).
版本和稳定性: 1.0.0. 出口的API跟随铸币局 @elah/timeline 实值
锁定后释放 @elah/core, (中文(简体) ), @elah/react 财务报告和审定财务报表 @elah/editor (所有1.0),所以请保持
四个在同一个版本。 AI - Faccing (英语) @elah/core 条目(@elah/core/spec, (中文(简体) ), /html, (中文(简体) ),
/ops, (中文(简体) ), /icons活在1.0中,并跟随铸币局 @elah/core 只有根这样
根会保持小。 此软件包不导入它们 。 参见《稳定》部分
变动情况。 。 。 .
套装大小 : 仅UI 层( 项目状态存在于 @elah/core页:1 clsx 财务报告和审定财务报表
tailwind-merge。在 1.0.0 与 node scripts/measure-bundle.mjs (建造、改良、,
g; react, (中文(简体) ), react-dom, (中文(简体) ), lucide-react 财务报告和审定财务报表 mediabunny 外部 :
| 应用程序导入 | gz 时速 |
|---|---|
编辑器应用程序 : EditorProvider, (中文(简体) ), Preview, (中文(简体) ), Timeline 从 @elah/editor | 112.0 基贝 |
@elah/core 仅限引擎( e)TimelineEngine, (中文(简体) ), resolveTimeline(中文(简体) ) | 44.7 基贝 |
@elah/core 发动机 + GpuRenderer + 键 exportVideo | 75.0基贝 |
@elah/timeline 枪管,每个出口都保留,加 clsx 财务报告和审定财务报表 tailwind-merge | 21.6 KiB(14.9 KiB自有代码) |
前三起是真正的应用程序在启动时支付的费用;后一起是无法接触到应用程序的最坏情况. 可选的渲染器特性(效果、发电机、序列夹、旋转口罩、音频增强) 并不属于这些数字. Bundle 战略.md 解释方法和每行包括什么.
剪切类型( 1. 0.0) : 时间线绘制每个剪辑类型 @elah/core ——视频,音频,文本,图像,形状,自由手,以及更新的调整,生成器,化合物和序列剪接等,以被标记的块来显示. 您可以移动、 修剪、 拆分、 复制并粘贴( 打印用途) engine.cloneClip) (中文(简体) ). 任意类型的选中剪辑显示其密钥框 键框道。 。 。 . @elah/editor 仍然没有编辑新剪辑类型的UI(没有检查器,曲线编辑器,效果堆栈或复合编辑器):通过引擎,操作或VideoSpec驱动. 见 现值 升起.md。 。 。 .
构成部分
| 构成部分 | 说明 |
|---|---|
Timeline | 主要组成部分——道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道道 拖曳来移动、 边缘拖曳来修剪、 单击或拖曳标尺来擦除 |
KeyframeLane | 沿着所选剪辑的下边缘键框方块 . Timeline 为你挂起它,你看 键框道 |
尺子、轨迹、剪接块和弹头是 Timeline: 它们没有被导出。 把它们换成 classNames 螺旋桨和 --elah-* 变量(下文).
快开始
Timeline 从 React 上下文读取引擎和播放时钟——它没有
engine 道具 (英语). 把它包起来 EditorContext.Provider 从 @elah/react (或使用)
EditorProvider 从 @elah/editor为你做这个:
import { Timeline } from '@elah/timeline'
import { TimelineEngine, PlaybackEngine } from '@elah/core'
import { EditorContext } from '@elah/react'
const engine = new TimelineEngine({ fps: 30, stage: { width: 1920, height: 1080 } })
const playback = new PlaybackEngine({ fps: 30, getTotalFrames: () => engine.getTotalFrames() })
function App() {
return (
<EditorContext.Provider value={{ engine, playback }}>
<Timeline fps={30} style={{ height: 300 }} />
</EditorContext.Provider>
)
}
EditorContext 只供应引擎配对——确实如此 没有 有线 engine
在Zustand中的事件 照亮了道路 <EditorProvider> 是的,所以一个赤裸的
EditorContext.Provider 像这样需要自己的 engine.on('change', ...) →
tracksStore.sync(...) 接线( 见 @elah/editor是因为 EditorProvider 来源)或
uI不会更新。 偏爱 EditorProvider 除非您特别需要一个
自定义存储同步策略 .
仓库 Timeline 改为:tracksStore, (中文(简体) ), playbackStore, (中文(简体) ), selectionStore, (...) (中文(简体) )
是模块级单数 @elah/core - 只有一个引擎状态可以
一次照在他们身上 二号 <Timeline> 由两棵树支撑
同一页的不同引擎会让它们为同一商店而战
@elah/core读取 详细情况.
样式
一次导入已编译的样式表( 平面 CSS — Tailwind 是) 没有 所需数量 您的应用程序, 并且没有用户类 泄露到您的全球范围 :
import '@elah/timeline/styles.css'
import '@elah/editor/styles/tokens.css' // --elah-* defaults (standalone use)
tokens.css 船舶在 @elah/editor,该软件包不依赖于:
npm install @elah/editor 或定义 --elah-* 变量本身
内部 .elah-root (见 THEMING.md 在此包中).
颜色由 --elah-* CSS变量. 重温主题,在你的
拥有 .elah-root 范围——见 设计 -- -- md。 。 。 .
行为超越—— classNames
对特定时间段的一次性造型,通过 classNames ——每秒地图
尾风课. 无论你通过什么 胜过内置课程(tailwind-merge) (中文(简体) ).
<Timeline
classNames={{
root: 'rounded-xl',
ruler: 'bg-zinc-900',
rulerTick: 'bg-zinc-700',
rulerLabel: 'text-zinc-500',
trackLabel: 'bg-zinc-900',
lane: 'bg-zinc-950',
clip: 'rounded-lg shadow-lg', // shape/shadow (all clip types)
// per-type clip color — body + accent are separate slots:
clipVideo: 'from-sky-400 to-sky-600', // body (gradient or solid bg-*)
clipVideoAccent: 'text-sky-300', // stripe + selection + track bar
playhead: 'text-rose-500',
}}
/>
公约:
- 背景情况 (尺子、车道、标签、剪接体)
bg-*或渐变 停车( e)from-… to-…;为您添加了梯度方向). - 分数 (玩家头,剪条纹/选取,音轨标签栏)
currentColor,所以重新涂上他们text-*课.
槽数 : root, (中文(简体) ), ruler, (中文(简体) ), rulerTick, (中文(简体) ), rulerLabel, (中文(简体) ), track, (中文(简体) ), trackLabel, (中文(简体) ), lane, (中文(简体) ),
clip, (中文(简体) ), clip{Video,Audio,Text,Image}, (中文(简体) ), clip{Video,Audio,Text,Image}Accent, (中文(简体) ),
playhead见 TimelineClassNames 类型.
键框道
选中的剪贴板显示其下边缘的密钥框为钻石 。 这是开源
编辑器的视觉密钥框编辑; Timeline 使其在选中范围内 ClipBlock这样
没有什么可以监听的.
- 点击 一个钻石来选择它 并寻找它的玩家.
- 拖动 重排时间 抓到的钥匙扣到其他钥匙 剪辑是第一个也是最后一个
边框和播放头
playbackStore.snapEnabled开始 整个拖累是一体的 撤销步骤( e)Move keyframes(三); 英格兰 取消这个. - Shift+ 单击 将钻石添加到选取中或取出. 拖动选定的钻石 将每个选定的密钥以相同数量移动。 未选中的密钥, 只需单击即可 单独选择它.
- 握取键框画作正方形; 键盘被左向修剪推到剪辑外 剪辑边并无法被拖动; 在远缩放时, 键比几个像素折叠成 一个集群标记(单击求取其第一个密钥,在您放大前不能拖动) .
- 在锁定的音轨上,您仍然可以选择和寻找,但不能移动 .
KeyframeLane 用于显示自己剪辑UI的主机。 这需要
EditorContext 提供者和一个绝对位置的父公司( 它填补了父公司的底部)
KEYFRAME_LANE_HEIGHT 像素, 14 个.
import { KeyframeLane, KEYFRAME_LANE_HEIGHT, type KeyframeLaneProps } from '@elah/timeline'
| Prop | 类型 | 含义 |
|---|---|---|
clip | Clip | 要画出钥匙的夹子. Keyframe.frame 是剪接相对的,所以 left = frame * zoom |
zoom | number | 每帧像素 |
locked | boolean (可选) | 导航但不编辑 |
channels | ReadonlyArray<AnyKeyframeChannel> (可选) | 只显示这些频道的密钥 。 显示每个频道的结合 |
选择的密钥在 useKeySelectionStore和剪辑分开的一栋露天店
选择( e)useSelectionStore) (中文(简体) ). 它每次持有一个剪辑的密钥: 从另一个键中选择一个密钥
剪辑取代选择,并取消剪辑的选择.
import { useKeySelectionStore, keysOf } from '@elah/timeline'
const clipId = useKeySelectionStore(s => s.clipId) // clip the selected keys belong to, or null
const frames = useKeySelectionStore(s => s.frames) // ReadonlySet<number> of clip-relative frames
useKeySelectionStore.getState().select(clipId, [12, 30]) // also: toggle, shift(delta), clear
keysOf(clipId) // non-reactive read: the set for that clip, else empty
钩子
import { useTracks, usePlayback, useSelection } from '@elah/timeline'
const tracks = useTracks(s => s.tracks)
const isPlaying = usePlayback(s => s.isPlaying)
const selectedClipIds = useSelection(s => s.selectedClipIds)
这些别名是: useTracksStore, (中文(简体) ), usePlaybackStore 财务报告和审定财务报表 useSelectionStore 从 @elah/react· ;
保持选择器的窄度( e)usePlayback(s => s) 重新对每段回放框进行回复).
useTimeline() 返回时 TimelineEngine 从上下文. useKeySelectionStore (见
键框道)是这个软件包拥有的一家商店.
拖放( D)
将媒体投放处理器附加到任何轨道道元素中. useTimelineDrop(trackId, lane)
将轨道ID和车道DOM节点定位,并读取引擎
页:1 EditorContext (所以它必须运行在提供者内部). 接线员说
副作用并返回车道的拖动状态, TimelineDropState数字 :
'valid' (相容的拖曳徘徊), 'invalid' (不兼容类型或锁定的音轨)
或者说 null (不拖放),用于突出显示投放目标 :
import { useState } from 'react'
import { useTimelineDrop } from '@elah/timeline'
function Lane({ trackId }: { trackId: string }) {
// State, not useRef: the hook needs the node on a render after it mounts.
const [lane, setLane] = useState<HTMLDivElement | null>(null)
const dropState = useTimelineDrop(trackId, lane)
return <div ref={setLane} data-drop={dropState ?? undefined} />
}
将一个媒体资产从库拖入道中解析位置 startFrame (尊重缩放和snap).
在探测介质前插入
insertMediaAsset(engine, assetId, opts?) 财务报告和审定财务报表 insertElement(engine, payload, opts?) 放置不拖动的片段。 对于远程资产
从核心开始 beginImportUrl 插入(立即返回待决资产)
后退长度的剪辑,然后调用 growClipToAssetDuration 一次真实
期限是已知的。 他的同伴 beginImportUrl 用于插入剪辑的主机
在探测其介质之前:
import { growClipToAssetDuration } from '@elah/timeline'
// growClipToAssetDuration(engine, clipId, expectedFallbackFrames, newDurationSec)
growClipToAssetDuration(engine, clipId, fallbackFrames, durationSec)
它只在下个片段(从不产生重叠)之前就发展到空白,并留下一个 如果用户已经将其从倒数长度中剪切出来,则单独剪切.
缩放和多条视频轨道
这个 Timeline 裁判曝光 { engine, playback, fitToWindow, zoomAtAnchor }。 。 。 .
zoomAtAnchor(nextZoom) 瞄准目标时 瞄准目标
取景器中心,所以工具栏缩放按钮或滑动器从不将播放头卷走. 用这个
而不是生下来 setZoom。 。 。 .
时间线使任何视频音轨都成为了最上方的道道道复合材料. 仅 滚动的取景口内部的剪辑被挂起(外加一个边距),所以较长的时长仍然很便宜.