Skip to content

Instantly share code, notes, and snippets.

@wycats
Created July 30, 2020 19:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wycats/a87b650d591863d3fc3668ec6661b3ba to your computer and use it in GitHub Desktop.
Save wycats/a87b650d591863d3fc3668ec6661b3ba to your computer and use it in GitHub Desktop.
alternative observer design
// observed popover is interested in changes to `width` and `height` relative to the
// page's origin, and normalized by rounding them to the nearest integer.
let observedPopover = new RectObserver(
popoverElement,
{
width: Math.round,
height: Math.round
},
{
relativeTo: 'origin' // not viewport
}
);
// observedViewport is interested in changes to the top and height of the viewport, relative
// to the page's origin, and normalized by rounding. These changes can occur if the page scrolls
// or is resized.
let observedViewport = new ViewportObserver({
top: Math.round,
height: Math.round
});
// observedTarget is interested in changes to all four measurements, relative to the page's
// origin, and also normalized by rounding.
let observedTarget = new RectObserver(targetElement, {
top: Math.round,
left: Math.round,
width: Math.round,
height: Math.round
}, {
relativeTo: 'origin'
});
// the beforePaint callback runs after the entire rendering pipeline, but before painting. Just like ResizeObserver,
// it may (and often will) result in a repetition of earlier steps in the pipeline.
//
// The callback runs if any of the specified coordinates have changed, after accounting for normalization.
Observer.all([observedPopover, observedViewport, observedTarget]).beforePaint((popover, viewport, target) => {
// do some math to compute the location of the popover
let { top, left } = compute(popover, viewport, target);
popover.target.style.top = `${top}px`;
popover.target.style.left = `${left}px`;
});
function compute(popover, viewport, target) {
// ...
}
declare const popoverElement: HTMLElement;
declare const targetElement: HTMLElement;
interface ObserverCoordinates {
top?: (value: number) => number;
left?: (value: number) => number;
width?: (value: number) => number;
height?: (value: number) => number;
}
interface ObserverOptions {
relativeTo: 'origin' | 'viewport' | 'offset-parent' | HTMLElement;
}
interface ObservedBox {
target: HTMLElement;
top: number;
left: number;
width: number;
height: number;
}
declare class RectObserver {
declare value: ObservedBox;
constructor(
element: HTMLElement,
coordinates: ObserverCoordinates,
options: ObserverOptions
);
}
declare class ViewportObserver {
declare value: ObservedBox;
constructor(coordinates: ObserverCoordinates);
}
type DOMObserver = RectObserver | ViewportObserver;
type ValueForDOMObservers<T extends readonly DOMObserver[]> = {
[P in keyof T]: P extends number ? T[P]["value"] : never;
};
declare class Observer<T extends readonly DOMObserver[]> {
static all<T extends readonly DOMObserver[]>(observers: T): Observer<T>;
beforePaint(callback: (...args: ValueForDOMObservers<T>) => void): void;
}
let observedPopover = new RectObserver(
popoverElement,
{
width: Math.round,
height: Math.round
},
{
relativeTo: 'origin' // not viewport
}
);
let observedViewport = new ViewportObserver({
top: Math.round,
height: Math.round
});
let observedTarget = new RectObserver(targetElement, {
top: Math.round,
left: Math.round,
width: Math.round,
height: Math.round
}, {
relativeTo: 'origin'
});
Observer.all([observedPopover, observedViewport, observedTarget]).beforePaint((popover, viewport, target) => {
// do some math to compute the location of the popover
let { top, left } = compute(popover, viewport, target);
popover.target.style.top = `${top}px`;
popover.target.style.left = `${left}px`;
});
declare function compute(popover: ObservedBox, viewport: ObservedBox, target: ObservedBox): { top: number, left: number };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment