Use an IntersectionObserver to reinit the carousel.
This commit is contained in:
@@ -18,21 +18,6 @@ const getEmptyImageSrc = (width: number, height: number) =>
|
||||
`data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}"%3E%3C/svg%3E`;
|
||||
export const IMG_EMPTY = getEmptyImageSrc(16, 9);
|
||||
|
||||
/**
|
||||
* A note on carousel reinitialization:
|
||||
* - The carousel needs to be reinitialized when slide sizes change, and slide
|
||||
* sizes change when content is loaded (lazily or otherwise). Reinitializing
|
||||
* the carousel when it's moving causes a jarring 'reset', so complexity is
|
||||
* required here to reinitialize only when the carousel is stable (including
|
||||
* on first media load, which may be after first carousel initialization).
|
||||
* - When the carousel is dragged, it can be reinitialized via the settle event.
|
||||
* - On very first load, or when the user has configured no slide transition
|
||||
* effect it can be reinitialized on the media load itself as long as
|
||||
* clickAllowed() confirms the carousel is otherwise stable.
|
||||
* - Example bug when this reinitialization is not performed:
|
||||
* https://github.com/dermotduffy/frigate-hass-card/issues/651
|
||||
*/
|
||||
|
||||
@customElement('frigate-card-media-carousel')
|
||||
export class FrigateCardMediaCarousel extends FrigateCardCarousel {
|
||||
// A "map" from slide number to MediaShowInfo object.
|
||||
@@ -47,10 +32,14 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel {
|
||||
// fullscreen, etc. Always call the adaptive height handler when the size
|
||||
// changes.
|
||||
protected _resizeObserver: ResizeObserver;
|
||||
protected _intersectionObserver: IntersectionObserver;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._resizeObserver = new ResizeObserver(this._adaptiveHeightHandler.bind(this));
|
||||
this._intersectionObserver = new IntersectionObserver(
|
||||
this._intersectionHandler.bind(this),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,6 +94,7 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel {
|
||||
this.addEventListener('frigate-card:media-show', this._adaptiveHeightHandler);
|
||||
this.addEventListener('frigate-card:media-show', this._titleHandler);
|
||||
this._resizeObserver.observe(this);
|
||||
this._intersectionObserver.observe(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,6 +107,39 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel {
|
||||
this.removeEventListener('frigate-card:media-show', this._adaptiveHeightHandler);
|
||||
this.removeEventListener('frigate-card:media-show', this._titleHandler);
|
||||
this._resizeObserver.disconnect();
|
||||
this._intersectionObserver.disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the carousel intersects with the viewport.
|
||||
* @param entries The IntersectionObserverEntry entries (should be only 1).
|
||||
*/
|
||||
protected _intersectionHandler(entries: IntersectionObserverEntry[]): void {
|
||||
/**
|
||||
* - If the DOM that contains this carousel changes such that it causes
|
||||
* slides to entirely appear/disappear (e.g. `display: none` or hidden),
|
||||
* then the displayed slide sizes will significantly change and the
|
||||
* carousel will need to be reinitialized. Without this, odd bugs may
|
||||
* occur for some users in some circumstances causing the carousel to
|
||||
* appear 'stuck'.
|
||||
* - Example bug when this reinitialization is not performed:
|
||||
* https://github.com/dermotduffy/frigate-hass-card/issues/651
|
||||
*/
|
||||
|
||||
const reinit = (): void => {
|
||||
this._carousel?.reInit();
|
||||
};
|
||||
|
||||
if (entries.some((entry) => entry.isIntersecting)) {
|
||||
// For performance, run the reinit in idle cycles if the browser supports
|
||||
// it, but only give it 400ms before running as it may otherwise be
|
||||
// noticeable to the user.
|
||||
if (window.requestIdleCallback !== undefined) {
|
||||
window.requestIdleCallback(reinit, { timeout: 400 });
|
||||
} else {
|
||||
reinit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected _destroyCarousel(): void {
|
||||
@@ -150,22 +173,6 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel {
|
||||
// Dispatch MediaShow events as the carousel is moved.
|
||||
carousel?.on('init', this._selectSlideMediaShowHandler.bind(this));
|
||||
carousel?.on('select', this._selectSlideMediaShowHandler.bind(this));
|
||||
|
||||
carousel?.on('settle', this._reinitIfNecessary.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reinitialize the carousel if necessary. See 'A note on carousel
|
||||
* reinitialization' above.
|
||||
*/
|
||||
protected _reinitIfNecessary(): void {
|
||||
if (this._needReInit) {
|
||||
// The original options are included here, although this should not be
|
||||
// necessary (without including them, Safari ends up not having a looping
|
||||
// live carousel).
|
||||
this._carousel?.reInit(this._getOptions());
|
||||
this._needReInit = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -269,15 +276,6 @@ export class FrigateCardMediaCarousel extends FrigateCardCarousel {
|
||||
// isValidMediaShowInfo is used to prevent saving media info that will be
|
||||
// rejected upstream (empty 1x1 images will be rejected here).
|
||||
if (mediaShowInfo && isValidMediaShowInfo(mediaShowInfo)) {
|
||||
// If this is the first media load for this slide, the carousel needs to
|
||||
// reinitialized as the slide size has changed. See 'A note on carousel
|
||||
// reinitialization' above.
|
||||
if (!(slideIndex in this._mediaShowInfo)) {
|
||||
this._needReInit = true;
|
||||
if (this._carousel?.clickAllowed()) {
|
||||
this._reinitIfNecessary();
|
||||
}
|
||||
}
|
||||
this._mediaShowInfo[slideIndex] = mediaShowInfo;
|
||||
if (this._carousel && this._carousel?.selectedScrollSnap() === slideIndex) {
|
||||
dispatchExistingMediaShowInfoAsEvent(this, mediaShowInfo);
|
||||
|
||||
Reference in New Issue
Block a user