diff --git a/README.md b/README.md index 187de86..c650bfe 100644 --- a/README.md +++ b/README.md @@ -28,10 +28,10 @@ npm install @bramus/style-observer const CSSStyleObserver = require('@bramus/style-observer'); // Vanilla JS (ES6) -import CSSStyleObserver from '@bramus/style-observer'; +import CSSStyleObserver, { NotificationMode } from '@bramus/style-observer'; // TypeScript -import CSSStyleObserver from '@bramus/style-observer/src/index.ts' +import CSSStyleObserver, { NotificationMode } from '@bramus/style-observer/src/index.ts' const cssStyleObserver = new CSSStyleObserver( /* CSS Properties to observe */ @@ -40,7 +40,11 @@ const cssStyleObserver = new CSSStyleObserver( /* This is called whenever there are changes */ (values) => { console.log(values['--variable1'], values['--variable2'], values['display']); - } + }, + /* Configuration options */ + { + notificationMode?: NotificationMode.CHANGED_ONLY + } ); cssStyleObserver.attach(document.body); /* Attach observer to `document.body` */ @@ -50,6 +54,10 @@ cssStyleObserver.attach(document.body); /* Attach observer to `document.body` * cssStyleObserver.detach(); /* Detach observer */ ``` +### Configuration options + +* `notificationMode` (`NotificationMode`, default: `CHANGED_ONLY`): Determines whether to pass all properties (`ALL`) or only the changed ones (`CHANGED_ONLY`) into the callback + Try out a demo on CodePen: [https://codepen.io/bramus/pen/WNqKqxj](https://codepen.io/bramus/pen/WNqKqxj?editors=1111) ## Local Development diff --git a/src/index.ts b/src/index.ts index 2879fbf..48aa02d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,6 +19,21 @@ export type CSSStyleObserverCallback = ( values: Readonly ) => void; +/** + * Enum for callback modes + */ +export enum NotificationMode { + CHANGED_ONLY = 'changed_only', + ALL = 'all', +} + +/** + * Options for configuring the CSSStyleObserver + */ +export interface CSSStyleObserverOptions { + notificationMode?: NotificationMode; +} + /** * Passive observer for CSS properties. Instead of typical polling approach, it uses CSS * transitions to detect changes. @@ -37,14 +52,18 @@ export class CSSStyleObserver { * * @param observedVariables list of CSS variables to observe * @param callback callback that will be invoked every time any of listed CSS variables change + * @param options configuration options */ constructor( observedVariables: string[], - callback: CSSStyleObserverCallback + callback: CSSStyleObserverCallback, + options: CSSStyleObserverOptions = {} ) { this._observedVariables = observedVariables; this._callback = callback; this._targetElement = null; + this._cachedValues = {}; + this._notificationMode = options.notificationMode ?? NotificationMode.CHANGED_ONLY; } /** @@ -73,7 +92,7 @@ export class CSSStyleObserver { } /* - * Observer CSS variables and their iternal identifiers. + * Observer CSS variables and their internal identifiers. */ private _observedVariables: string[]; @@ -92,6 +111,16 @@ export class CSSStyleObserver { */ private _targetElement: HTMLElement | null; + /* + * Cache to store previous values of observed properties + */ + private _cachedValues: { [key: string]: string }; + + /* + * Mode to determine whether to observe all properties or only the changed ones + */ + private _notificationMode: NotificationMode; + /** * Attach the styles necessary to track the changes to the given element * @@ -125,16 +154,21 @@ export class CSSStyleObserver { if (this._targetElement) { const computedStyle = getComputedStyle(this._targetElement); - const variables: CSSDeclarations = {}; + const changedProperties: CSSDeclarations = {}; + + this._observedVariables.forEach(propertyName => { + const currentValue = computedStyle.getPropertyValue(propertyName); + const previousValue = this._cachedValues[propertyName] || ''; - this._observedVariables - .forEach(value => { - variables[value] = computedStyle.getPropertyValue(value); - }); + if (this._notificationMode === NotificationMode.ALL || currentValue !== previousValue) { + changedProperties[propertyName] = currentValue; + this._cachedValues[propertyName] = currentValue; + } + }); - // Do not invoke callback if no variables are defined - if (Object.keys(variables).length > 0) { - this._callback(variables); + // Invoke callback only if there are changes + if (Object.keys(changedProperties).length > 0) { + this._callback(changedProperties); } } }