npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@webqit/realdom

v2.1.24

Published

Low-level DOM APIs.

Downloads

62

Readme

realdom

npm version npm downloads bundle License

A small, low-level utility for working with the (real) DOM in realtime!

Documentation

Info This is (Early stage) documentation for v2.x. (Looking for v1.x?)

Download Options

Use as an npm package:

npm i @webqit/realdom
// Import
import init from '@webqit/realdom';

// Initialize the lib
init.call( window );

// Obtain the APIs
const { ready, realtime, schedule } = window.webqit.realdom;

Use as a script:

<script src="https://unpkg.com/@webqit/realdom/dist/main.js"></script>
// Obtain the APIs
const { ready, realtime, schedule } = window.webqit.realdom;

The Ready-State API

Know when the document is ready! This is a simplistic API for working with the document's ready state.

Method: realdom.ready()

Know when the document is ready.

// Signature 1
ready([ callback = undefined ]);
// Signature 2
ready([ timing = 'interactive'[, callback = undefined ]]);

The ready() function takes a callback function to be called at a certain document-ready state. This function receives the window object.

// Binding to the document's ready state
ready( window => console.log( `Document "ready state" is now "interactive"` ) );

When no callback function is provided, a promise is returned.

// Awaiting the document's ready state
await ready();
console.log( `Document "ready state" is now "interactive"` );

--> Use the two-parameter syntax to specify the timing - i.e. ready state - at which to be called. This can be either of two values:

  • interactive - (The default) The point at which the document has finished loading and the document has been parsed but sub-resources such as scripts, images, stylesheets and frames are still loading.

  • complete - The point at which the document and all sub-resources have finished loading.

// Binding to the document's "complete" ready state
ready( 'complete', () => console.log( 'Document "ready state" is now "complete"' ) );
// Awaiting the document's "complete" ready state
await ready( 'complete' );
console.log( `Document "ready state" is now "complete"` );

The Realtime Mutations API

React to realtime DOM operations! This is a set of succint and consistent methods for accessing the DOM - either on-demand (you calling the DOM... as you would using querySelectorAll()) or in realtime (you letting the DOM call you... as you would using the MutationObserver API).

Method: realdom.realtime( context ).observe()

Observe the (real) DOM in realtime!

// Signature 1
realtime( context ).observe( callback[, options = {} ]);
// Signature 2
realtime( context ).observe( targets, callback[, options = {} ]);

Concept: Scope

Report all direct children additions and removals to/from the given element - the context:

// Observing all direct children mutations
realtime( document.body ).observe( handleChanges );

--> Observe entire subtree of the given element using the options.subtree flag:

// Observing all subtree mutations
realtime( document.body ).observe( handleChanges, { subtree: true } );

Info You'd normally always need this flag when the document object is the context.

Concept: Targets

Pass in a CSS selector to match elements in realtime; e.g. "p" for all <p> elements:

// Observing only "p" elements mutations
realtime( document.body ).observe( 'p', handleChanges, { subtree: true } );

...whether "p" elements added via markup:

// and whether or not it's deeply nested as par { subtree: true }:
document.body.innerHTML = '<div><p></p></div>';

...or "p" elements added programmatically:

// and whether or not it's deeply nested as par { subtree: true }:
const p = document.createElement( 'p' );
const div = document.createElement( 'div' );
div.appendChild( p );
document.body.appendChild( div );

--> Pass in an Xpath query to express what can't be expressed in a CSS selector; e.g. for when you need to match text and comment nodes in realtime. Xpath expressions must be enclosed in parentheses.

// Observing all "comment" nodes having a certain content
realtime( document.body ).observe( '(comment()[contains(., "hello world")])', handleChanges, { subtree: true } );

Info Note that Xpath expressions must not be prefixed with the direct children / or descendant // qualifiers as that is controlled internally. The options.subtree parameter is how you specify the resolution context for your queries.

--> Observe element instances as targets too. (E.g. a "p" instance.)

// observing an instance plus a selector
const pElement = document.createElement( 'p' );
realtime( document.body ).observe( [ pElement, orCssSelector ], handleChanges, { subtree: true } );

...both for when they're added:

// and whether or not it's deeply nested as par { subtree: true }:
const div = document.createElement( 'div' );
div.appendChild( pElement );
document.body.appendChild( div );

...and when they're removed:

// either via an overwrite... (indirect overwrite in this case)...
document.body.innerHTML = '';
// or via some programmatic means... (indirect removal in this case)...
document.querySelector( 'div' ).remove();

Concept: Records

Handle mutation records - each having an entrants and an exits array property, representing added and removed nodes respectively:

// Handling changes
function handleChanges( record ) {
    for ( const addedNode of record.entrants ) {
        console.log( 'added:', addedNode );
    }
    for ( const removedNode of record.exits ) {
        console.log( 'removed:', removedNode );
    }
}

--> Use the options.generation parameter to require only either the entrants or exits list:

// Requiring only the "entrants" list
realtime( document.body ).observe( handleChanges, { generation: 'entrants' } );
// Handling just record.entrants
function handleChanges( record, context ) {
    for ( const addedNode of record.entrants ) {
        console.log( 'added:', addedNode );
    }
    console.log( record.exits ); // Empty array
}

--> Use the record.target property to access the mutation target - often, the parent element under which mutation happened:

// Inspecting record.target
function handleChanges( record ) {
    console.log( record.target ); // HTMLBodyElement
}

Concept: Static Sensitivity

When targeting elements using attribute selectors, use the options.staticSensitivity flag to opt in to statically matching elements based on the attributes mentioned in the selector:

// Adding the options.staticSensitivity flag
realtime( document.body ).observe( 'p[draggable="true"]', handleChanges, { staticSensitivity: true } );

Now, "p" elements are matched for [draggable="true"] in their static state too:

// The following "p" element suddenly matches and is reported (record.entrants)
document.querySelector( 'p' ).setAttribute( 'draggable', 'true' );
// The following "p" element suddenly doesn't match and is reported (record.exits)
document.querySelector( 'p' ).setAttribute( 'draggable', 'false' );

Concept: Event Details

Use the option.eventDetails flag to require the actual DOM operation that happened under the hood:

// Requiring that event details be added
realtime( document.body ).observe( 'p[draggable="true"]', handleChanges, { eventDetails: true } );
// Inspecting record.event
function handleChanges( record ) {
    console.log( record.event );
}

You get an array in the format: [ HTMLBodyElement, 'appendChild' ] - for mutations that happen programatically:

// Running an operation
document.body.appendChild( pElement );

You get the keyword: parse - for elements recorded directly from the HTML parser while the document loads. (This happens for mutation listeners created early in the document tree.):

<html>
  <head>
    <script>
      realdom.realtime( document ).observe( 'meta[foo]', handleChanges, { subtree: true } );
    </script>
    <meta name="foo" content="bar">
    <!--
    At this point in the document parsing, the meta element is now reported, and record.event is: "parse"
    -->
    <script>
      const meta2 = document.createElement( 'meta' );
      meta2.name = 'foo';
      meta2.content = 'baz';
      document.head.appendChild( meta2 );
    </script>
    <!--
    At this point in the document parsing, the meta2 element is now reported, and record.event is: [ HTMLHeadElement, 'appendChild' ]
    -->
  </head>
</html>

You get the keyword: mutation - for mutations that happen in other ways; e.g in when the user directly alters the DOM tree from the browser console.

Concept: Abort Signals

Pass in an Abort Signal that you can use to abort your mutation listener at any time:

// Providing an AbortSignal
const abortController = new AbortController;
realtime( document.body ).observe( 'p', handleChanges, { signal: abortController.signal } );
// Abort at any time
abortController.abort();

Concept: Life Cycle Signals

When dealing with nested event listeners - event handlers that themselves create event listeners, tying child listeners' lifecycle to parent's lifecycle can be cumbersome.

// Managing nested lifecycles using multiple AbortSignals
const parentAbortController = new AbortController;
let recursionAbortController;
realtime( document.body ).observe( 'p', record => {
    // Abort all nested listeners in "previous" recursion
    recursionAbortController?.abort();
    // Create a new AbortController for listeners in "this" recursion
    recursionAbortController = new AbortController;
    for( const addedNode of record.entrants ) {
        addedNode.addEventListener( 'click', handleClick, { signal: recursionAbortController.signal } );
    }
}, { signal: parentAbortController.signal } );
// Abort parent at any time
parentAbortController.abort();
// Abort the latest instance of recursionAbortController
recursionAbortController?.abort();

--> Use the options.lifecycleSignals parameter to opt in to receiving auto-generated signals for tying nested listeners:

// Managing nested lifecycles using automatic lifecycle signals
const parentAbortController = new AbortController;
realtime( document.body ).observe( 'p', ( record, flags ) => {
    for( const addedNode of record.entrants ) {
        addedNode.addEventListener( 'click', handleClick, { signal: flags.signal } );
    }
}, { signal: parentAbortController.signal, lifecycleSignals: true } );
// Abort parent at any time
parentAbortController.abort();
// The latest flags.signal instance is also automatically aborted

Concept: Timing

For when timing is everything, meet the options.timing parameter!

By default, mutation records are delivered at the "async" timing of the MutationObserver API. This means that there's a small lag between when mutations happen and when they are delivered.

// Observing with "asynchronous" timing
let deliveredElement;
realtime( document.body ).observe( 'p', record => {
    deliveredElement = record.entrants[ 0 ];
} );
// Confirming the "async" delivery
const pElement = document.createElement( 'p' );
document.body.appendChild( pElement );
console.log( pElement.isConnected ); // true
console.log( deliveredElement ); // undefined
// Estimating delivery timing
setTimeout( () => {
    console.log( deliveredElement ); // HTMLParagraphElement
}, 0 );

--> Use the options.timing = "sync" parameter to observe mutations synchronously:

// Opting in to "synchronous" timing
let deliveredElement;
realtime( document.body ).observe( 'p', record => {
    deliveredElement = record.entrants[ 0 ];
}, { timing: 'sync' } );
// Confirming the "sync" delivery
const pElement = document.createElement( 'p' );
document.body.appendChild( pElement );
console.log( pElement.isConnected ); // true
console.log( deliveredElement ); // HTMLParagraphElement

There is also a rare case where a tool needs to extend the DOM in more low-level ways, and this time, needs to intercept certain mutations before they actually happen. For example, you could only really rewrite <script> elements before they're parsed and executed if you could intercept them.

--> Use the options.timing = "intercept" parameter to observe mutations before they actually happen:

// Trying the "intercept" timing
realtime( document.body ).observe( 'script', handleScripts, { timing: 'intercept' } );
// Making the mutation
const scriptElement = document.createElement( 'script' );
document.body.appendChild( scriptElement );
// Confirming the "intercpted" delivery
function handleScripts( record ) {
    const deliveredElement = record.entrants[ 0 ];
    // We're receiving an element that is only just about to be added to the DOM
    console.log( deliveredElement.isConnected ); // false
    console.log( deliveredElement.parentNode ); // null
    console.log( record.event ); // [ HTMLBodyElement, 'appendChild' ]
    // We can rewrite this script
    deliveredElement.text = 'alert( "Tada!" )';
}

And thanks to the MutationObserver API, interception also works at parse time for mutation listeners created early enough:

<html>
  <head>
    <script>
      realdom.realtime( document ).observe( 'script[rewriteme]', handleScripts, { timing: 'intercept' } );
    </script>
    <script rewriteme>
        alert( 'Hello world!' );
    </script>
    <!--
    At this point in the document parsing, the script[rewriteme] element is now reported, and rewritten
    But note that this time, element was just already in the DOM, only yet to be handled. So...
    console.log( deliveredElement.isConnected ); // true
    console.log( deliveredElement.parentNode ); // HTMLHeadElement
    console.log( record.event ); // "parse"
    -->
  </head>
</html>

Method: realdom.realtime( context ).query()

Work with the (real) DOM both on-demand (as you would with querySelectorAll()), and in realtime (as you would with realtime().observe()).

// Signature 1
realtime( context ).query([ callback = undefined[, options = {} ]]);
// Signature 2
realtime( context ).query( targets[, callback = undefined[, options = {} ]]);
// Signature 3
const records = realtime( context ).query([ options = {} ]);

Concept: Scope

Get all direct children of the given element delivered:

// Getting all children delivered to a callback
realtime( document.body ).query( handleResult );
// Retreiving all children
const records = realtime( document.body ).query();
// Deligating to handler
records.forEach( record => handleResult( record ) );

--> Use the realtime().children() alias:

// Delivering, using the .children() alias
realtime( document.body ).children( handleResult );
// Retreiving, using the .children() alias
const records = realtime( document.body ).children();
// Deligating to handler
records.forEach( record => handleResult( record ) );

Info Using the options.subtree flag without specifying targets produces no result.

Concept: Targets

Pass in a CSS selector to match elements: e.g. "p" for all <p> elements:

// Matching just "p" children
realtime( document.body ).query( 'p', handleResult );
// Using the .children() alias
realtime( document.body ).children( 'p', handleResult );
// Retreiving, using the .children() alias
const records = realtime( document.body ).children( 'p' );
// Deligating to handler
records.forEach( record => handleResult( record ) );

--> Use the options.subtree flag to query the entire subtree.

// Using the options.subtree flag
realtime( document.body ).query( 'p', handleResult, { subtree: true } );

--> Use the realtime().subtree() alias:

// Using the .subtree() alias
realtime( document.body ).subtree( 'p', handleResult );
// Retreiving, using the .subtree() alias
const records = realtime( document.body ).subtree( 'p' );
// Deligating to handler
records.forEach( record => handleResult( record ) );

--> Pass in an Xpath query to express what can't be expressed in a CSS selector; e.g. for when you need to match text and comment nodes. Xpath expressions must be enclosed in parentheses.

// Query all "comment" nodes having a certain content
realtime( document.body ).query( '(comment()[contains(., "hello world")])', handleResult, { subtree: true } );

Info: Note that Xpath expressions must not be prefixed with the direct children / or descendant // qualifiers as that is controlled internally. The options.subtree parameter is how you specify the resolution context for your queries.

Concept: Records

Handle query result records in the same way as mutation records:

// Handling query result
function handleResult( record ) {
    // record.entrants is the list of matched nodes
    for ( const matchedNode of record.entrants ) {
        console.log( 'matched:', matchedNode );
    }
    console.log( record.exits ); // Always an empty array
    console.log( record.event ); // Always the keyword: "query"
}

Info Setting the options.generation parameter to exits effectively defies the logic, thus no query actually happens.

--> Use the record.target property to access the query context for the record:

// Inspecting record.target
function handleResult( record ) {
    console.log( record.target ); // HTMLBodyElement
}

Info Elements are organized into records by common parent. Thus, an expression like realtime().subtree( 'p' ) on the below will produce 2 records, with 2 entrants per record:

<html>
  <body>
    <section>
      <p></p>
      <p></p>
    </section>
    <p></p>
    <p></p>
  </body>
</html>

Concept: Realtime Queries

Get both current and future elements delivered to the same handler function:

// Matching all current "p[draggable]" elements
realtime( document ).query( 'p[draggable]', handleDraggables, { subtree: true } );
// Subscribe to future matching elements
realtime( document ).observe( 'p[draggable]', handleDraggables, { subtree: true } );

--> Use the options.live flag to achieve the same:

// Matching all current "p[draggable]" elements and staying subscribed
realtime( document ).query( 'p[draggable]', handleDraggables, { subtree: true, live: true } );

--> Use equivalent APIs the same way:

// Using the .subtree() alias
realtime( document ).subtree( 'p[draggable]', handleDraggables, { live: true } );
// Using the .children() alias
realtime( document ).children( 'p[draggable]', handleDraggables, { live: true } );

--> Use the options.live flag in conjunction with other concepts:

// Using the options.live flag
const abortController = new AbortController;
realtime( document ).children( 'p[draggable]', handleDraggables, {
    live: true,
    signal: abortController.signal,
    lifecycleSignals: true,
    staticSensitivity: true,
    timing: 'sync',
    eventDetails: true,
} );

Method: realdom.realtime( context, 'attr' ).observe()

Observe DOM attributes in realtime!

// Signature 1
realtime( context, 'attr' ).observe( callback[, options = {} ]);
// Signature 2
realtime( context, 'attr' ).observe( targets, callback[, options = {} ]);

Concept: Scope

Report all attribute changes on the given element:

// Observing all attributes
realtime( document.body, 'attr' ).observe( handleChanges );

--> Observe all attribute changes across the entire subtree of the given context using the options.subtree flag:

// Observing entire subtree
realtime( document.body, 'attr' ).observe( handleChanges, { subtree: true } );

Info You'd normally always need this flag when the document object is the context.

Concept: Targets

Observe specific attributes on the given context:

// Observing the "draggable" attribute
realtime( element, 'attr' ).observe( [ 'draggable' ], handleChanges );

Concept: Records

Handle mutation records - an array of attribute-change records:

// Handling mutation records
function handleChanges( records ) {
    for ( const record of records ) {
        console.log( record.name );
    }
}

--> Use the options.oldValue and options.newValue flags to require attribute's old and new values respectively:

// Requiring attribute's old and new value
realtime( element, 'attr' ).observe( [ 'draggable' ], handleChanges, { newValue: true, oldValue: true } );
// Inspecting attribute's old and new value
function handleChanges( records ) {
    for ( const record of records ) {
        console.log( record.name, record.value, record.oldValue );
    }
}

--> Use the record.target property to access the mutation target - the element on which mutation happened:

// Inspecting record.target
function handleChanges( records ) {
    console.log( records[ 0 ].target );
}

--> Where exactly one attribute is being observed and is passed as a string instead of an array, mutation records are delivered in singular form instead of as an array:

// Passing the observed attribute as a bare string instead of an array
realtime( element, 'attr' ).observe( 'draggable', handleChanges );
// Receiving records in singular form instead of as an array
function handleChanges( record ) {
    console.log( record.name );
}

Concept: Atomic Delivery

Get records for multiple attributes delivered atomically - in whole - whenever any of the attributes change:

// Observing multiple attributes atomically
realtime( element, 'attr' ).observe( [ 'attr1', 'attr2', 'attr3' ], handleChanges, { atomic: true } );
// Receiving all 3 records anytime any one of them changes
function handleChanges( records ) {
    const [ attr1, attr2, attr3 ] = records;
}

Concept: Event Details

Use the option.eventDetails flag to require the actual DOM operation that happened under the hood:

// Requiring that event details be added
realtime( element, 'attr' ).observe( [ 'draggable' ], handleChanges, { eventDetails: true } );
// Inspecting record.event
function handleChanges( records ) {
    console.log( records[ 0 ].event );
}

You get an array in the format: [ HTMLInputElement, 'toggleAttribute' ] - for mutations that happen programatically:

// Running an operation
element.toggleAttribute( 'required' );

You get the keyword: mutation - for mutations that happen in other ways; e.g in when the user directly alters an element's attribute from the browser console.

Concept: Abort Signals

Pass in an Abort Signal that you can use to abort your mutation listener at any time:

// Providing an AbortSignal
const abortController = new AbortController;
realtime( element, 'attr' ).observe( [ 'required' ], { signal: abortController.signal } );
// Abort at any time
abortController.abort();

Concept: Life Cycle Signals

When dealing with nested event listeners (see above), use the options.lifecycleSignals parameter to opt in to receiving auto-generated signals for tying nested listeners:

// Managing nested lifecycles using automatic lifecycle signals
const parentAbortController = new AbortController;
realtime( element, 'attr' ).observe( [ 'draggable' ], ( records, flags ) => {
    if ( records[ 0 ].value === 'true' ) {
        element.addEventListener( 'drag', handleDrag, { signal: flags.signal } );
    }
}, { newValue: true, signal: parentAbortController.signal, lifecycleSignals: true } );
// Abort parent at any time
parentAbortController.abort();
// The latest flags.signal instance is also automatically aborted

Concept: Timing

For when timing is critical (see above), use the options.timing = "sync" parameter to observe mutations synchronously:

// Opting in to "synchronous" timing
realtime( element, 'attr' ).observe( [ 'draggable' ], records => {
    // Handle records
}, { timing: 'sync' } );

--> Use the options.timing = "intercept" parameter to observe attribute mutations before they actually happen:

// Opting in to "synchronous" timing
realtime( img, 'attr' ).observe( [ 'src' ], records => {
    // Handle records
}, { timing: 'intercept' } );

Method: realdom.realtime( context, 'attr' ).get()

Work with attributes both on-demand and in realtime (as you would with realtime( element, 'attr' ).observe()).

// Signature 1
realtime( context, 'attr' ).get([ callback = undefined[, options = {} ]]);
// Signature 2
realtime( context, 'attr' ).get( targets[, callback = undefined[, options = {} ]]);
// Signature 3
const records = realtime( context, 'attr' ).get([ options = {} ]);

Concept: Scope

Get all attributes on the given element:

// Getting all attributes delivered to a handler
realtime( document.body, 'attr' ).get( handleChanges );
// Retreiving all attributes
const records = realtime( document.body, 'attr' ).get();
// Deligating to handler
handleChanges( records );

Concept: Targets

Get specific attributes on the given context:

// Getting the "draggable" attributes delivered to a handler
realtime( element, 'attr' ).get( [ 'draggable' ], handleChanges );
// Retreiving the "draggable" attributes
const records = realtime( document.body, 'attr' ).get( [ 'draggable' ] );
// Deligating to handler
handleChanges( records );

Concept: Realtime Attributes

Get both current and future attributes delivered to the same handler function:

// Getting the "draggable" attributes delivered to a handler
realtime( element, 'attr' ).get( [ 'draggable' ], handleChanges );
// Observing future "draggable" attribute changes
realtime( element, 'attr' ).observe( [ 'draggable' ], handleChanges );

--> Use the options.live flag to achieve the same:

// Getting current and future state of the "draggable" attributes delivered to a handler
realtime( element, 'attr' ).get( [ 'draggable' ], handleChanges, { live: true } );

--> Use the options.live flag in conjunction with other concepts:

// Using the options.live flag with other flags
const abortController = new AbortController;
realtime( element, 'attr' ).get( [ 'attr1', 'attr2', 'attr3' ], handleChanges, {
    newValue: true,
    oldValue: true,
    atomic: true,
    live: true,
    signal: abortController.signal,
    lifecycleSignals: true,
    timing: 'sync',
    eventDetailsL true,
} );

Method: realdom.realtime( context ).attr()

An alias for realtime( context, 'attr' ).get().

Implementation Notes

  • The realtime API's unique timing capabilities is based on literal interception of DOM APIs. And here is the complete list of them:

    • Node: insertBefore, replaceChild, removeChild, appendChild, textContent, nodeValue.
    • Element: insertAdjacentElement, insertAdjacentHTML, setHTML, replaceChildren, replaceWith, remove. before, after, append, prepend, toggleAttribute, removeAttribute, setAttribute.
    • HTMLElement: outerText, innerText.

    You may need to consider this caveat on your specific usecase.

The Render Scheduling API

Eliminate layout thrashing by scheduling DOM read/write operations. (Compare fastdom)

schedule( 'read', () => {
  console.log( 'reading phase of the UI' );
} );

schedule( 'write', () => {
  console.log( 'writing phase of the UI' );
} );

schedule( 'read', () => {
  console.log( 'reading phase of the UI'  );
} );

schedule( 'write', () => {
  console.log( 'writing phase of the UI'  );
} );
reading phase of the UI
reading phase of the UI
writing phase of the UI
writing phase of the UI

Concept

The schedule API works as a regulatory layer between your app/library and the DOM. It lets you think of the DOM in terms of a "reading" phase and a "writing" phase, and lets you hook into this cycle when working with the DOM: schedule( 'read', ... ) for doing "read" operations, and schedule( 'write', ... ) for doing "write" operations. Batching DOM operations this way lets us avoid unnecessary document reflows and dramatically speeds up layout performance.

Each read/write operation is added to a corresponding read/write queue. The queues are emptied (reads, then writes) at the turn of the next frame using window.requestAnimationFrame.

Method: realdom.schedule( 'read', ... )

// Signature
schedule( 'read', readCallback[, inPromiseMode = false ]);

Schedules a job for the "read" phase. Can return a promise that resolves when job eventually executes; you ask for a promise by supplying true as a second argument.

const promise = schedule( 'read', () => {
  const width = element.clientWidth;
}, true/*give back a promise*/ );

Method: realdom.schedule( 'write', ... )

// Signature
schedule( 'write', writeCallback[, inPromiseMode = false ]);

Schedules a job for the "write" phase. Can return a promise that resolves when job eventually executes; you ask for a promise by supplying true as a second argument.

const promise = schedule( 'write', () => {
  element.style.width = width + 'px';
}, true/*give back a promise*/ );

Method: realdom.schedule( 'cycle', ... )

// Signature
schedule( 'cycle', readCallback, writeCallback );

Puts your read/write operations in a cycle that keeps in sync with the UI's read/write cycle.

schedule( 'cycle',
    // onread
    () => {
        // Do a read operation
        const width = element.clientWidth;
        // Now if we return anything other than undefined, the "onwrite" block is executed
        return width; // recieved by the "onwrite" callback on its first parameter
    },
    // onwrite
    ( width, carried ) => {
        // Do a write operation
        element.style.width = width + 'px';
        // Now if we return anything other than undefined, the cycle repeats starting with the "onread" block
        return newCarry; // recieved by the "onwrite" block again on its second parameter: "carried"
    }
);

Issues

To report bugs or request features, please submit an issue.

License

MIT.