FastDOM eliminates layout thrashing by batching DOM read and write operations to prevent unnecessary document reflows. It provides a simple API that ensures DOM measurements and mutations happen at optimal times during the browser's rendering cycle, resulting in smoother animations and better performance.
npm install fastdomimport fastdom from "fastdom";For CommonJS:
const fastdom = require("fastdom");For browser (global):
<script src="fastdom.js"></script>
<!-- fastdom is available as window.fastdom -->import fastdom from "fastdom";
// Schedule DOM reads (measurements) first
fastdom.measure(() => {
const width = element.clientWidth;
const height = element.clientHeight;
// Schedule DOM writes (mutations) based on measurements
fastdom.mutate(() => {
element.style.width = width * 2 + 'px';
element.style.height = height * 2 + 'px';
});
});
// Multiple operations are batched automatically
fastdom.measure(() => console.log('measure 1'));
fastdom.measure(() => console.log('measure 2'));
fastdom.mutate(() => console.log('mutate 1'));
fastdom.mutate(() => console.log('mutate 2'));
// Output: measure 1, measure 2, mutate 1, mutate 2FastDOM operates as a singleton across all modules in your application, providing a regulatory layer between your code and the DOM:
requestAnimationFrame to time operations with the browser's rendering cycleSchedule DOM read operations that won't cause layout thrashing.
/**
* Schedules a job for the 'measure' queue. Returns a unique ID that can be used to clear the scheduled job.
* @param {Function} fn - Callback function to execute during read phase
* @param {Object} [ctx] - Optional context to bind to the function
* @returns {Function} The scheduled task that can be passed to clear()
*/
function measure(fn: () => void, ctx?: any): () => void;Usage Examples:
// Basic measurement
const task = fastdom.measure(() => {
const rect = element.getBoundingClientRect();
console.log(rect.width, rect.height);
});
// With context binding
class Component {
updateSize() {
fastdom.measure(this.measureElement, this);
}
measureElement() {
this.width = this.element.clientWidth;
}
}Schedule DOM write operations to prevent layout thrashing.
/**
* Schedules a job for the 'mutate' queue. Returns a unique ID that can be used to clear the scheduled job.
* @param {Function} fn - Callback function to execute during write phase
* @param {Object} [ctx] - Optional context to bind to the function
* @returns {Function} The scheduled task that can be passed to clear()
*/
function mutate(fn: () => void, ctx?: any): () => void;Usage Examples:
// Basic mutation
const task = fastdom.mutate(() => {
element.style.transform = 'translateX(100px)';
element.classList.add('moved');
});
// Chaining measure and mutate
fastdom.measure(() => {
const width = element.clientWidth;
fastdom.mutate(() => {
element.style.width = (width * 1.5) + 'px';
});
});Clear scheduled tasks before they execute.
/**
* Clears any scheduled job.
* @param {Function} task - The task returned from measure() or mutate()
* @returns {boolean} True if task was successfully cleared
*/
function clear(task: () => void): boolean;Usage Examples:
const readTask = fastdom.measure(() => {
console.log('This will be cancelled');
});
const writeTask = fastdom.mutate(() => {
console.log('This will also be cancelled');
});
// Cancel the tasks before they execute
fastdom.clear(readTask); // returns true
fastdom.clear(writeTask); // returns trueCreate extended FastDOM instances with additional functionality.
/**
* Extend this FastDom with custom functionality.
* @param {Object} props - Properties to mixin to create extended instance
* @returns {FastDom} New extended FastDom instance
*/
function extend(props: object): FastDom;Usage Examples:
const myFastdom = fastdom.extend({
initialize() {
console.log('Extension initialized');
},
// Override existing methods
measure(fn) {
console.log('Custom measure logic');
return this.fastdom.measure(fn);
},
// Add new methods
batch(operations) {
operations.forEach(op => {
if (op.type === 'measure') {
this.measure(op.fn);
} else {
this.mutate(op.fn);
}
});
}
});
myFastdom.batch([
{ type: 'measure', fn: () => console.log('measure') },
{ type: 'mutate', fn: () => console.log('mutate') }
]);Configure custom error handling for task failures.
/**
* Error handler property for tasks that throw exceptions
* @type {Function|null}
*/
let catch: ((error: Error) => void) | null;Usage Examples:
// Set global error handler
fastdom.catch = (error) => {
console.error('FastDOM task failed:', error);
// Send to error reporting service
errorReporting.report(error);
};
// Tasks that error will now be handled gracefully
fastdom.measure(() => {
throw new Error('Something went wrong');
// This error will be caught and handled by the catch function
});Adds Promise-based API for improved control flow.
<script src="fastdom.js"></script>
<script src="extensions/fastdom-promised.js"></script>// For CommonJS/ES modules
const fastdomPromised = require('fastdom-promised');
// or: import fastdomPromised from 'fastdom-promised';
// For browser global (after loading script)
// fastdomPromised is available as window.fastdomPromised
// Extend fastdom with promise functionality
const promisedFastdom = fastdom.extend(fastdomPromised);
/**
* Promise-based measure operation
* @param {Function} fn - Task function that can return a value
* @param {Object} [ctx] - Optional execution context
* @returns {Promise} Promise that resolves with the function's return value
*/
function measure(fn: () => any, ctx?: any): Promise<any>;
/**
* Promise-based mutate operation
* @param {Function} fn - Task function that can return a value
* @param {Object} [ctx] - Optional execution context
* @returns {Promise} Promise that resolves with the function's return value
*/
function mutate(fn: () => any, ctx?: any): Promise<any>;
/**
* Clear a promise-based task
* @param {Promise} promise - The promise returned from measure/mutate
*/
function clear(promise: Promise<any>): void;Usage Examples:
// Promise-based API
const width = await promisedFastdom.measure(() => element.clientWidth);
await promisedFastdom.mutate(() => {
element.style.width = width * 2 + 'px';
});
// Chaining promises
promisedFastdom.measure(() => element.clientWidth)
.then(width => promisedFastdom.mutate(() => {
element.style.width = width * 2 + 'px';
}))
.then(() => console.log('Done!'));
// Clearing promise-based tasks
const promise = promisedFastdom.measure(() => {
return element.clientWidth;
});
promisedFastdom.clear(promise);Provides task grouping for component-based development.
<script src="fastdom.js"></script>
<script src="extensions/fastdom-sandbox.js"></script>// For CommonJS/ES modules
const fastdomSandbox = require('fastdom-sandbox');
// or: import fastdomSandbox from 'fastdom-sandbox';
// For browser global (after loading script)
// fastdomSandbox is available as window.fastdomSandbox
// Extend fastdom with sandbox functionality
const sandboxedFastdom = fastdom.extend(fastdomSandbox);
/**
* Create a new sandbox for grouping tasks
* @returns {Sandbox} New sandbox instance
*/
function sandbox(): Sandbox;
interface Sandbox {
/**
* Schedule measure task in sandbox
* @param {Function} fn - Task function
* @param {Object} [ctx] - Optional execution context
* @returns {Function} The scheduled task
*/
measure(fn: () => void, ctx?: any): () => void;
/**
* Schedule mutate task in sandbox
* @param {Function} fn - Task function
* @param {Object} [ctx] - Optional execution context
* @returns {Function} The scheduled task
*/
mutate(fn: () => void, ctx?: any): () => void;
/**
* Clear single task or all tasks in sandbox
* @param {Function} [task] - Specific task to clear, or clear all if omitted
* @returns {boolean} Result from fastdom.clear()
*/
clear(task?: () => void): boolean;
}Usage Examples:
// Create sandbox for component
class Component {
constructor() {
this.sandbox = sandboxedFastdom.sandbox();
}
updateLayout() {
this.sandbox.measure(() => {
this.width = this.element.clientWidth;
});
this.sandbox.mutate(() => {
this.element.style.width = this.width * 2 + 'px';
});
}
destroy() {
// Clear all tasks for this component
this.sandbox.clear();
}
}
// Manual sandbox usage
const sandbox = sandboxedFastdom.sandbox();
sandbox.measure(() => console.log('measure 1'));
sandbox.measure(() => console.log('measure 2'));
sandbox.mutate(() => console.log('mutate 1'));
// Clear all tasks in the sandbox
sandbox.clear();Development-time enforcement of proper DOM access patterns.
<script src="fastdom.js"></script>
<script src="fastdom-strict.js"></script>Note: When fastdom-strict.js is loaded, it extends the global fastdom instance with strict mode functionality. The strict() method becomes available on the fastdom instance.
Usage Examples:
// Strict mode is enabled by default when fastdom-strict.js is loaded
// This will throw an error:
element.clientWidth; // Error: Can only get .clientWidth during 'measure' phase
// This will throw an error:
fastdom.mutate(() => {
element.clientWidth; // Error: Can only get .clientWidth during 'measure' phase
});
// This works correctly:
fastdom.measure(() => {
element.clientWidth; // No error
});
// Disable strict mode using the method on the fastdom instance
fastdom.strict(false);
element.clientWidth; // No error when disabled
// Re-enable strict mode
fastdom.strict(true);// Core FastDom interface
interface FastDom {
measure<T extends () => void>(fn: T, ctx?: any): T;
mutate<T extends () => void>(fn: T, ctx?: any): T;
clear<T extends () => void>(task: T): boolean;
extend<T extends object>(props: T): Omit<FastDom, keyof T & keyof FastDom> & T;
catch: null | ((e: unknown) => any);
// Available when fastdom-strict.js is loaded
strict?(value: boolean): void;
}
// FastDOM Promised extension types
interface FastdomPromised {
clear<T extends Promise<any>>(task: T): void;
initialize(): void;
measure<T extends () => void>(fn: T, ctx?: any): Promise<ReturnType<T>>;
mutate<T extends () => void>(fn: T, ctx?: any): Promise<ReturnType<T>>;
}
// Sandbox interface
interface Sandbox {
fastdom: FastDom;
tasks: (() => void)[];
measure(fn: () => void, ctx?: any): () => void;
mutate(fn: () => void, ctx?: any): () => void;
clear(task?: () => void): boolean;
}