vevet
Version:
Vevet is a JavaScript library for creative development that simplifies crafting rich interactions like split text animations, carousels, marquees, preloading, and more.
141 lines (115 loc) • 3.79 kB
text/typescript
import { getPos } from 'get-image-pos';
import { TRequiredProps } from '@/internal/requiredProps';
import {
ICanvasMediaCallbacksMap,
ICanvasMediaMutableProps,
ICanvasMediaStaticProps,
} from './types';
import { Canvas, ICanvasRenderArg } from '../Canvas';
import { addEventListener } from '@/utils';
export * from './types';
/**
* The `CanvasMedia` class allows pre-rendering of media (such as images or video) onto a canvas.
* This can be useful for reducing payloads by preparing the media for further use in a more optimized form.
*
* [Documentation](https://antonbobrov.github.io/vevet/docs/components/CanvasMedia)
*
* @group Components
*/
export class CanvasMedia<
CallbacksMap extends ICanvasMediaCallbacksMap = ICanvasMediaCallbacksMap,
StaticProps extends ICanvasMediaStaticProps = ICanvasMediaStaticProps,
MutableProps extends ICanvasMediaMutableProps = ICanvasMediaMutableProps,
> extends Canvas<CallbacksMap, StaticProps, MutableProps> {
/** Get default static properties */
public _getStatic(): TRequiredProps<StaticProps> {
return {
...super._getStatic(),
autoRenderVideo: true,
} as TRequiredProps<StaticProps>;
}
/** Get default mutable properties */
public _getMutable(): TRequiredProps<MutableProps> {
return {
...super._getMutable(),
rule: 'cover',
} as TRequiredProps<MutableProps>;
}
constructor(props?: StaticProps & MutableProps) {
super(props);
this._setMediaEvents();
}
/** Checks if the media element has the `requestVideoFrameCallback` method */
protected get hasRequestVideoFrameCallback() {
return 'requestVideoFrameCallback' in this.props.media;
}
/** Add media events */
protected _setMediaEvents() {
const { autoRenderVideo: hasVideoAutoRender, media } = this.props;
if (!hasVideoAutoRender || !(media instanceof HTMLVideoElement)) {
return;
}
// use requestVideoFrameCallback
if (this.hasRequestVideoFrameCallback) {
this._requestVideoFrame();
return;
}
// use timeupdate listener
const timeupdate = addEventListener(media, 'timeupdate', () => {
this.render();
});
this.onDestroy(() => timeupdate());
}
/** Resize the canvas */
public resize() {
super.resize();
this.render();
}
/** Auto rendering for videos */
protected _requestVideoFrame() {
if (this.isDestroyed) {
return;
}
this.render();
const { media } = this.props;
if (media instanceof HTMLVideoElement) {
media.requestVideoFrameCallback(() => this._requestVideoFrame());
}
}
/** Pre-renders the media resource onto the canvas. */
public render() {
super.render((props) => this._prerender(props));
}
/**
* Prerenders the media onto the canvas using the specified positioning rule.
*/
protected _prerender({ width, height, ctx }: ICanvasRenderArg) {
const { media, rule } = this.props;
// Determine the media source and its dimensions
let source: Exclude<ICanvasMediaStaticProps['media'], Canvas>;
let sourceWidth: number | undefined;
let sourceHeight: number | undefined;
if (media instanceof Canvas) {
source = media.canvas;
sourceWidth = media.width;
sourceHeight = media.height;
} else {
source = media as any;
}
// Calculate media position and size based on the posRule
const size = getPos({
source,
sourceWidth,
sourceHeight,
rule,
scale: 1,
width,
height,
});
// Clear the canvas and draw the media with the calculated size
ctx.clearRect(0, 0, width, height);
ctx.drawImage(source, size.x, size.y, size.width, size.height);
// Trigger prerender callback
this.callbacks.emit('render', undefined);
}
}