Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand All @@ -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` */
Expand All @@ -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
Expand Down
54 changes: 44 additions & 10 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,21 @@ export type CSSStyleObserverCallback = (
values: Readonly<CSSDeclarations>
) => 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.
Expand All @@ -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;
}

/**
Expand Down Expand Up @@ -73,7 +92,7 @@ export class CSSStyleObserver {
}

/*
* Observer CSS variables and their iternal identifiers.
* Observer CSS variables and their internal identifiers.
*/
private _observedVariables: string[];

Expand All @@ -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
*
Expand Down Expand Up @@ -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);
}
}
}
Expand Down