Skip to content

Instantly share code, notes, and snippets.

@jonathantneal
Created April 7, 2022 04:34
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jonathantneal/d387612bdfe86fa2b7135bc359c38c07 to your computer and use it in GitHub Desktop.
Save jonathantneal/d387612bdfe86fa2b7135bc359c38c07 to your computer and use it in GitHub Desktop.
CSS Style Observer (695 bytes minified, 397 bytes gzipped)
class CSSStyleRecord {
constructor(
/** @type {{ target: Element, propertyName: string, oldValue: string, newValue: string }} */
init
) {
Object.assign(this, init)
}
}
class CSSStyleObserver {
#read = () => {
this.#frameId = setTimeout(this.#emit, 1000 / 4)
/** @type {string} */
let propertyName
/** @type {string} */
let oldValue
/** @type {string} */
let newValue
for (let [ target, [ cache, style ] ] of this.#observed) {
for (propertyName in cache) {
oldValue = cache[propertyName]
newValue = cache[propertyName] = style.getPropertyValue(propertyName)
if (oldValue !== newValue) {
this.#records.push(new CSSStyleRecord({
target,
propertyName,
oldValue,
newValue,
}))
}
}
}
}
#emit = () => {
this.#frameId = setTimeout(this.#read, 1000 / 4)
if (this.#records.length) {
this.#callback(this.#records.splice(0))
}
}
#frameId = 0
/** @type {CSSStyleRecord[]} */
#records = []
/** @type {Map<Element, [ Record<string, string>, CSSStyleDeclaration ]>} */
#observed = new Map()
/** @type {CSSStyleObserverCallback} */
#callback
constructor(/** @type {CSSStyleObserverCallback} */ callback) {
this.#callback = callback
}
observe(/** @type {Element} */ element, /** @type {string[]} */ ...propertyNames) {
let cache = this.#observed.get(element)?.[0]
/** @type {string} */
let propertyName
if (!cache) {
this.#observed.set(element, [ cache = {}, getComputedStyle(element) ])
}
for (propertyName of propertyNames) {
cache[propertyName] = ''
}
if (this.#frameId === 0) {
this.#read()
}
}
unobserve(/** @type {Element} */ element) {
if (this.#observed.delete(element) && this.#observed.size === 0) {
this.disconnect()
}
}
disconnect() {
clearTimeout(this.#frameId)
this.#frameId = 0
}
}
/** @typedef {{ (records: CSSStyleRecord[]): void }} CSSStyleObserverCallback */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment