预览区组件
预览区可以理解为画布中元素的显示渲染模块,预览区的组件本质就是一个 Canvas 元素,该 canvas 元素会绘制到内核渲染画布中。
预览区组件是一个 React hooks 组件,元素默认的 Props 参数如下:
ts
/**
* 元素组件props参数,其实我们只需要关心以下参数:
* element 元素数据
* relativeTime 相对时间
* currentTime 绝对时间
* dirty 元素更新标识
*/
export interface PixiElementProps {
currentTime: number; // 绝对时间,元素相对视频的时间,默认从0开始
relativeTime: number | null; // 元素相对元素的延迟时间(startTime)的时间 = currentTime - startTime
visible: boolean; // 元素在当前时间节点是否显示
hide: boolean; // 隐藏元素,优先级会比 visible更高 - 编辑器中使用
lock: boolean; // 锁定元素,锁定元素之后元素将无法被编辑 - 编辑器中使用
scale: number; // 画布的缩放大小
trackIndex: number; // 轨道序号
element: BaseElement; // 元素数据
parent?: PIXI.Container; // 父元素的PIXI容器节点
dirty: string; // 如果数据发生变化,表示通知元素进行更新
store: Store; // 组件全局参数和方法的集合
env: Env; // 内核的使用环境
}
/**
* 内核的使用环境
* editor: 编辑模式
* preview: 预览模式
* export: 导出视频模式
*/
export type Env = "editor" | "preview" | "export";
需要注意的是env
这个参数,正常情况下,在不同的环境中,我们的渲染业务都是基于 canvas 做的渲染,但是有些插件可能在不同环境中的渲染模式是不一样的,比如视频,在编辑器和预览模式中可以直接在画布中绘制<video />
标签元素,但是在导出视频的时候,需要逐帧解码。
以二维码插件为例:
tsx
import { useMemo, useEffect, useState, useRef } from "react";
import * as PIXI from "pixi.js";
import QRCode from "qrcode";
import { plugin, options } from "@h5/videoPluginSDK";
import { QrcodeElement } from "./types";
const { Animate, ControlElment, useSyncPixiElement } = plugin;
function QrcodeEl(props: PixiElementProps) {
const { visible, env, trackIndex, relativeTime } = props;
const element = props.element as QrcodeElement;
// const [, forceUpdate] = useReducer(x => x + 1, 0);
const store = props.store;
const animateRef = useRef();
const cav = useRef<HTMLCanvasElement>();
// 绘制二维码
const drawQrcode = (ctx, elem) => {
// 处理组件中异步问题,当渲染的时候会等renderAsyncMark[elementId] 变为 'success'才会继续执行
store.renderAsyncMark[element.id] = "start";
// 生成二维码
QRCode.toDataURL(
elem.text,
{
errorCorrectionLevel: elem.correctLevel,
type: "image/png",
quality: 1,
width: elem.style.width,
height: elem.style.height,
color: {
dark: elem.colorDark,
light: elem.colorLight,
},
},
(err, base64 = "") => {
if (!base64) return;
// 二维码创建成功后,使用图片当新的纹理
const img = new Image();
img.onload = () => {
const sprite = pixiElem.children.find(
(d) => d.name === "element"
) as PIXI.Sprite;
ctx.drawImage(img, 0, 0);
sprite.texture.update();
syncPixiStyle({ ...elem.style }, pixiElem);
store.renderAsyncMark[elem.id] = "success";
};
img.src = base64;
}
);
};
// 创建pixi元素
const pixiElem = useMemo<PIXI.Container>(() => {
const container = new PIXI.Container();
// 默认使用一个空白的图片当纹理创建一个图片元素
cav.current = document.createElement("canvas");
cav.current.width = element.style.width;
cav.current.height = element.style.height;
// 绘制二维码
drawQrcode(cav.current.getContext("2d"), element);
const texture = PIXI.Texture.from(cav.current);
const sprite = new PIXI.Sprite(texture);
// useSyncPixiElement 会使用到 name
sprite.name = "element";
container.addChild(sprite);
return container;
}, [trackIndex]);
// 使用防抖函数更新视图,因为修改二维码内容是一个非常消耗性能的操作
const updateDataDebounce = useCallback(
debounce((elem) => {
drawQrcode(cav.current.getContext("2d"), elem);
}, 300),
[]
);
// 设置大小
useEffect(
() => updateDataDebounce(element),
[
element.text,
element.style.width,
element.style.height,
element.colorDark,
element.colorLight,
element.correctLevel,
]
);
/**
* 数据和pixi元素进行绑定,这部分代码可以直接复制使用,通常情况下不会变化
* syncPixiStyle是一个同步元素数据和Pixi元素样式的方法,具体使用方法: syncPixiStyle({ ...element.style }, pixiElem);
*/
const [syncPixiStyle] = useSyncPixiElement(
pixiElem,
{
style: { ...element.style! },
parent: props.parent!,
animateRef,
visible,
hide: props.hide,
lock: props.lock,
relativeTime: props.relativeTime,
store: props.store,
},
["x", "y", "width", "height", "alpha", "rotation"],
element
);
return (
<>
{env === "editor" && (
<ControlElment
trackIndex={trackIndex}
hide={props.hide}
lock={props.lock}
scale={props.scale}
store={props.store}
element={element}
visible={visible}
/>
)}
<Animate
ref={animateRef}
store={props.store}
pixiElem={pixiElem}
elementData={element}
currentTime={props.currentTime}
relativeTime={props.relativeTime}
/>
</>
);
}
export default QrcodeEl;