Make autoplay/pause work correctly with lazyloading.

This commit is contained in:
Dermot Duffy
2022-01-22 18:23:30 -08:00
parent 05bcbbb16b
commit 34448549b6
7 changed files with 182 additions and 122 deletions
+91
View File
@@ -0,0 +1,91 @@
import { EmblaCarouselType, EmblaPluginType } from 'embla-carousel';
export type LazyloadOptionsType = {
count?: number;
lazyloadCallback: (index: number, slide: HTMLElement) => void;
};
export const defaultOptions: Partial<LazyloadOptionsType> = {
count: 0,
};
export type LazyloadType = EmblaPluginType<LazyloadOptionsType> & {
hasLazyloaded: (index: number) => boolean;
};
export function Lazyload(userOptions?: LazyloadOptionsType): LazyloadType {
const options = Object.assign({}, defaultOptions, userOptions);
let carousel: EmblaCarouselType;
let slides: HTMLElement[];
const isSlideLazyloaded: Record<number, boolean> = {};
/**
* Initialize the plugin.
*/
function init(embla: EmblaCarouselType): void {
carousel = embla;
slides = carousel.slideNodes();
carousel.on('init', lazyLoadHandler);
carousel.on('select', lazyLoadHandler);
carousel.on('resize', lazyLoadHandler);
}
/**
* Destroy the plugin.
*/
function destroy(): void {
carousel.off('init', lazyLoadHandler);
carousel.off('select', lazyLoadHandler);
carousel.off('resize', lazyLoadHandler);
}
/**
* Determine if a slide index has been lazily loaded.
* @param index Slide index.
* @returns `true` if the slide has been lazily loaded.
*/
function hasLazyloaded(index: number): boolean {
return !!isSlideLazyloaded[index];
}
/**
* Lazily load media in the carousel.
*/
function lazyLoadHandler(): void {
const lazyLoadCount = options.count ?? 0;
const slidesInView = carousel.slidesInView(true);
const slidesToLoad = new Set<number>();
const minSlide = Math.min(...slidesInView);
const maxSlide = Math.max(...slidesInView);
// Lazily load 'count' slides on either side of the slides in view.
for (let i = 1; i <= lazyLoadCount && minSlide - i >= 0; i++) {
slidesToLoad.add(minSlide - i);
}
slidesInView.forEach((index) => slidesToLoad.add(index));
for (let i = 1; i <= lazyLoadCount && maxSlide + i < slides.length; i++) {
slidesToLoad.add(maxSlide + i);
}
slidesToLoad.forEach((index) => {
// Only lazy load slides that are not already loaded.
if (isSlideLazyloaded[index]) {
return;
}
isSlideLazyloaded[index] = true;
options.lazyloadCallback(index, slides[index]);
});
}
const self: LazyloadType = {
name: 'Lazyload',
options,
init,
destroy,
hasLazyloaded,
};
return self;
}
+24 -19
View File
@@ -1,22 +1,26 @@
import { EmblaCarouselType, EmblaPluginType } from 'embla-carousel';
import { FrigateCardMediaPlayer } from '../../types';
export type MediaAutoplayOptionsType = {
export type MediaAutoPlayPauseOptionsType = {
autoplay?: boolean;
autopause?: boolean;
playerSelector: string;
};
export const defaultOptions: Partial<MediaAutoplayOptionsType> = {
autoplay: true,
export const defaultOptions: Partial<MediaAutoPlayPauseOptionsType> = {
// Frigate card media autoplays when the media loads, not necessarily when the
// slide is selected.
autoplay: false,
autopause: true,
};
export type MediaAutoplayType = EmblaPluginType<MediaAutoplayOptionsType>;
export type MediaAutoPlayPauseType = EmblaPluginType<MediaAutoPlayPauseOptionsType> & {
play: () => void;
}
export function MediaAutoplay(
userOptions?: MediaAutoplayOptionsType,
): MediaAutoplayType {
export function MediaAutoPlayPause(
userOptions?: MediaAutoPlayPauseOptionsType,
): MediaAutoPlayPauseType {
const options = Object.assign({}, defaultOptions, userOptions);
let carousel: EmblaCarouselType;
@@ -31,12 +35,12 @@ export function MediaAutoplay(
if (options.autopause) {
carousel.on('destroy', pauseAllHandler);
carousel.on('select', autopausePreviousHandler);
carousel.on('select', pausePrevious);
}
if (options.autoplay) {
carousel.on('select', autoplayCurrentHandler);
carousel.on('init', autoplayCurrentHandler);
carousel.on('select', play);
carousel.on('init', play);
}
}
@@ -46,12 +50,12 @@ export function MediaAutoplay(
function destroy(): void {
if (options.autopause) {
carousel.off('destroy', pauseAllHandler);
carousel.off('select', autopausePreviousHandler);
carousel.off('select', pausePrevious);
}
if (options.autoplay) {
carousel.off('select', autoplayCurrentHandler);
carousel.off('init', autoplayCurrentHandler);
carousel.off('select', play);
carousel.off('init', play);
}
}
@@ -60,8 +64,8 @@ export function MediaAutoplay(
* @param slide
* @returns A FrigateCardMediaPlayer object or `null`.
*/
function getPlayer(slide: HTMLElement): FrigateCardMediaPlayer | null {
return slide.querySelector(options.playerSelector) as FrigateCardMediaPlayer | null;
function getPlayer(slide: HTMLElement | undefined): FrigateCardMediaPlayer | null {
return slide?.querySelector(options.playerSelector) as FrigateCardMediaPlayer | null;
}
/**
@@ -74,22 +78,23 @@ export function MediaAutoplay(
/**
* Autoplay the current slide.
*/
function autoplayCurrentHandler(): void {
function play(): void {
getPlayer(slides[carousel.selectedScrollSnap()])?.play();
}
/**
* Autopause the previous slide.
*/
function autopausePreviousHandler(): void {
function pausePrevious(): void {
getPlayer(slides[carousel.previousScrollSnap()])?.pause();
}
const self: MediaAutoplayType = {
name: 'MediaAutoplay',
const self: MediaAutoPlayPauseType = {
name: 'MediaAutoPlayPause',
options,
init,
destroy,
play,
};
return self;
}