@turbox3d/graphic-component-pixi
Version:
Graphic component library based on pixi
202 lines (187 loc) • 5.7 kB
text/typescript
import * as PIXI from 'pixi.js';
import { Mesh2D } from '@turbox3d/renderer-pixi';
import { fail, Vec2, cropImage } from '@turbox3d/shared';
import DrawUtils from '../draw-utils';
import { IFitStyle } from '../draw-utils/drawRect';
export interface IImage2dProps {
x?: number;
y?: number;
width: number;
height: number;
rotation?: number;
scale?: Vec2;
/**
* 传入的位置坐标是否是矩形中心点
*/
central?: boolean;
radius?: number;
lineWidth?: number;
lineColor?: number;
lineAlpha?: number;
/**
* 边框内扩 0、外扩 1
*/
alignment?: number;
native?: boolean;
fillColor?: number;
fillAlpha?: number;
alpha?: number;
backgroundImage?: string | HTMLImageElement;
materialDirection?: Vec2;
fit?: IFitStyle;
zIndex?: number;
}
/** UI组件-图片 */
export default class Image2d extends Mesh2D<IImage2dProps> {
protected view: PIXI.Container;
protected reactivePipeLine = [
this.updateGeometry,
this.updateMaterial,
this.updatePosition,
this.updateRotation,
this.updateScale,
];
private g = new PIXI.Graphics();
private s = new PIXI.Sprite();
componentDidMount() {
this.g.zIndex = -1;
this.s.zIndex = -1;
this.view.addChild(this.g);
this.view.addChild(this.s);
}
private async loadTextureResource() {
const { backgroundImage } = this.props;
if (!backgroundImage) {
return;
}
let t: PIXI.Texture | undefined;
if (backgroundImage instanceof HTMLImageElement) {
t = await PIXI.Texture.fromLoader(backgroundImage, backgroundImage.src).catch(() => {
fail('Load Image2d backgroundImage texture failed.');
});
} else {
t = await PIXI.Texture.fromURL(backgroundImage).catch(() => {
fail('Load Image2d backgroundImage texture failed.');
});
}
return t;
}
async updateGeometry() {
this.g.clear();
this.s.visible = false;
const {
width,
height,
radius = 0,
lineWidth = 0,
lineColor = 0x0,
lineAlpha = 1,
alignment = 0,
native,
fillColor = 0x0,
fillAlpha = 0,
alpha = 1,
backgroundImage = '',
fit = 'none',
} = this.props;
let alignParam = alignment;
if (alignment === 0) {
alignParam = 1;
} else if (alignment === 1) {
alignParam = 0;
}
const fillPos = { x: lineWidth * alignParam, y: lineWidth * alignParam };
const rw = width - lineWidth * 2 * alignParam;
const rh = height - lineWidth * 2 * alignParam;
DrawUtils.drawRect(this.g, {
x: 0,
y: 0,
width,
height,
central: false,
radius,
lineWidth,
lineColor,
lineAlpha,
alignment,
native,
fillColor,
fillAlpha,
alpha,
});
const t = await this.loadTextureResource();
if (!t) {
return;
}
this.s.texture = t;
const { width: tw, height: th } = this.s.texture;
const imgRatio = tw / th;
const rectRatio = width / height;
const center = { x: width / 2, y: height / 2 };
if (fit === 'none') {
if (rectRatio > 1) {
this.s.width = rw;
this.s.height = rw / imgRatio;
} else {
this.s.width = rh * imgRatio;
this.s.height = rh;
}
this.s.position.set(center.x - this.s.width / 2, center.y - this.s.height / 2);
} else if (fit === 'fill') {
this.s.width = rw;
this.s.height = rh;
this.s.position.set(fillPos.x, fillPos.y);
} else if (fit === 'cover') {
if (imgRatio <= rectRatio) {
const ratio = rw / tw;
const { element } = await cropImage(backgroundImage, { start: { x: 0, y: th / 2 - rh / ratio / 2 }, end: { x: tw, y: th / 2 + rh / ratio / 2 }});
if (element) {
this.s.texture = PIXI.Texture.from(element);
}
this.s.width = rw;
this.s.height = rh;
} else {
const ratio = rh / th;
const { element } = await cropImage(backgroundImage, { start: { x: tw / 2 - rw / ratio / 2, y: 0 }, end: { x: tw / 2 + rw / ratio / 2, y: th }});
if (element) {
this.s.texture = PIXI.Texture.from(element);
}
this.s.width = rw;
this.s.height = rh;
}
this.s.position.set(fillPos.x, fillPos.y);
} else if (fit === 'contain') {
if (imgRatio <= rectRatio) {
this.s.width = rh * imgRatio;
this.s.height = rh;
this.s.position.set(fillPos.x + rw / 2 - (rh * imgRatio) / 2, fillPos.y);
} else {
this.s.width = rw;
this.s.height = rw / imgRatio;
this.s.position.set(fillPos.x, fillPos.y + rh / 2 - rw / imgRatio / 2);
}
}
// 方便做贴图的镜像翻转
this.s.anchor.set(0.5, 0.5);
// anchor 设置为 0.5 后,此处需要将贴图调整回去
this.s.position.set(this.s.position.x + this.s.width / 2, this.s.position.y + this.s.height / 2);
this.s.visible = true;
}
updateMaterial() {
const { zIndex = 0, materialDirection = { x: 1, y: 1 } } = this.props;
this.view.zIndex = zIndex;
this.s.scale.set(materialDirection.x > 0 ? Math.abs(this.s.scale.x) : -Math.abs(this.s.scale.x), materialDirection.y > 0 ? Math.abs(this.s.scale.y) : -Math.abs(this.s.scale.y));
}
updatePosition() {
const { x = 0, y = 0, central = false, width, height } = this.props;
const [posX, posY] = central ? [x - width / 2, y - height / 2] : [x, y];
this.view.position.x = posX;
this.view.position.y = posY;
}
updateRotation() {
this.view.rotation = this.props.rotation ?? 0;
}
updateScale() {
this.view.scale.set(this.props.scale?.x ?? 1, this.props.scale?.y ?? 1);
}
}