@juggle/resize-observer
Advanced tools
Comparing version 0.10.1 to 1.0.0-rc.0
import { ResizeObserverController } from './ResizeObserverController'; | ||
import { ResizeObserverBoxOptions } from './ResizeObserverBoxOptions'; | ||
import { POLYFILL_CONSOLE_OUTPUT } from './utils/prettify'; | ||
const DPPB = ResizeObserverBoxOptions.DEVICE_PIXEL_BORDER_BOX; | ||
export default class ResizeObserver { | ||
constructor(callback) { | ||
if (arguments.length === 0) { | ||
throw new TypeError(`Failed to construct 'ResizeObserver': 1 argument required, but only 0 present.`); | ||
} | ||
if (typeof callback !== 'function') { | ||
throw new TypeError(`Failed to construct 'ResizeObserver': The callback provided as parameter 1 is not a function.`); | ||
} | ||
ResizeObserverController.connect(this, callback); | ||
} | ||
observe(target, options) { | ||
if (arguments.length === 0) { | ||
throw new TypeError(`Failed to execute 'observe' on 'ResizeObserver': 1 argument required, but only 0 present.`); | ||
} | ||
if (target instanceof Element === false) { | ||
throw new TypeError(`Failed to execute 'observe' on 'ResizeObserver': parameter 1 is not of type 'Element`); | ||
} | ||
if (options && options.box === DPPB && target.tagName !== 'CANVAS') { | ||
@@ -15,2 +28,8 @@ throw new Error(`Can only watch ${options.box} on canvas elements.`); | ||
unobserve(target) { | ||
if (arguments.length === 0) { | ||
throw new TypeError(`Failed to execute 'unobserve' on 'ResizeObserver': 1 argument required, but only 0 present.`); | ||
} | ||
if (target instanceof Element === false) { | ||
throw new TypeError(`Failed to execute 'unobserve' on 'ResizeObserver': parameter 1 is not of type 'Element`); | ||
} | ||
ResizeObserverController.unobserve(this, target); | ||
@@ -22,5 +41,5 @@ } | ||
static toString() { | ||
return 'function ResizeObserver () { [polyfill code] }'; | ||
return POLYFILL_CONSOLE_OUTPUT; | ||
} | ||
} | ||
export { ResizeObserver }; |
@@ -1,2 +0,2 @@ | ||
import { schedule } from './utils/scheduler'; | ||
import { scheduler } from './utils/scheduler'; | ||
import { ResizeObservation } from './ResizeObservation'; | ||
@@ -11,2 +11,8 @@ import { ResizeObserverDetail } from './ResizeObserverDetail'; | ||
const observerMap = new Map(); | ||
let watching = 0; | ||
const updateCount = (n) => { | ||
!watching && n > 0 && scheduler.start(); | ||
watching += n; | ||
!watching && scheduler.stop(); | ||
}; | ||
const getObservationIndex = (observationTargets, target) => { | ||
@@ -43,3 +49,4 @@ for (let i = 0; i < observationTargets.length; i += 1) { | ||
detail.observationTargets.push(new ResizeObservation(target, options && options.box)); | ||
schedule(); | ||
updateCount(1); | ||
scheduler.schedule(); | ||
} | ||
@@ -54,2 +61,3 @@ } | ||
detail.observationTargets.splice(index, 1); | ||
updateCount(-1); | ||
} | ||
@@ -63,2 +71,3 @@ } | ||
observerMap.delete(resizeObserver); | ||
updateCount(-detail.observationTargets.length); | ||
} | ||
@@ -65,0 +74,0 @@ } |
@@ -1,2 +0,13 @@ | ||
declare const schedule: () => void; | ||
export { schedule }; | ||
declare class Scheduler { | ||
private observer; | ||
private listener; | ||
stopped: boolean; | ||
constructor(); | ||
run(frames: number): void; | ||
schedule(): void; | ||
private observe; | ||
start(): void; | ||
stop(): void; | ||
} | ||
declare const scheduler: Scheduler; | ||
export { scheduler }; |
import { process } from '../ResizeObserverController'; | ||
import { prettifyConsoleOutput } from './prettify'; | ||
const requestAnimationFrame = window.requestAnimationFrame; | ||
const observerConfig = { attributes: true, characterData: true, childList: true, subtree: true }; | ||
const events = [ | ||
'resize', | ||
'load', | ||
'transitionend', | ||
@@ -17,23 +21,80 @@ 'animationend', | ||
]; | ||
let frameId; | ||
const run = (frames) => { | ||
cancelAnimationFrame(frameId); | ||
frameId = requestAnimationFrame(() => { | ||
if (process()) { | ||
run(60); | ||
const rafSlots = new Map(); | ||
const resizeObserverSlots = new Map(); | ||
let handle; | ||
const dispatchCallbacksOnNextFrame = () => { | ||
if (typeof handle === 'number') { | ||
return; | ||
} | ||
function dispatchFrameEvents(t) { | ||
handle = undefined; | ||
const callbacks = []; | ||
rafSlots.forEach(callback => callbacks.push(callback)); | ||
resizeObserverSlots.forEach(callback => callbacks.push(callback)); | ||
rafSlots.clear(); | ||
resizeObserverSlots.clear(); | ||
for (let callback of callbacks) { | ||
callback(t); | ||
} | ||
else if (frames) { | ||
run(frames - 1); | ||
} | ||
}); | ||
} | ||
; | ||
handle = requestAnimationFrame(dispatchFrameEvents); | ||
}; | ||
const schedule = () => run(1); | ||
events.forEach(name => window.addEventListener(name, schedule, true)); | ||
const createObserver = () => { | ||
if ('MutationObserver' in window) { | ||
const observerConfig = { attributes: true, characterData: true, childList: true, subtree: true }; | ||
new MutationObserver(schedule).observe(document.body, observerConfig); | ||
class Scheduler { | ||
constructor() { | ||
this.stopped = true; | ||
this.listener = () => this.schedule(); | ||
} | ||
run(frames) { | ||
resizeObserverSlots.set(this, () => { | ||
if (process()) { | ||
this.run(60); | ||
} | ||
else if (frames) { | ||
this.run(frames - 1); | ||
} | ||
}); | ||
dispatchCallbacksOnNextFrame(); | ||
} | ||
schedule() { | ||
this.run(1); | ||
} | ||
observe() { | ||
const cb = () => this.observer && this.observer.observe(document.body, observerConfig); | ||
document.body ? cb() : window.addEventListener('DOMContentLoaded', cb); | ||
} | ||
start() { | ||
if (this.stopped) { | ||
this.stopped = false; | ||
if ('MutationObserver' in window) { | ||
this.observer = new MutationObserver(this.listener); | ||
this.observe(); | ||
} | ||
events.forEach(name => window.addEventListener(name, this.listener, true)); | ||
} | ||
} | ||
stop() { | ||
if (!this.stopped) { | ||
this.observer && this.observer.disconnect(); | ||
events.forEach(name => window.removeEventListener(name, this.listener, true)); | ||
this.stopped = true; | ||
} | ||
} | ||
} | ||
const scheduler = new Scheduler(); | ||
let rafIdBase = 0; | ||
window.requestAnimationFrame = function (callback) { | ||
if (typeof callback !== 'function') { | ||
throw new Error('requestAnimationFrame expects 1 callback argument of type function.'); | ||
} | ||
const handle = rafIdBase += 1; | ||
rafSlots.set(handle, callback); | ||
dispatchCallbacksOnNextFrame(); | ||
return handle; | ||
}; | ||
document.body ? createObserver() : document.addEventListener('DOMContentLoaded', createObserver); | ||
export { schedule }; | ||
window.cancelAnimationFrame = function (handle) { | ||
rafSlots.delete(handle); | ||
}; | ||
prettifyConsoleOutput(window.requestAnimationFrame); | ||
prettifyConsoleOutput(window.cancelAnimationFrame); | ||
export { scheduler }; |
{ | ||
"name": "@juggle/resize-observer", | ||
"version": "0.10.1", | ||
"version": "1.0.0-rc.0", | ||
"description": "ResizeObserver - Based on the official draft specification", | ||
@@ -5,0 +5,0 @@ "main": "./lib/ResizeObserver.js", |
@@ -131,11 +131,24 @@ # Resize Observer Polyfill | ||
## Features | ||
- Inbuilt resize loop protection. | ||
- Supports pseudo classes `:hover`, `:active` and `:focus`. | ||
- Supports transitions and animations, including infinite and long-running. | ||
- Detects changes which occur during animation frame. | ||
- Includes support for latest draft spec - observing different box sizes. | ||
- Polls only when required, then shuts down automatically, reducing CPU usage. | ||
- No notification delay - Notifications are batched and delivered immediately, before the next paint. | ||
## Limitations | ||
- No support for **IE10** and below. **IE11** is supported. | ||
- Dynamic stylesheet changes may not be noticed and updates will occur on the next interaction. | ||
- Dynamic stylesheet changes may not be noticed.* | ||
- Transitions with initial delays cannot be detected.* | ||
- Animations and transitions with long periods of no change, will not be detected.* | ||
\* If other interaction occurs, changes will be detected. | ||
## TypeScript support | ||
This library is written in TypeScript, however, it's compiled into JavaScript during release. Definition files are included in the package and should be picked up automatically to re-enable support in TypeScript projects. |
41662
41
587
154