This guide is for developers extending impress.js, maintainers of the project, and architects who need to understand how the framework is built, how its plugin system works, and how to deploy and self-host presentations. For authoring presentations, see the companion User Guide.
Table of Contents
- Executive Summary
- Architecture Overview
- Repository Structure
- Core Engine Deep Dive (
src/impress.js) - The Library Layer (
src/lib/) - The Plugin System
- Plugin Reference (by type)
- The Build Pipeline
- Configuration Reference
- Deployment & Hosting
- Security Analysis
- Code Quality & Conventions
- Testing Strategy
- Writing a Plugin (extension recipe)
- Potential Improvements & Tech Debt
- Quick Reference
1. Executive Summary
impress.js is a client-side presentation framework written in dependency-free, ES5-style vanilla JavaScript. It transforms a tree of <div class="step"> elements positioned with data-* attributes into a zoomable, pannable, rotatable 3D presentation, driven entirely by CSS3 transforms/transitions and a small JavaScript engine.
| Aspect | Detail |
|---|---|
| Language | JavaScript (ES5-style, IIFE-wrapped, "use strict") |
| Runtime dependencies | None (no jQuery, no frameworks, no polyfills) |
| Distribution | Single concatenated file js/impress.js; CDN via jsDelivr; not on npm |
| Architecture | Tiny core + library layer + 22 opt-in default plugins |
| Configuration | HTML data-* attributes (no config file, no env vars) |
| Build | node build.js — concatenation + Terser minification |
| Effective version | 2.0.0 (CDN tag + Getting Started); package.json lags at 1.1.0 |
| License | MIT |
| Browser requirements | CSS 3D Transforms, CSS Transitions, DataSet API, ClassList API |
Note
There is no server component, database, runtime API, or deployment infrastructure in impress.js itself. "Deployment" means serving static HTML/JS/CSS files. This guide therefore focuses on the build pipeline, plugin architecture, and extension model rather than operations.
Important
Version inconsistency in the repo.
package.jsondeclares"version": "1.1.0", but the source header insrc/impress.jssaysversion: 2.0.0, the CDN serves a@2.0.0tag, andDOCUMENTATION.mdreferences av1.2.0default-resolution change. Treat 2.0.0 as the effective released version (matching the latest tag) and thepackage.jsonvalue as stale. Several open issues (#653 "push v1.0.0 to npm", #879 "Publish Library to NPM") reflect the unfinished release/publishing process.
2. Architecture Overview
impress.js is organized as four layers that the build concatenates into one file:
+-----------------------------------------------------------+
| js/impress.js |
| (generated build artifact) |
| |
| +-----------------------------------------------------+ |
| | Layer 1: CORE (src/impress.js) | |
| | - window.impress() factory -> API per root | |
| | - init / goto / next / prev / swipe / tear | |
| | - config + step data parsing from data-* attrs | |
| | - the canvas/root transform engine | |
| | - plugin + library registration hooks | |
| +-----------------------------------------------------+ |
| ^ |
| +-----------------------------------------------------+ |
| | Layer 2: LIBRARIES (src/lib/*.js) | |
| | gc.js (garbage collection / teardown) | |
| | util.js ($/$$/byId/triggerEvent/toNumber...) | |
| | rotation.js (quaternion math for rel plugin) | |
| | -> registered via impress.addLibraryFactory() | |
| | -> exposed to plugins as api.lib.* | |
| +-----------------------------------------------------+ |
| ^ |
| +-----------------------------------------------------+ |
| | Layer 3: PLUGINS (src/plugins/*/*.js) x22 | |
| | pre-init | pre-stepleave | init (event) plugins | |
| +-----------------------------------------------------+ |
+-----------------------------------------------------------+
| reads at runtime
v
Author's HTML: <div id="impress"> <div class="step" data-*>
|
v
Browser: CSS3 transforms on root (scale) + canvas (translate/rotate)
The camera model. impress.js never moves the slides. On init(), it wraps all steps inside a canvas div inside the root div. To "go to" a step, it computes the inverse of that step's transform (negated translate/rotate, reciprocal scale) and applies it to the canvas + root, so the target step lands centered and upright. The root element animates scale (zoom); the canvas element animates translate + rotate. They use staggered transition delays so zoom-in starts with movement and delays scaling, while zoom-out scales first — making transitions feel natural.
Two coordinate concerns split across two elements is the single most important architectural fact to internalize before reading goto().
3. Repository Structure
impress.js/
├── index.html Official demo (also the canonical, commented tutorial)
├── README.md Project overview, install, browser support
├── DOCUMENTATION.md Reference API (data-* attrs, CSS classes, JS API)
├── GettingStarted.md Beginner tutorial
├── npm-readme.md README variant for npm packaging
├── build.js Build script (concat + Terser minify + examples index)
├── package.json Build/test tooling spec (NOT a runtime dependency)
├── karma.conf.js Karma test runner config
├── qunit_test_runner.html Browser-based QUnit runner
├── css/
│ ├── impress-common.css OPTIONAL sample plugin CSS (help/toolbar/progress/...)
│ └── impress-demo.css Styling for the official demo
├── js/
│ └── impress.js GENERATED bundle (core + libs + all plugins)
├── src/ AUTHORED SOURCE (source of truth)
│ ├── impress.js Core engine
│ ├── lib/
│ │ ├── gc.js Garbage collection / teardown
│ │ ├── util.js DOM + numeric helpers
│ │ ├── rotation.js Quaternion rotation math (used by rel)
│ │ └── README.md
│ └── plugins/ 22 default plugins, one dir each (plugin.js + README.md)
├── examples/ Demo presentations (classic-slides, cube, 3D-rotations, ...)
├── extras/ Git submodule mount point for impress-extras (empty by default)
└── test/ QUnit/Syn tests for core + per-plugin tests
Warning
js/impress.jsis a build artifact, regenerated bybuild.js(cat src/impress.js src/lib/*.js src/plugins/*/*.js). Never hand-edit it — edit the files undersrc/and rebuild. The minifiedjs/impress.min.jsis not committed to the repo (it's generated at build time).
Note
Each plugin directory contains its own
README.md— these are the authoritative per-feature docs and are the basis for the User Guide's plugin catalog.
4. Core Engine Deep Dive (src/impress.js)
The core is a single IIFE ( function( document, window ) { ... } )( document, window ). Key internals:
Feature detection
On load, the core builds a pfx() vendor-prefix resolver (Modernizr-style) and computes impressSupported:
var impressSupported =
( pfx( "perspective" ) !== null ) && // CSS 3D transforms
( body.classList ) && // ClassList API
( body.dataset ); // DataSet API
If unsupported, it adds impress-not-supported to the body, and impress() returns a dummy API (all no-op functions) so author code calling .init() etc. won't throw.
The impress(rootId) factory
window.impress = function(rootId) returns (and caches) the API object for a given root id ("impress" by default). Each root gets its own closure with its own stepsData, activeStep, currentState, steps, windowScale, and a fresh set of library instances.
Defaults & config
var defaults = {
width: 1920, height: 1080,
maxScale: 3, minScale: 0,
perspective: 1000, transitionDuration: 1000
};
buildConfig() reads the root element's dataset and falls back to these defaults. impress.getConfig() exposes the resolved config (used by util.toNumberAdvanced to compute w/h relative units).
Step initialization
initStep(el, idx) reads each step's data-* attributes into a stepsData record:
step = {
translate: { x, y, z }, // via util.toNumberAdvanced (supports w/h units)
rotate: { x, y, z, order }, // data-rotate-z falls back to data-rotate
scale: toNumber(data.scale, 1),
transitionDuration: toNumber(data.transitionDuration, config.transitionDuration),
el
};
Steps without an id are auto-assigned step-N. initAllSteps() runs on every goto(), which is why you can mutate step coordinates at runtime and have them apply on the next transition.
The transform engine (goto)
goto(el, duration, reason, origEvent) is the heart of the engine:
- Re-reads all steps (
initAllSteps()). - Resolves the target via
getStep()(accepts index, id, or element; negative indices count from the end). - Runs
execPreStepLeavePlugins(event)— pre-stepleave plugins may redirect (event.detail.next), retime (transitionDuration), or abort (returnfalse). - Updates
.activeclasses and theimpress-on-{id}body class. - Computes the target canvas state as the inverse of the step transform (negated rotate/translate,
1/scale). - Determines
zoomindirection to choose the transition-delay strategy. - Applies CSS to
root(perspective + scale) andcanvas(rotate + translate) with staggeredtransitionDelay. - Fires
impress:stepenterafterduration + delayviasetTimeout(chosen overtransitionendfor simplicity across two animated elements).
Navigation helpers
prev(origEvent)/next(origEvent)walkstepsin document order, wrapping around at the ends.swipe(pct)is a semi-internal API used by the touch plugin to animate a partial swipe (interpolating between current and adjacent step states); committing the transition is the caller's responsibility. It is intentionally undocumented inDOCUMENTATION.md.
Events
The core triggers four custom events on the root: impress:init, impress:stepenter, impress:stepleave, impress:steprefresh. It also wires up:
- Step state classes (
future/present/past) on enter/leave. - Hash sync: writes
#/step-idon enter, listens forhashchangetogoto()the hashed step, and starts on the hashed step (or step 0) at init.
The returned API
{ init, goto, next, prev, swipe, tear, lib }
Plus static members: impress.supported, impress.addLibraryFactory, impress.addPreInitPlugin, impress.addPreStepLeavePlugin, impress.getConfig.
5. The Library Layer (src/lib/)
Libraries are registered with impress.addLibraryFactory({ name: factoryFn }) and instantiated per-root inside initLibraries(). They're exposed to plugins as api.lib.{name}. A name collision throws impress.js ERROR: Two libraries both tried to use libname: ....
gc.js — Garbage collection / teardown
The mechanism that makes tear() possible. Plugins register the DOM elements and event listeners they create so they can all be cleanly removed:
| Function | Purpose |
|---|---|
pushElement(el) |
Track an element for removal |
appendChild(parent, el) |
appendChild + track |
pushEventListener(target, type, fn) |
Track a listener |
addEventListener(target, type, fn) |
addEventListener + track |
pushCallback(fn) |
Register arbitrary teardown logic |
teardown() |
Run callbacks (LIFO), remove elements, remove listeners |
gc also records the DOM's "virgin" state at init (recordStartingState) — original step ids, the body's impress-not-supported class, and the viewport <meta> — and restores it on teardown (resetStartingState). This is what lets you tear() → modify → init() again without residue.
Tip
Any plugin that injects DOM or binds events must route them through
api.lib.gcsotear()works correctly. This is the most important convention for plugin authors.
util.js — DOM & numeric helpers
| Function | Purpose |
|---|---|
$ / $$ |
querySelector / querySelectorAll (the latter returns a real Array via arrayify) |
byId(id) |
getElementById |
getElementFromHash() |
Resolve the step from the URL hash (handles both #id and #/id) |
getUrlParamValue(name) |
Read a URL query parameter (used by autoplay's ?impress-autoplay=) |
throttle(fn, delay) |
Throttle (used by resize) |
toNumber(v, fallback) |
Safe numeric parse |
toNumberAdvanced(v, fallback) |
Like toNumber but resolves Nw/Nh screen-relative units against the config |
triggerEvent(el, name, detail) |
Dispatch a CustomEvent |
arrayify(a) |
Array-like → Array |
rotation.js — Quaternion math
A self-contained quaternion/vector library (dot/cross/triple products, quaternion multiplication, coordinate↔rotation conversion, bestRotation selection). Its public surface is a single translateRelative(relative, prevRotation) function. It exists to support the rel plugin's data-rel-position="relative" feature, which must compose rotations correctly so a slide can be positioned relative to the rotated frame of a previous slide.
6. The Plugin System
impress.js has three distinct plugin types, distinguished by when and how they hook into the core.
Type 1: Init plugins (event-driven)
The simplest and most common. They listen for impress:init on the document, grab the API from event.detail.api, and then bind their own behavior using the public API and api.lib.*. They never touch core internals.
document.addEventListener( "impress:init", function( event ) {
var api = event.detail.api;
var gc = api.lib.gc;
// ... bind keydown/keyup/click using api.next(), api.prev(), api.goto()
}, false );
Examples: navigation, navigation-ui, progress, help, blackout, fullscreen, impressConsole, mobile, resize, mouse-timeout, toolbar, form, media, touch, autoplay, bookmark.
Type 2: Pre-init plugins (attribute filters)
Registered via impress.addPreInitPlugin(fn, weight). They run synchronously at the very start of init(), before the core reads step attributes. This lets them rewrite data-* attributes that the core will then consume.
impress.addPreInitPlugin( rel ); // weight defaults to 10
weight(positive integer, default 10) controls ordering — lower runs first. Non-positive weight throws.- The canonical example is rel, which converts
data-rel-*into absolutedata-x/data-y/data-zbefore the core parses them.
Type 3: Pre-stepleave plugins (transition interceptors)
Registered via impress.addPreStepLeavePlugin(fn, weight). They run at the start of every goto(), before the impress:stepleave event. They receive the event object and can:
- Redirect the transition by changing
event.detail.next. - Retime it via
event.detail.transitionDuration. - Abort it entirely by returning
false.
Examples: goto (redirect to an out-link), skip (skip a step), stop (cancel forward motion), substep (intercept to reveal a sub-element instead of leaving).
Plugin registration summary
| Hook | Registration | Runs | Can do |
|---|---|---|---|
| Init | addEventListener("impress:init") |
After init | Bind behavior, add DOM/UI |
| Pre-init | impress.addPreInitPlugin(fn, weight) |
Start of init() |
Rewrite step attributes |
| Pre-stepleave | impress.addPreStepLeavePlugin(fn, weight) |
Start of goto() |
Redirect/retime/abort transitions |
| Library | impress.addLibraryFactory({name: fn}) |
On first impress(rootId) |
Provide api.lib.{name} services |
Inter-plugin communication
Plugins coordinate through custom DOM events rather than direct calls. The help plugin, for instance, exposes an impress:help:add event; other plugins dispatch it to register a help line:
util.triggerEvent( document, "impress:help:add",
{ command: "Left & Right", text: "Previous & Next step", row: 1 } );
This keeps plugins decoupled — the navigation plugin doesn't need a reference to the help plugin.
7. Plugin Reference (by type)
| Plugin | Type | Trigger | Notes |
|---|---|---|---|
| navigation | init | keyboard/mouse | Core navigation; maps key codes to next/prev; click-to-step |
| navigation-ui | init | toolbar | Back/Forward buttons + slide dropdown (needs toolbar) |
| touch | init | touch events | Swipe via core swipe() API |
| rel | pre-init | data-rel-* |
Relative positioning; uses rotation lib; commented out registration toggles it off |
| goto | pre-stepleave | data-goto* |
Out-link redirection |
| skip | pre-stepleave | class="skip" |
Skip steps in linear nav |
| stop | pre-stepleave | class="stop" |
Block forward wrap |
| substep | pre-stepleave | class="substep" |
Reveal sub-elements before leaving |
| autoplay | init | data-autoplay / ?impress-autoplay= |
Timed auto-advance; adds a toolbar widget |
| progress | init | .impress-progress* divs |
Progress bar + counter |
| help | init | #impress-help div |
Help popup; exposes impress:help:add event |
| blackout | init | B/. keys |
Blank screen |
| fullscreen | init | F5/Esc |
Fullscreen API |
| impressConsole | init | P key |
Speaker console window; reads .notes |
| mobile | init | mobile UA | Adds impress-mobile + next/prev classes |
| resize | init | window resize | Re-fit via throttle |
| mouse-timeout | init | 3 s idle | Adds impress-mouse-timeout body class |
| toolbar | init | #impress-toolbar div |
Shared widget host |
| form | init | automatic | stopPropagation on inputs; de-focus on leave |
| media | init | data-media-* |
Per-step audio/video control |
| extras | init | extra <script>s |
Auto-init 3rd-party add-ons (e.g. mermaid.initialize()) |
Note
rel carries a documented backward-incompatible side effect: with it enabled, a missing
data-x/y/zinherits from the previous step instead of defaulting to0. Its registration line (impress.addPreInitPlugin( rel );) can be commented out to disable the behavior without removing the file.
8. The Build Pipeline
The build is deliberately minimal — concatenation plus minification. build.js (run with node build.js):
- Concatenates files in a fixed order into
js/impress.js:src/impress.js(core) first- then
src/lib/gc.js,src/lib/util.js,src/lib/rotation.js - then all 22 plugins in
src/plugins/*/*.js(explicit order list inbuild.js) - prepended with a generated header comment.
- Minifies with Terser into
js/impress.min.js+ a source mapjs/impress.min.js.map(preserving/^!/license comments). These minified outputs are not committed. - Auto-generates
examples/index.html— an<ul>linking every subdirectory underexamples/(used for the gh-pages examples listing).
npm install # installs build + test tooling
npm run build # node build.js -> regenerates js/impress.js (+ .min.js)
Important
Plugin load order is defined explicitly in
build.js, not by directory glob alphabetization. If you add a plugin, add it to thefiles.push(...)list inbuild.js. Pre-init/pre-stepleave ordering at runtime is governed by theweightargument, but file order still affects which code registers first.
The build essentially does cat src/impress.js src/lib/*.js src/plugins/*/*.js > js/impress.js. The only npm packages it relies on are buildify/ls/terser; impress.js at runtime depends on nothing.
npm scripts (package.json)
| Script | Command | Purpose |
|---|---|---|
build |
node build.js |
Regenerate the bundle |
test |
karma start --log-level debug --single-run=true |
Run tests headless via Karma |
lint |
jshint src test/*.js && jscs src test/*.js |
Legacy linting |
new-lint |
eslint src test |
ESLint |
all |
npm run build && npm run test && npm run lint |
Full CI sequence |
Dev requirements
- Node >= 7.6
- npm
9. Configuration Reference
impress.js has no configuration file and no environment variables. All configuration is HTML data-* attributes, resolved at runtime.
Note
The single entry in the extracted
config_keysdata isCHROME_BIN(aprocess.envread inkarma.conf.js) — that is a test-harness variable for pointing Karma at a Chrome binary, not a runtime setting of the library. There are no runtime env vars.
Root configuration (defaults from src/impress.js)
data-* attribute |
Default | Maps to internal |
|---|---|---|
data-width |
1920 |
config.width |
data-height |
1080 |
config.height |
data-max-scale |
3 |
config.maxScale |
data-min-scale |
0 |
config.minScale |
data-perspective |
1000 |
config.perspective |
data-transition-duration |
1000 |
config.transitionDuration |
All other data-* attributes are consumed per-step by the core (data-x/y/z, data-rotate*, data-scale, data-rotate-order, per-step data-transition-duration) or by individual plugins (see the User Guide reference and Section 7).
Test-harness configuration
karma.conf.js reads process.env.CHROME_BIN to locate a Chrome/Chromium binary for headless testing. puppeteer is a devDependency that can supply one.
10. Deployment & Hosting
Because a presentation is just static files, "deployment" is serving HTML/CSS/JS.
Option A: Single self-contained directory
Ship your index.html, your CSS, a local js/impress.js, and any media. Open the file directly or serve it from any static host (GitHub Pages, Netlify, S3, nginx, etc.).
my-talk/
├── index.html
├── css/
│ ├── impress-common.css (optional)
│ └── my-style.css
├── js/
│ └── impress.js
└── images/
Option B: CDN for the framework
Reference https://cdn.jsdelivr.net/gh/impress/impress.js@2.0.0/js/impress.js and host only your own HTML/CSS. Lighter to deploy, but requires network access at present time.
Tip
Pin a specific
@versionin the CDN URL for reproducibility. The unversioned.../impress.js/js/impress.jsalways tracks the latest source and can change under you.
Self-hosting the project / examples (gh-pages model)
The project's own demos are published to GitHub Pages (http://impress.github.io/impress.js/). build.js regenerates examples/index.html so the examples listing stays current. To self-host the demos, build and serve the repository root as static files.
Warning
No build step is required to deploy a presentation — only to regenerate
js/impress.jsfromsrc/. End users serving a pre-builtjs/impress.jsneed no Node toolchain at all.
11. Security Analysis
impress.js is a client-side library with no server, no network calls, no authentication, no secrets, and no data persistence. Its attack surface is correspondingly small, but there are real considerations for anyone embedding it in a larger application.
Threat model
| Concern | Assessment |
|---|---|
| Secrets / credentials | None. No keys, tokens, or .env in the runtime library. |
| Network / data exfiltration | None. The library makes no outbound requests. |
| Authentication / authorization | N/A. There is no access control to attack. |
| Input handling | The library reads data-* attributes and your DOM. It does not sanitize HTML — your slide content is rendered as-is. |
| DOM injection by plugins | Several plugins build HTML strings via innerHTML (e.g. the help plugin renders a <table> from registered rows). |
Key findings
Caution
Untrusted content = XSS risk. impress.js renders whatever HTML you put in your steps. If any part of a presentation is built from user-supplied or remote content (e.g. a CMS, comments, query parameters), you must sanitize it yourself before injecting it into a step. The framework provides no sanitization layer.
Warning
Plugins use
innerHTML. The help plugin assembles its popup withhelpDiv.innerHTML = "<table>" + rows.join() + "</table>", where rows come fromimpress:help:addevents. Any plugin that dispatches that event controls markup written to the DOM. In a trusted, author-controlled deck this is fine; if you let third parties register help text or write plugins, treat the help/toolbar text as a markup sink.
Note
The
data-goto/data-bookmarkkey handling and URL hash routing read values from the page/URL andgetElementByIdthem. They locate existing elements rather than executing strings, so they are not script-injection vectors, but a malicious hash simply navigates the deck — not a meaningful risk for a presentation.
Tip
pointer-eventsworkaround.impress-common.csssetspointer-events: noneon.impress-enabledand re-enables it on#impress(and toolbar/console buttons) to work around a Chrome bug where Z-pushed elements fall behind<body>. This is a usability fix, not a security boundary, but be aware it makes everything outside#impressunclickable when applied.
Hardening checklist (for embedding impress.js in an app)
- [ ] Sanitize any non-author content before placing it in a step (use a vetted sanitizer like DOMPurify).
- [ ] Don't expose the
impress:help:addevent surface to untrusted code. - [ ] Serve over HTTPS and pin the CDN version if loading remotely.
- [ ] Apply a Content-Security-Policy on the hosting page; impress.js needs no
evalor remote scripts of its own (it does not useeval). - [ ] Audit any extra add-ons you include — they are third-party code outside the impress.js codebase.
12. Code Quality & Conventions
| Convention | Detail |
|---|---|
| Module pattern | Every file is an IIFE ( function(document, window){ "use strict"; ... } )(document, window) |
| Style | ES5-style (var, function expressions); a few ES2015 touches (default params in rotation.js) |
| Naming | camelCase functions/vars; data-* attributes are kebab-case → dataset camelCase |
| Comments | Unusually rich, conversational inline comments — the source doubles as a teaching tool |
| Dependencies | Zero runtime deps by design; explicitly avoids jQuery and polyfills |
| Linting | Dual legacy (jshint + jscs) and modern (eslint) configs coexist (.jshintrc, .jscsrc, .eslintrc.js) |
| Browser API usage | Direct DOM APIs (dataset, classList, querySelector, CustomEvent, CSS transforms) |
| State management | Per-root closures; module-level registries for plugins/libraries |
Note
The codebase predates ES modules and bundler ecosystems and stays intentionally framework-free. This is a deliberate design value ("showcase awesome CSS3 features... do not use jQuery or any other JavaScript libraries"), not technical debt.
Extraction caveat for tooling
Static analysis tools that look for function name( / class X declarations will find almost nothing here — the entire API is built from function expressions assigned to vars and returned in object literals, inside IIFEs. The authoritative API surface is DOCUMENTATION.md plus the returned object in src/impress.js (the { init, goto, next, prev, swipe, tear, lib } literal and the impress.* static assignments).
13. Testing Strategy
| Aspect | Detail |
|---|---|
| Frameworks | QUnit (assertions) + Syn (synthetic user input) + qunit-assert-close |
| Runner (headless) | Karma (karma-chrome-launcher, karma-firefox-launcher, karma-qunit), via npm run test |
| Runner (browser) | qunit_test_runner.html — open directly in Firefox/Chrome |
| Core tests | test/core_tests.js, test/core_tests_presentation.html, test/helpers.js, test/non_default.* |
| Plugin tests | Co-located per plugin (e.g. test/plugins/rel/* for the rel plugin: padding, rel-to, screen-size, rotation) |
| Headless Chrome | karma.conf.js reads CHROME_BIN; puppeteer can provide the binary |
Tip
The maintainers note that running
firefox qunit_test_runner.htmlis often more informative thankarma(npm run test) — they run the same tests, but the browser runner gives clearer output. Use Karma for CI, the browser runner for debugging.
Note
Test coverage is acknowledged as incomplete ("Yes, more tests are much welcome"). The
relplugin is the best-covered plugin; most other plugins have thin or no tests. This is the largest quality gap (see Improvements).
14. Writing a Plugin (extension recipe)
A. Init plugin (most common)
Use this when your plugin reacts to navigation or adds UI/behavior.
/* global document */
( function( document ) {
"use strict";
document.addEventListener( "impress:init", function( event ) {
var api = event.detail.api; // the impress API for this root
var gc = api.lib.gc; // ALWAYS route DOM/listeners through gc
var util = api.lib.util;
// Bind behavior using the public API
gc.addEventListener( document, "keyup", function( e ) {
if ( e.keyCode === /* your key */ 0 ) {
api.next();
}
}, false );
// Optionally register a help line
util.triggerEvent( document, "impress:help:add",
{ command: "X", text: "What X does", row: 5 } );
}, false );
} )( document );
Important
Route every element you add and every listener you bind through
api.lib.gc(gc.addEventListener,gc.appendChild,gc.pushElement,gc.pushCallback). Otherwiseimpress().tear()will leave your plugin's DOM/listeners behind.
B. Pre-init plugin (rewrite step attributes before init)
( function( document, window ) {
"use strict";
var impress = window.impress;
var myPreInit = function( root ) {
// Read your custom data-* attrs and write data-x/y/z etc.
};
impress.addPreInitPlugin( myPreInit, 10 ); // weight: lower runs first
} )( document, window );
C. Pre-stepleave plugin (redirect/abort transitions)
( function( document, window ) {
"use strict";
var impress = window.impress;
var myPreStepLeave = function( event ) {
// event.detail.next, .reason, .transitionDuration are mutable
// return false to ABORT the transition
if ( /* condition */ false ) {
return false;
}
};
impress.addPreStepLeavePlugin( myPreStepLeave, 10 );
} )( document, window );
D. Add it to the build
Append your file to the files.push(...) list in build.js (mind the order), then npm run build. Add a README.md in your plugin directory and a test under test/plugins/yourplugin/.
Recipe checklist
- [ ] IIFE +
"use strict" - [ ] Choose the correct plugin type (init / pre-init / pre-stepleave)
- [ ] Use the public API +
api.lib.*only — don't reach into core internals - [ ] Route all DOM/listeners through
gc - [ ] Register help text if you bind a key
- [ ] Add to
build.js, write aREADME.md, add tests - [ ] Verify
tear()cleans up your plugin
15. Potential Improvements & Tech Debt
Drawn from the source, the project's own READMEs, and the open GitHub issues.
Release & packaging
- Version inconsistency between
package.json(1.1.0) and the source/CDN (2.0.0). Reconcile and bumppackage.json. - Not published to npm (issues #879, #653). A real npm package would let users
npm installand enable bundler workflows. (#700 also asks for extra add-ons as npm dependencies.) - Consider a proper release artifact that includes the minified
js/impress.min.js(currently uncommitted).
Architecture & features
- Multiple-root support is half-hearted — the code supports several
#impressroots per page but warns it's not really intended; issue #664 proposes removing it. Decide: support it properly or drop it. - Hash-based routing clutters browser history (#644) — each step pushes a history entry;
history.replaceStatewould avoid the back-button pile-up. - Step-independent zoom/pan (#649) is a requested capability the current camera model doesn't expose.
- Resize transition hardcoded to 500 ms (#682).
- Scaling quality on lower-than-config resolutions produces blurriness (#609) — a known CSS
scaleartifact.
Mobile
- Positioning is off on some mobile devices (#844). The
mobileplugin notes it doesn't account for non-linear (skip/goto) transitions.
Code & docs
- Form plugin is incomplete — Tab navigation between form fields is unsupported because the navigation plugin must own the Tab key (flagged in the plugin's own README, "REQUIRES FURTHER DEVELOPMENT").
- No JSDoc API documentation (#662) — the API is documented in prose (
DOCUMENTATION.md) but not machine-readable. - Documentation gaps: add
gcusage docs tosrc/plugins/README.md(#677); a media-plugin usage example (#689); a bug in GettingStarted was reported (#860, closed). - Thin test coverage outside the
relplugin (acknowledged in README). Each plugin should get atest/plugins/<name>/suite. - Dual lint toolchains (
jshint+jscslegacy alongsideeslint) should converge on ESLint and the legacy configs/scripts be retired. - A "Major Refactor" is an open umbrella issue (#863).
- No dedicated project webpage (#645).
Maintainability notes
- The build's explicit plugin list in
build.jsmust be kept in sync manually when plugins are added/removed. extras/depends on a git submodule (impress-extras); a non-recursive clone silently omits it.
16. Quick Reference
Build & test
npm install
npm run build # node build.js -> js/impress.js (+ .min.js, .map)
npm run test # karma headless
npm run new-lint # eslint src test
npm run all # build + test + lint
firefox qunit_test_runner.html # browser test runner (more verbose)
Core API (returned by impress(rootId))
| Member | Type | Purpose |
|---|---|---|
init() |
method | Start the presentation |
goto(idx|id|el [, ms]) |
method | Go to a step |
next() / prev() |
method | Sequential navigation |
swipe(pct) |
method | Semi-internal swipe animation (touch plugin) |
tear() |
method | Reset DOM to pre-init state |
lib |
object | lib.gc, lib.util, lib.rotation |
Static members on impress
| Member | Purpose |
|---|---|
impress.supported |
Boolean feature-support flag |
impress.getConfig() |
Resolved config object |
impress.addLibraryFactory({name:fn}) |
Register a library |
impress.addPreInitPlugin(fn, weight) |
Register a pre-init plugin |
impress.addPreStepLeavePlugin(fn, weight) |
Register a pre-stepleave plugin |
Events (on the root element)
impress:init · impress:stepenter · impress:stepleave · impress:steprefresh
Plugin coordination events
impress:help:add (register a help-popup line)
Build order (from build.js)
src/impress.js
src/lib/{gc,util,rotation}.js
src/plugins/{autoplay,blackout,extras,form,fullscreen,goto,bookmark,help,
impressConsole,media,mobile,mouse-timeout,navigation,navigation-ui,
progress,rel,resize,skip,stop,substep,touch,toolbar}/*.js
Key files
| File | Role |
|---|---|
src/impress.js |
Core engine (edit here) |
src/lib/*.js |
gc / util / rotation libraries |
src/plugins/*/ |
22 default plugins (+ READMEs) |
build.js |
Build (concat + minify + examples index) |
js/impress.js |
Generated bundle (don't edit) |
css/impress-common.css |
Optional sample plugin CSS |
DOCUMENTATION.md |
Reference API (authoritative) |
test/ |
QUnit/Syn tests |