A Javascript engine that allows arbitrary functionality to be triggered according to a given timeline provider. A timeline provider can be a video, an audio file, a request animation frame loop, etc.
The engine can be the basis for video annotations, presentation software or interactive infographics, for example.
Eligius is NOT a game or animation engine, instead, it is a Story Telling Engine.
Eligius is also the runtime for the Eligian language.
The main concepts in Eligius are timelines, actions and operations. A timeline can be played, paused, stopped and scrubbed through.
Actions can be called on specified points on such a timeline. An action consists of a list of start and end operations.
An operation is an atomic function. A context, for sharing data, is passed between each successive operation. Examples of operations are select-element, create-element or add-class. Here is a complete list of all available operations.
By composing these atomic operations complex functionality can be achieved.
An example of an action is adding and removing an image from the DOM at specific times. This action would consist of these start operations: select-element(to select the element that will act as the image's parent), create-element(to create the img element with the required src attribute) and set-element-contents(to add the img element to the previously selected parent).
The end operations would be: select-element(to select the parent again) and clear-element(to remove the img element).
A more comprehensive example could be, for example, a rich subtitling system for a videoplayer where the videoplayer acts as the timeline provider and Eligius is used to render the subtitles on top of it.
The final two puzzle pieces are the EngineFactory and EligiusEngine. The Factory will create instances of the engine and the engine orchestrates the timelines and executes the actions at the specified times.
The Eligius engine is populated by a JSON configuration. Think of this JSON as its file format. This configuration contains all of the timelines, actions, operations and other data for the engine to run. A JSON schema for this configuration can be found here.
This section describes the different parts of this configuration.
In the rest of this section the property name systemName will be shown often. The value of this property describes a named import.
Currently one resource importer is supported by default: EligiusResourceImporter.
The engine setting defines the engine for which this configuration is written. Currently there is only one option here, but this might change in the future. The engine block could, for example, also contain a version number.
{
...
"engine": {
"systemName": "EligiusEngine"
}
...
}
{
...
"timelineProviderSettings": {
"animation": {
"positionSource": {
"systemName": "RafPositionSource",
"tickInterval": 100
}
}
}
...
}
These settings describe the providers that are available for the different kinds of timelines. The tickInterval property controls the precision of timeline positions - a value of 100 (milliseconds) enables 0.1 second precision, allowing fractional positions like 1.3 or 5.7. Timeline providers are assembled from decomposed components:
Position Sources (required) - drive timeline position:
RafPositionSource - RequestAnimationFrame-based, for animation timelinesScrollPositionSource - Scroll-based, for scroll-driven timelinesVideoPositionSource - Video.js-based, for video timelines (requires video.js as a peer dependency)Container Providers (optional) - manage DOM elements where content is rendered:
DomContainerProvider - Manages a DOM element by selectorPlaylists (optional) - manage multi-item navigation:
SimplePlaylist - Basic playlist with array of itemsExample with all components:
{
"timelineProviderSettings": {
"animation": {
"positionSource": {
"systemName": "RafPositionSource",
"tickInterval": 100
},
"container": {
"systemName": "DomContainerProvider",
"selector": "#render-area"
},
"playlist": {
"systemName": "SimplePlaylist",
"items": [{ "uri": "/video1.mp4" }, { "uri": "/video2.mp4" }],
"identifierKey": "uri"
}
}
}
}
This is a jQuery selector that points to the root element where the engine will render its output.
{
...
"containerSelector": "#some-element-id"
...
}
Eligius aims to be fully multi lingual. This setting describes the different languages that are used within the current presentation.
{
...
"availableLanguages": [
{
"code": "en-US",
"label": "English"
},
{
"code": "nl-NL",
"label": "Nederlands"
}
]
...
}
This defines the default language, the value needs to equal one of the code values in the availableLanguages properties.
{
...
"language": "en-US"
...
}
This can contain a string representing a block of HTML that will be rendered into the element defined by container selector. This HTML should contain the main (or initial) layout for the presentation.
{
...
"layoutTemplate": "<div class=\"some-class\"><section><header>My header</header></section><div class=\"main\"></div></div>"
...
}
The actions defined in this array will be executed during initialisation and tear down of the engine. During initialisation the start operations are executed and during tear down the end operations. Use these, for example, to load data, render global UI, etc.
{
...
"initActions": [
...
{
"name": "some name",
"startOperations": [
...
{
"systemName": "selectElement",
"operationData": {
"selector": "#main-title"
}
}
...
],
"endOperations": [...]
}
...
]
...
}
This is a list of actions that are not directly associated with initialisation, tear down or time line positions. These can be executed from within another action.
{
...
"actions": [
...
{
"name": "some name",
"startOperations": [
...
{
"systemName": "selectElement",
"operationData": {
"selector": "#main-title"
}
}
...
],
}
...
]
...
}
This block defines a list of timelines with its associated actions. This part of the configuration contains the brunt of a presentation since this defines all of the actions that are triggered along each timeline position.
Timeline positions support fractional values (e.g., 1.3, 5.7) when using a tickInterval of 100 (0.1 second precision).
{
...
"timelines": [
{
"type": "animation",
"uri": "animation-01",
"duration": 45.5,
"loop": true,
"selector": ".timeline-div",
"timelineActions": [
...
{
"name": "ShowSomething",
"duration": {
"start": 1.3,
"end": 3.7
},
"startOperations": [...],
"endOperations": [...]
}
...
]
}
]
...
}
This a list of actions that can be triggered by an event broadcast. Since an event doesn't have a tear down phase, these actions can only have a list of start operations.
{
...
"eventActions": [
...
{
"name": "some name",
"startOperations": [...],
}
...
]
...
}
A list of label information that describes all of the textual content for the presentation.
Labels can be rendered by the LabelController. This controller listens for the appropriate language change events as well, and will change the contents of the element it controls accordingly.
{
"labels": [
{
"id": "mainTitle",
"labels": [
{
"code": "en-US",
"label": "This is the main title"
},
{
"code": "nl-NL",
"label": "Dit is de hoofdtitel"
}
]
}
]
}
Running the engine is a matter of loading the configuration and feeding it to an engine instance.
import { IEngineConfiguration, EngineFactory, EligiusResourceImporter } from 'eligius';
import * as engineConfig from './my-eligius-config.json';
const factory = new EngineFactory(new EligiusResourceImporter(), window);
// createEngine returns { engine, languageManager, eventbus, destroy }
const { engine, destroy } = factory.createEngine((engineConfig as unknown) as IEngineConfiguration);
await engine.init();
console.log('Eligius engine ready for business');
// When done, clean up everything
await destroy();
The engine exposes a type-safe event API:
// Subscribe to engine events directly
const unsubscribe = engine.on('time', (position) => {
console.log('Current position:', position); // e.g., 5.3, 5.4, 5.5...
});
engine.on('start', () => console.log('Playback started'));
engine.on('pause', () => console.log('Playback paused'));
engine.on('timelineComplete', () => console.log('Timeline finished'));
// Unsubscribe when done
unsubscribe();
The engine exposes read-only properties:
engine.position // Current timeline position in seconds (supports fractional values like 5.3)
engine.duration // Timeline duration (number | undefined)
engine.playState // 'stopped' | 'playing' | 'paused'
engine.currentTimelineUri // Current timeline URI (string)
engine.container // Timeline container element (JQuery)
engine.engineRoot // Engine root element (JQuery)
await engine.start(); // Start playback
engine.pause(); // Pause playback
engine.stop(); // Stop and reset to beginning
await engine.seek(10.5); // Seek to position (seconds, supports fractional values)
await engine.switchTimeline('timeline-uri', 5.3); // Switch timeline, optionally at position
Obviously having to type all of that JSON by hand can be a tad overwhelming and time consuming. Check out the ConfigurationFactory for a strongly typed, fluent and extensible API to construct a configuration programmatically.
It is encouraged to use this API to build specialized DSL's for specific presentations.
Also, there is the Eligian project that aims to become an actual programming language for the Eligius runtime.
Check out this link for the full type docs
npm install eligius
or
yarn add eligius
npm installnpm run test to run the unit tests (tests use Vitest)npm run build to create a production library bundle