press-plus
Version:
456 lines (399 loc) • 13.1 kB
text/typescript
// @ts-ignore
// eslint-disable-next-line import/no-unresolved
import { PAGInit, types, clearCache } from 'libpag-miniprogram';
import { createProxyPAGComposition } from './pag-composition';
import { createProxyPAGFile } from './pag-file';
import { createProxyPAGVIew } from './pag-view';
import type {
TipPAGWebOptions,
TipPAGWebCore,
TipPAGWebLoadResult,
TipPAGWebScaleInfo,
TipPAGWebBaseLayerInfo,
TipPAGWebLayerInfo,
PAG,
PAGTypes,
PAGFile,
PAGView,
PAGComposition,
PAGLayer,
Vector,
} from './types';
/**
* @description 二次封装,方便业务调用
* @class PAGWeb
*/
class PAGWeb {
private pag: PAG | null | undefined;
private pagTypes: PAGTypes | null | undefined;
private pagFile: PAGFile | null | undefined;
private pagView: PAGView | null | undefined;
private pagComposition: PAGComposition | null | undefined;
constructor(options: TipPAGWebOptions) {
this.pag = null;
this.pagFile = null;
this.pagView = null;
this.pagComposition = null;
if (options?.autoLoad) {
this.load();
}
}
/**
* @description 加载依赖库
* @returns Promise<TipPAGWebLoadResult>
* @memberof PAGWeb
*/
load(): Promise<TipPAGWebLoadResult> {
return new Promise((resolve, reject) => {
if (this.pag && this.pagTypes) {
resolve({ pag: this.pag, pagTypes: this.pagTypes });
return;
}
PAGInit({ locateFile: file => `/static/libpag/${file}` }) // 小程序包内路径
.then((pag) => {
this.pag = pag as any;
this.pagTypes = types as any;
resolve({ pag: this.pag, pagTypes: this.pagTypes as any });
})
.catch(err => reject(err));
});
}
/**
* @description 完成初始化并渲染
* @param {HTMLCanvasElement} $canvas 挂载的 <canvas />
* @param {File} file .pag格式的素材文件对象
* @param {object} [options={}] 选项,同 PAGWebOptions
* @returns Promise<TipPAGWebCore|undefined>
* @memberof PAGWeb
*/
async create(
$canvas: HTMLCanvasElement,
file: File,
options: TipPAGWebOptions = {},
): Promise<TipPAGWebCore|undefined> {
if (this.pagFile) this.pagFile.destroy();
if (this.pagView) this.pagView.destroy();
if (this.pagComposition) this.pagComposition.destroy();
const pagCanvas = $canvas;
const defaultOptions = {
useScale: true,
useCanvas2D: false,
firstFrame: true,
};
const initOptions = {
...defaultOptions,
...options,
};
// 加载素材
this.pagFile = await this.pag?.PAGFile?.load(file);
if (!this.pagFile) return;
// 初始化舞台
this.pagView = (await this.pag?.PAGView?.init(this.pagFile, pagCanvas, initOptions));
if (!this.pagView) return;
this.pagView.setRepeatCount(0); // 设置初始画面
// 挂载合成器
this.pagComposition = this.pagView.getComposition();
if (!this.pagComposition) return;
// 二次封装
const pagTypes = this.getPAGTypes();
const pagView = this.getPAGView();
const pagFile = this.getPAGFile();
const pagComposition = this.getPAGComposition();
// 返回
return {
pagTypes,
pagView,
pagFile,
pagComposition,
};
}
/**
* @description 封装 load 和 create,方便直接加载后渲染
* @param {HTMLCanvasElement} $canvas 挂载的 <canvas />
* @param {File} file .pag格式的素材文件对象
* @param {object} [options={}] 选项,同
* @returns Promise<TipPAGWebCore|undefined>
* @memberof PAGWeb
*/
async init($canvas: HTMLCanvasElement, file: File, options: TipPAGWebOptions = {}): Promise<TipPAGWebCore|undefined> {
await this.load();
const pag = await this.create($canvas, file, options);
return pag;
}
/**
* 清空舞台渲染的图层
* @description
* https://bbs.pag.art/thread/883
* PAGView 的 destroy 只是清除实例,并没有清除 Canvas 的内容或者从 DOM 树上移除 Canvas, 因为 Canvas 是由业务上创建的,控制权在业务上。如果你需要在动画结束后将画面清空,你有几个方法:
* 1. 当你不需要 CanvasElement 时,你可以将 CanvasElement 从 DOM 树上移除。
* 2. 当你还需要 CanvasElement 但希望它呈现透明状态时,你可以用 WebGL 命令手动将 Canvas 的内容清空;
* 或者使用 pagView.pagSurface.clearAll() 将内容清空,不过需要注意 PAGView 实例上的 pagSurface 是一个私有对象。
* @memberof PAGWeb
*/
clearAll() {
if (!this.pagView) {
return;
}
// @ts-ignore
this.pagView?.pagSurface?.clearAll();
}
/**
* 清空占用的内存
* @description 为避免PAG的缓存把用户缓存目录内存占用过高,建议在不需要使用PAG的时候,调用 clearCache() 方法进行缓存清理。
* @memberof PAGWeb
*/
clearCache() {
clearCache();
}
/**
* 获取内置类型
* @returns PAGTypes|null
* @memberof PAGWeb
*/
getPAGTypes() {
if (!this.pagTypes) return null;
return this.pagTypes;
}
/**
* 获取二次封装的 pagFile 对象
* @returns PAGFile|null
* @memberof PAGWeb
*/
getPAGFile() {
if (!this.pagFile) return null;
return createProxyPAGVIew(this.pagFile);
}
/**
* 获取二次封装的 pagView 对象
* @returns PAGView|null
* @memberof PAGWeb
*/
getPAGView() {
if (!this.pagView) return null;
return createProxyPAGFile(this.pagView);
}
/**
* 获取二次封装的 pagComposition 对象
* @returns PAGComposition|null
* @memberof PAGWeb
*/
getPAGComposition() {
if (!this.pagComposition) return null;
return createProxyPAGComposition(this.pagComposition);
}
/**
* 获取缩放信息
* @returns {(TipPAGWebScaleInfo | undefined)}
* @memberof PAGWeb
*/
getScaleInfo(): TipPAGWebScaleInfo | undefined {
const pagView = this.getPAGView();
const pagTypes = this.getPAGTypes();
if (!pagView || !pagTypes) return;
const { useScale } = pagView.pagViewOptions;
const dpr = useScale ? window.devicePixelRatio : 1; // 有开启 useScale
const m = pagView.matrix();
// @ts-ignore
const mi = pagTypes.MatrixIndex;
const scaleX = m.get(mi.a);
const scaleY = m.get(mi.d);
const tx = m.get(mi.tx);
const ty = m.get(mi.ty);
const info = {
dpr,
scaleX,
scaleY,
tx,
ty,
};
console.log('[info]pag-web: getScaleInfo', info);
return info;
}
/**
* 获取自构造的图层信息,方便业务调用
* @param {PAGLayer} layer 图层对象
* @returns {TipPAGWebBaseLayerInfo}
* @memberof PAGWeb
*/
getLayerInfo(layer: PAGLayer): TipPAGWebBaseLayerInfo {
const { right, bottom } = layer.getBounds(); // 算得是内部四个点的位置
const width = right;
const height = bottom;
const uniqueID = layer.uniqueID();
const layerType = layer.layerType();
const layerName = layer.layerName();
const alpha = layer.alpha();
const visible = layer.visible();
const editableIndex = layer.editableIndex();
const duration = layer.duration();
const frameRate = layer.frameRate();
const localStartTime = layer.startTime();
const startTime = layer.localTimeToGlobal(localStartTime);
return {
uniqueID,
layerType,
layerName,
width,
height,
alpha,
visible,
editableIndex,
frameRate,
startTime,
duration,
};
}
/**
* 获取可编辑图层集合
* @param {types.LayerType} layerTypes 过滤指定图层类型,若传入 LayerType.Image 代表只返回图像类型的图层
* @returns {TipPAGWebLayerInfo[]} 图层信息集合
* @memberof PAGWeb
*/
getEditableLayers(layerTypes: number[]): TipPAGWebLayerInfo[] {
const editableLayers: TipPAGWebLayerInfo[] = [];
const pagTypes = this.getPAGTypes();
const pagFile = this.getPAGFile();
if (!pagTypes || !pagFile) return [];
// 为空时,默认取图片类型
let layerTypeList = layerTypes;
if (!layerTypes || layerTypes.length === 0) {
layerTypeList = [pagTypes.LayerType.Image];
}
// 根据图层类型过滤获取
layerTypeList.forEach((layerType: number) => {
const indices = pagFile.getEditableIndices(layerType);
indices.forEach((index: number) => {
const imageLayers: Vector<PAGLayer> = pagFile.getLayersByEditableIndex(index, layerType);
for (let j = 0; j < imageLayers.size(); j++) {
const layer: PAGLayer = imageLayers.get(j);
const layerInfo = this.getLayerInfo(layer);
editableLayers.push({
layer,
...layerInfo,
});
}
});
});
return editableLayers;
}
/**
* 通过图层名字获取图层,并返回匹配集合
* @param {string} layerName 图层名称
* @returns {TipPAGWebLayerInfo[]}
* @memberof PAGWeb
*/
getLayersByName(layerName: string): TipPAGWebLayerInfo[] {
const pagComposition = this.getPAGComposition();
if (!pagComposition) return [];
const pagLayerList = pagComposition.getLayersByName(layerName);
const layerList = pagLayerList.map((layer: PAGLayer) => {
console.log(`test getLayersByName: layerName: ${layer.layerName()}`);
const layerInfo = this.getLayerInfo(layer);
return {
layer,
...layerInfo,
};
});
return layerList;
}
/**
* 获取事件响应时坐标位置所在的图层集合
* @param {Event} { event } 事件对象
* @returns {TipPAGWebLayerInfo[]}
* @memberof PAGWeb
*/
getLayersUnderPoint({ event }: { event: MouseEvent }): TipPAGWebLayerInfo[] {
const pagComposition = this.getPAGComposition();
if (!pagComposition) return [];
const scaleInfo = this.getScaleInfo();
if (!scaleInfo) return [];
const { dpr, scaleX, scaleY, tx, ty } = scaleInfo;
const localX = (event.offsetX * dpr) / scaleX + (tx * -1) / scaleX;
const localY = (event.offsetY * dpr) / scaleY + (ty * -1) / scaleY;
const pagLayers = pagComposition.getLayersUnderPoint(localX, localY);
// console.log('[info]点击位置', event.offsetX, event.offsetY);
console.log('[info]计算后的画布点击位置', localX, localY);
// console.log('[info]scaleInfo', scaleInfo);
// 添加常用的图层信息
const paglayerList = pagLayers.map((layer: PAGLayer) => {
const layerInfo = this.getLayerInfo(layer);
return {
layer,
...layerInfo,
};
});
// 打印日志
paglayerList.forEach((layer: PAGLayer) => {
console.log(`
舞台的点击响应位置${localX}, ${localY}
点击中了图层: ${layer.layerName}
图层的唯一ID: ${layer.uniqueID}
图层的可编辑ID(同类型公用一个ID): ${layer.editableIndex}
`, layer);
});
return paglayerList;
}
/**
* 替换图像
* @param {number} layerIndex 图层的可编辑下标
* @param {File} file 待替换的图像文件对象
* @memberof PAGWeb
*/
async replaceImage(layerIndex: number, file: File) {
const pagView = this.getPAGView();
const pagComposition = this.getPAGComposition();
if (!pagView || !pagComposition) return;
const pagImage = await this.pag?.PAGImage?.fromFile(file);
if (!pagImage) return;
pagComposition.replaceImage(layerIndex, pagImage);
await pagView.flush();
pagImage.destroy();
}
/**
* 替换文本
* @param {number} editableTextIndex 图层的可编辑下标
* @param {object} texture 文本属性对象,支持属性:https://pag.art/apis/web/classes/types.TextDocument.html
* @memberof PAGWeb
*/
async replaceText(editableTextIndex: number, texture: Record<string, any>) {
const pagFile = this.getPAGFile();
const pagView = this.getPAGView();
if (!pagView || !pagFile) return;
const textDocument = pagFile.getTextData(editableTextIndex);
console.log('[info]pag-web-api: 当前文本', textDocument);
// 设置文本属性:https://pag.art/apis/web/classes/types.TextDocument.html
Object.keys(texture).forEach((key) => {
const value = texture[key];
textDocument[key] = value;
});
// 更新文本
pagFile.replaceText(editableTextIndex, textDocument);
await pagView.flush();
}
/**
* 对当前舞台进行截图
* @returns {dataUrl|undefined} base64格式的图像数据
* @memberof PAGWeb
*/
async makeSnapshot() {
const pagView = this.getPAGView();
if (!pagView) return;
const bitmap = await pagView.makeSnapshot();
if (bitmap) {
const snapshotCanvas = document.createElement('canvas');
snapshotCanvas.width = bitmap.width;
snapshotCanvas.height = bitmap.height;
const ctx = snapshotCanvas.getContext('2d');
if (ctx) {
ctx.drawImage(bitmap, 0, 0);
const dataUrl = snapshotCanvas.toDataURL();
return dataUrl;
}
return;
}
return;
}
}
export {
PAGWeb,
};