Window Resize Observer

Manage and perform a list of functions

Demo:

Open your console or resize this window

Let's make some simple example functions for this demo:

After page load, if this window is resized, let's make a UI update and log some messages to the console:

UI The page has not been resized.

Javascript

function updateDemoUI()
{
    const badge = document.getElementById('demoTarget-UI');
    const text = document.getElementById('demoTarget-text');

    badge.classList.replace('badge-primary', 'badge-success');
    text.innerText = 'The UI has been updated';

}

function test1() { console.log('performed test function 1'); }
function test2() { console.log('performed test function 2'); }
function test3() { console.log('performed test function 3'); }

This demo runs each function once. The WindowResizeObserver can be continuous and/or allow functions to run again after a timeout.

How to:

Javascript

Get a copy of WindowResizeObserver.js

Javascript

class WindowResizeObserver
{
    /**
     *
     * @param {Boolean} [once] Perform all actions once. Default false.
     * @param {Number} [queueDelay] Milliseconds before enqueueing again. Default 0 - does not requeue.
     *
     */
    constructor(once = false, queueDelay = 0)
    {
        this.actions = [];
        this.once = once;
        this.queueDelay = queueDelay;
        this.enqueueActions();
    }


    /**
     *
     * Assign 'resize' listener to window that will run the main function
     *
     * @return {undefined}
     */
    enqueueActions()
    {
        window.addEventListener('resize', (event) =>
        {
            this.performActions();

        }, { once: this.once });
    }


    /**
     * Change action enqueue delay or turn it off by setting it to 0
     *
     * @param {Number} queueDelay Milliseconds delay before enqueueing action list again.
     *
     * @return {undefined}
     */
    resetQueueDelay(queueDelay)
    {
        if (queueDelay > 0) { this.enqueueActions(); }
        else { this.queueDelay = 0; }
    }


    /**
     *
     * main: do each action then put the actions back in queue
     *
     * @return {undefined}
     *
     */
    performActions()
    {
        this.actions.forEach( (action) => { action(); } );
        if (this.queueDelay)
        {
            setTimeout(() => { this.enqueueActions(); }, this.queueDelay);
        }
    }


    /**
     * Get the actions list
     *
     * @param {Boolean} [asShallowCopy] Optional. Default false.
     *
     * @return {*[]|[]}
     */
    getActions(asShallowCopy = false)
    {
        // return this.actions;
        return (asShallowCopy) ? [...this.actions] : this.actions;
    }


    /**
     * Get length of list of actions
     *
     * @return {Number}
     */
    getActionsLength() { return this.actions.length; }


    /**
     * Get an action at an index
     *
     * @param {Number} pos
     *
     * @return {Function}
     */
    getActionAt(pos) { return this.actions[pos]; }


    /**
     * Get the index of an action by its name
     *
     * @param {Function} action
     *
     * @return {number}
     */
    getIndexOf(action)
    {
        const pos = this.actions.indexOf(action);

        if(pos >= 0) { return pos; }
        else { console.error(`No index found for ${action.name}.`); }
    }


    /**
     * Push to actions array
     *
     * @param {Function} action
     *
     * @return {undefined}
     */
    append(action) { this.actions.push(action); }


    /**
     * Unshift to actions array
     *
     * @param {Function} action
     *
     * @return {undefined}
     */
    prepend(action) { this.actions.unshift(action); }


    /**
     * Replace an action at an index
     *
     * @param {Number} pos
     * @param {Function} newAction
     *
     * @return {undefined}
     */
    replaceActionAt(pos, newAction)  { this.actions[pos] = newAction; }


    /**
     * Replace an action by its name
     *
     * @param {Function} action
     * @param {Function} newAction
     *
     * @return {undefined}
     */
    replaceAction(action, newAction)
    {
        const pos = this.getIndexOf(action);
        if ( pos >= 0) { this.replaceActionAt(pos, newAction); }
        else { console.error(`Unable to swap ${action} for ${newAction}. No index found for ${action}.`); }
    }


    /**
     * Swap positions of two actions
     *
     * @param {Number} posA Index of an action to be swapped
     * @param {Number} posB Index of an action to be swapped
     *
     * @return {undefined}
     */
    swapActionsAt(posA, posB)
    {
        if (this.actions[a] && this.actions[b])
        {
            [this.actions[a], this.actions[b]] = [this.actions[b], this.actions[a]];
        }

        else
        {
            if (this.actions[a] === undefined && this.actions[b] === undefined)
            {
                console.error(`Attempting to swap with undefined indices: [${a}] and [${b}].`)
            }

            if(this.actions[a] === undefined && this.actions[b] !== undefined)
            {
                console.error(`Attempting to swap [${b}] with an undefined index [${a}].`)
            }

            if(this.actions[b] === undefined && this.actions[a] !== undefined)
            {
                console.error(`Attempting to swap [${a}] with an undefined index [${b}].`)
            }
        }
    }


    /**
     * Get index positions of functions a and b and passes them to swapActionsAt()
     *
     * @param {Function} actionA A function to swap
     * @param {Function} actionB A function to swap
     */
    swapActions(actionA, actionB)
    {
        const posA = this.getIndexOf(a);
        const posB = this.getIndexOf(b);
        this.swapActionsAt(posA, posB);
    }


    /**
     * Remove a function from list of actions by index
     *
     * @param {number} pos
     *
     * @return {undefined}
     */
    removeActionAt(pos) { this.actions.splice(pos, 1); }


    /**
     * Remove a function from list of actions by name
     *
     * @param {Function} action
     *
     * @return {undefined}
     */
    removeAction(action) { this.removeActionAt( this.getIndexOf(action) ); }


    /**
     * Reset the action queue to an empty array
     *
     * @return undefined
     */
    clearQueue()
    {
        this.actions = [];
    }


    /**
     * Log the list of actions
     *
     * @param {Boolean} asShallowCopy Default false
     *
     * @return {undefined}
     */
    dumpActions(asShallowCopy = false)
    {
        console.log(this.getActions(asShallowCopy));
    }


    /**
     * Log the action list's indices and (functions or function names)
     *
     * @param {boolean} asShallowCopy Default false
     * @param {boolean} namesOnly Default false
     *
     * @return {undefined}
     */
    dumpValues(asShallowCopy = false, namesOnly = false)
    {
        const current = this.getActions(asShallowCopy);
        if (namesOnly) { for (const i in current) { console.log(`${i}: ${current[i].name}`); } }
        else { for (const i in current) { console.log(`${i}: ${current[i]}`); } }
    }

    /**
     * Log the action list formatted as a string
     *
     * @param {Boolean} asShallowCopy Default false
     *
     * @return {undefined}
     */
    dumpActionNames(asShallowCopy = false)
    {
        const current = this.getActions(asShallowCopy);
        const names = this.actionNamesToString(current);
        console.log(names);
    }


    /**
     * Build a string from the names of the functions in the action list
     *
     * @param {Array} actionsList
     * @return {String}
     */
    actionNamesToString(actionsList)
    {
        // get each function name
        const names = [];
        for (const action of actionsList) { names.push(action.name) }

        // return them as a string
        return '[' + names.join(', ') + ']';
    }

}

Create an instance of the WindowResizeObserver class:

Javascript

<script src="path/to/WindowResizeObserver.js"></script>
<script>
    const ResizeObserver = new WindowResizeObserver(true);
</script>

Populate the action queue using append():

Javascript

ResizeObserver.append(updateDemoUI);
ResizeObserver.append(test1);
ResizeObserver.append(test2);
ResizeObserver.append(test3);

Using these demo functions, when you resize the window, your console output should be:

performed test function 1

performed test function 2

performed test function 3

More:

What else can you do with WindowResizeObserver.js?

Observe your action queue:

  • Get action queue as a string with dumpActionNames(). Pass in true to get a shallow copy.
  • Log the action queue to the console with dumpActions(). Pass in true to get a shallow copy.
  • Log each index and action definition in the queue with dumpValues().
    • Pass in true as the first parameter to get a shallow copy.
    • If you pass in true as the second parameter, it will log function names instead of definitions.

Manage your action queue:

  • Add a function to the beginning of the queue with prepend(action)
  • Add a function to the end of the queue with append(action)
  • Replace an action by name or index position
    • replaceAction(action, newAction)
    • replaceActionAt(pos, newAction)
  • Swap the position of 2 actions by name or index position
    • swapActions(actionA, actionB)
    • swapActionsAt(posA, posB)
  • Remove all actions from the queue with clearQueue()
  • Remove an action by name or index position
    • removeAction(action)
    • removeActionAt(pos)
  • Enable, disable, or adjust the re-queue delay with resetQueueDelay(delay) where 'delay' is some number of milliseconds.
    • Pass in 0 to disable. Negative numbers will be reset to 0.
    • Use a number greater than 0 to enable or adjust the re-queue delay.
  • Trigger the queue with performActions()

Query your action queue:

  • Get the action queue with getActions(). Pass in true to get a shallow copy.
  • Get the number of actions in queue with getActionsLength()
  • Get the position of an action, by name, with getIndexOf(action)
  • Get the action at an index with getActionAt(pos)

Options:

Running the queue more than once.

Decide how often your functions need to run.

There are 4 ways you can use the WindowResizeObserver. Two of them are "safe". One can be questionable, and one can be outright madness. The class has 2 optional parameters for construction. By default, they are both false. Using the class this way can quickly lead to your functions being run dozens, hundreds, thousands... of times. Chances are that's not what you want to do.

You should initialize new instances by passing in 'true' as the first argument. This will make your functions run once. The page will have to be reloaded for them to run again.

You can also pass in a number (of milliseconds) as the second argument. Doing so will trigger a timeout before re-queueing your action list again. For example:

const ResizeObserver = new WindowResizeObserver(true, 1000);

That will create an instance that runs once, then waits one second before allowing actions to run again.

If you have questions about how to use WindowResizeObserver.js to implement actions triggered by window size changes on your website, hit me up on LinkedIn or Github.

Project Galleries

Tool

Color Average

Blend two colors to get their linear and logarithmic mid-point.

Tool

Multi Tab Opener

Like bookmarks on steroids: time released links with options and storage.

App

Jitter Bug

Simulated page traffic and product sales for marketing.

App

Shopping Cart

Originally made for use on Unbounce with Checkout Champ.

Tool

Public Library

A nifty little reading lens for classic novels using the Spritz SDK.