Jesse A. Green / notebook

impress.js Admin & Developer Guide

Generated June 2026 from impress/impress.js at c9f6c67

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

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

  1. Executive Summary
  2. Architecture Overview
  3. Repository Structure
  4. Core Engine Deep Dive (src/impress.js)
  5. The Library Layer (src/lib/)
  6. The Plugin System
  7. Plugin Reference (by type)
  8. The Build Pipeline
  9. Configuration Reference
  10. Deployment & Hosting
  11. Security Analysis
  12. Code Quality & Conventions
  13. Testing Strategy
  14. Writing a Plugin (extension recipe)
  15. Potential Improvements & Tech Debt
  16. 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.json declares "version": "1.1.0", but the source header in src/impress.js says version: 2.0.0, the CDN serves a @2.0.0 tag, and DOCUMENTATION.md references a v1.2.0 default-resolution change. Treat 2.0.0 as the effective released version (matching the latest tag) and the package.json value 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.js is a build artifact, regenerated by build.js (cat src/impress.js src/lib/*.js src/plugins/*/*.js). Never hand-edit it — edit the files under src/ and rebuild. The minified js/impress.min.js is 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:

  1. Re-reads all steps (initAllSteps()).
  2. Resolves the target via getStep() (accepts index, id, or element; negative indices count from the end).
  3. Runs execPreStepLeavePlugins(event) — pre-stepleave plugins may redirect (event.detail.next), retime (transitionDuration), or abort (return false).
  4. Updates .active classes and the impress-on-{id} body class.
  5. Computes the target canvas state as the inverse of the step transform (negated rotate/translate, 1/scale).
  6. Determines zoomin direction to choose the transition-delay strategy.
  7. Applies CSS to root (perspective + scale) and canvas (rotate + translate) with staggered transitionDelay.
  8. Fires impress:stepenter after duration + delay via setTimeout (chosen over transitionend for simplicity across two animated elements).
  • prev(origEvent) / next(origEvent) walk steps in 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 in DOCUMENTATION.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-id on enter, listens for hashchange to goto() 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.gc so tear() 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 absolute data-x/data-y/data-z before 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 &amp; Right", text: "Previous &amp; 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/z inherits from the previous step instead of defaulting to 0. 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):

  1. 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 in build.js)
    • prepended with a generated header comment.
  2. Minifies with Terser into js/impress.min.js + a source map js/impress.min.js.map (preserving /^!/ license comments). These minified outputs are not committed.
  3. Auto-generates examples/index.html — an <ul> linking every subdirectory under examples/ (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 the files.push(...) list in build.js. Pre-init/pre-stepleave ordering at runtime is governed by the weight argument, 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_keys data is CHROME_BIN (a process.env read in karma.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 @version in the CDN URL for reproducibility. The unversioned .../impress.js/js/impress.js always 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.js from src/. End users serving a pre-built js/impress.js need 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 with helpDiv.innerHTML = "<table>" + rows.join() + "</table>", where rows come from impress:help:add events. 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-bookmark key handling and URL hash routing read values from the page/URL and getElementById them. 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-events workaround. impress-common.css sets pointer-events: none on .impress-enabled and 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 #impress unclickable 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:add event 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 eval or remote scripts of its own (it does not use eval).
  • [ ] 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.html is often more informative than karma (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 rel plugin 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). Otherwise impress().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 a README.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 bump package.json.
  • Not published to npm (issues #879, #653). A real npm package would let users npm install and 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 #impress roots 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.replaceState would 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 scale artifact.

Mobile

  • Positioning is off on some mobile devices (#844). The mobile plugin 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 gc usage docs to src/plugins/README.md (#677); a media-plugin usage example (#689); a bug in GettingStarted was reported (#860, closed).
  • Thin test coverage outside the rel plugin (acknowledged in README). Each plugin should get a test/plugins/<name>/ suite.
  • Dual lint toolchains (jshint+jscs legacy alongside eslint) 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.js must 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