Add GLCanvas

This commit is contained in:
2022-02-06 11:50:29 +08:00
parent e0500fd6e8
commit 670e2e4d11
7 changed files with 525 additions and 15 deletions
+104
View File
@@ -0,0 +1,104 @@
export type EventType = string | symbol;
// An event handler can take an optional event argument
// and should not return a value
export type Handler<T = any> = (event: T) => void;
// An array of all currently registered event handlers for a type
export type EventHandlerList<T = any> = Array<Handler<T>>;
// A map of event types and their corresponding event handlers.
export type EventHandlerMap<Events extends Record<EventType, any>> = Map<
keyof Events,
EventHandlerList<Events[keyof Events]>
>;
// Emitter function type
type IEmitParamType<E extends Record<EventType, any>, K extends keyof E> =
E[K] extends ( undefined | void ) ? [type: K] : [type: K, evt: E[K]];
// Mixin to event object
export type EventMixin<A extends Record<EventType, any>, B extends Record<EventType, any>> = {
[P in (keyof A | keyof B)] :
P extends (keyof A & keyof B) ?
A[P] :
P extends keyof A ?
A[P] :
P extends keyof B ? B[P] :
never;
}
export class Emitter<Events extends Record<EventType, any>> {
/**
* A Map of event names to registered handler functions.
*/
public all: EventHandlerMap<Events>;
public constructor() {
this.all = new Map();
}
public resetAll() {
this.all = new Map();
}
public reset<Key extends keyof Events>(type: Key) {
this.all!.set(type, [] as EventHandlerList<Events[keyof Events]>);
}
/**
* Register an event handler for the given type.
* @param {string|symbol} type Type of event to listen for
* @param {Function} handler Function to call in response to given event
* @memberOf mitt
*/
public on<Key extends keyof Events>(type: Key, handler: Handler<Events[Key]>) {
const handlers: Array<Handler<Events[Key]>> | undefined = this.all!.get(type);
if (handlers) {
handlers.push(handler);
}
else {
this.all!.set(type, [handler] as EventHandlerList<Events[keyof Events]>);
}
}
/**
* Remove an event handler for the given type.
* If `handler` is omitted, all handlers of the given type are removed.
* @param {string|symbol} type Type of event to unregister `handler` from
* @param {Function} [handler] Handler function to remove
* @memberOf mitt
*/
public off<Key extends keyof Events>(type: Key, handler?: Handler<Events[Key]>) {
const handlers: Array<Handler<Events[Key]>> | undefined = this.all!.get(type);
if (handlers) {
if (handler) {
handlers.splice(handlers.indexOf(handler) >>> 0, 1);
}
else {
this.all!.set(type, []);
}
}
}
/**
* Invoke all handlers for the given type.
*
* @param {string|symbol} type The event type to invoke
* @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler
* @memberOf mitt
*/
public emit<Key extends keyof Events>(...param: IEmitParamType<Events, Key>): this {
const [ type, evt ] = param;
let handlers = this.all!.get(type);
if (handlers) {
(handlers as EventHandlerList<Events[keyof Events]>)
.slice()
.map((handler) => {
handler(evt!);
});
}
return this;
}
}
+365
View File
@@ -0,0 +1,365 @@
import { Emitter } from "../Common/Emitter";
import { ResizeObserver as Polyfill } from '@juggle/resize-observer';
export { GLCanvas, GLCanvasOption};
/**
* GLCanvas 的设置
*/
interface GLCanvasOption {
/**
* 分辨率自适应
*/
autoResize?: boolean;
/**
* 是否监听鼠标事件
*/
mouseEvent?: boolean;
/**
* 调试时使用
* 打印事件
*/
eventLog?: boolean
}
type GLCanvasEvent = {
mouseup: GLCanvas,
mousemove: GLCanvas,
mousedown: GLCanvas,
resize: GLCanvas,
};
/**
* 封装 GLCanvas
* 管理封装画布的功能属性
* 监听画布事件
*
* @event resize 画布缓冲区大小改变
* @event mousemove 鼠标移动
* @event mouseup 鼠标抬起
* @event mousedown 鼠标按下
* @event resize 大小变化
*/
class GLCanvas extends Emitter<GLCanvasEvent> {
/**
* HTML节点
*/
private readonly canvas:HTMLCanvasElement;
private readonly div:HTMLDivElement;
/**
* 获取节点
*/
public get dom(){
return this.div;
}
public get can(){
return this.canvas;
}
/**
* 像素分辨率
*/
public pixelRatio:number = devicePixelRatio ?? 1;
/**
* 帧缓冲区宽度
*/
public get width():number {
return this.canvas.width
}
/**
* 帧缓冲区高度
*/
public get height():number {
return this.canvas.height
}
/**
* 画布宽度
*/
public get offsetWidth():number {
return this.canvas.offsetWidth
}
/**
* 画布高度
*/
public get offsetHeight():number {
return this.canvas.offsetHeight
}
/**
* 缩放 X
*/
public get scaleX():number {
return this.canvas.width / this.canvas.offsetWidth
}
/**
* 缩放 Y
*/
public get scaleY():number {
return this.canvas.height / this.canvas.offsetHeight
}
/**
* 分辨率 (画布宽高比)
*/
public get ratio():number {
return this.canvas.offsetWidth / this.canvas.offsetHeight;
}
/**
* 缓存判断是否要设置 canvas 大小
*/
private readonly offsetFlg:[number,number] = [NaN, NaN];
/**
* 画布大小适应到 css 大小
*/
public resize(){
if (
this.offsetWidth !== this.offsetFlg[0] ||
this.offsetHeight !== this.offsetFlg[1]
) {
// 缓存记录
this.offsetFlg[0] = this.offsetWidth;
this.offsetFlg[1] = this.offsetHeight;
// 重置缓冲区
this.canvas.width = this.offsetWidth * this.pixelRatio;
this.canvas.height = this.offsetHeight * this.pixelRatio;
this.emit("resize", this);
}
}
/**
* 鼠标 X 坐标
*/
public mouseX:number = 0;
/**
* 鼠标 Y 坐标
*/
public mouseY:number = 0;
/**
* 鼠标相对 X 坐标
*/
public mouseUvX:number = 0;
/**
* 鼠标相对 Y 坐标
*/
public mouseUvY:number = 0;
/**
* 鼠标 GLX 坐标
*/
public mouseGlX:number = 0;
/**
* 鼠标 GLY 坐标
*/
public mouseGlY:number = 0;
/**
* 鼠标 X 变化量
*/
public mouseMotionX:number = 0;
/**
* 鼠标 Y 变化量
*/
public mouseMotionY:number = 0;
/**
* 缓存鼠标位置
*/
private readonly mouseFlg:[number, number] = [NaN, NaN];
/**
* 保存鼠标数据
*/
private calcMouseData(offsetX:number, offsetY:number):boolean {
if (
offsetX !== this.mouseFlg[0] ||
offsetY !== this.mouseFlg[1]
){
this.mouseX = offsetX;
this.mouseY = offsetY;
this.mouseUvX = offsetX / this.offsetWidth;
this.mouseUvY = offsetY / this.offsetHeight;
this.mouseGlX = this.mouseUvX * 2 - 1;
this.mouseGlY = - this.mouseUvY * 2 + 1;
this.mouseMotionX = offsetX - this.mouseFlg[0];
this.mouseMotionY = offsetY - this.mouseFlg[1];
this.mouseFlg[0] = offsetX;
this.mouseFlg[1] = offsetY;
return true;
}
return false;
}
private calcMouseDataFromTouchEvent(e:TouchEvent){
if (e.touches.length <= 0) return;
let offsetX = e.touches[0].clientX - this.canvas.offsetLeft;
let offsetY = e.touches[0].clientY - this.canvas.offsetTop;
return this.calcMouseData(offsetX, offsetY);
}
/**
* 鼠标触摸触发计数
*/
private touchCount:number = 0;
/**
* 鼠标是否按下
*/
public mouseDown:boolean = false;
/**
* 检测 canvas 变化
*/
private readonly obs?: ResizeObserver;
/**
* 使用 canvas 节点创建
* 不适用节点则自动创建
* @param ele 使用的 canvas节点
* @param o 设置
*/
public constructor(ele?:HTMLCanvasElement, o?:GLCanvasOption){
super();
let opt = o ?? {};
let autoResize = opt.autoResize ?? true;
let mouseEvent = opt.mouseEvent ?? true;
let eventLog = opt.eventLog ?? false;
// 获取/创建节点
this.canvas = ele ?? document.createElement("canvas");
this.div = document.createElement("div");
this.div.appendChild(this.canvas);
this.canvas.style.width = "100%";
this.canvas.style.height = "100%";
if (autoResize){
// 创建观察者
this.obs = new (window.ResizeObserver || Polyfill)
((entries:ResizeObserverEntry[])=>{
for (let entry of entries) {
if(entry.target === this.canvas) this.resize();
}
});
// 大小监听
this.obs.observe(this.canvas);
}
if (mouseEvent) {
this.canvas.addEventListener("mouseup",(e)=>{
if(this.touchCount >= 2) {
this.touchCount = 0;
return;
}
if (eventLog) console.log("GLCanvas: mouseup");
this.mouseDown = false;
this.calcMouseData(e.offsetX, e.offsetY);
this.emit("mouseup", this);
});
this.canvas.addEventListener("touchstart",(e)=>{
this.touchCount = 1;
if (eventLog) console.log("GLCanvas: touchstart");
this.mouseDown = true;
this.calcMouseDataFromTouchEvent(e);
this.emit("mousedown", this);
});
this.canvas.addEventListener("mousedown",(e)=>{
if(this.touchCount >= 2) return;
if (eventLog) console.log("GLCanvas: mousedown");
this.mouseDown = true;
this.calcMouseData(e.offsetX, e.offsetY);
this.emit("mousedown", this);
});
this.canvas.addEventListener("touchend",(e)=>{
this.touchCount ++;
if (eventLog) console.log("GLCanvas: touchend");
this.mouseDown = false;
this.calcMouseDataFromTouchEvent(e);
this.emit("mouseup", this);
});
this.canvas.addEventListener("mousemove",(e)=>{
if(this.touchCount >= 2) return;
if (eventLog) console.log("GLCanvas: mousemove");
if (this.calcMouseData(e.offsetX, e.offsetY)) this.emit("mousemove", this);
});
this.canvas.addEventListener("touchmove",(e)=>{
if (eventLog) console.log("GLCanvas: touchmove");
if (this.calcMouseDataFromTouchEvent(e)) this.emit("mousemove", this);
});
}
}
}
+8 -4
View File
@@ -1,6 +1,10 @@
.main {
div.a {
font-size: 2.5em;
background-color: rgb(175, 204, 166);
div.main-canvas {
position: fixed;
width: 100%;
height: 100%;
div.canvas {
width: 100%;
height: 100%;
}
}
+28 -10
View File
@@ -1,17 +1,35 @@
import { Component, ReactNode, StrictMode } from "react";
import { Component, ReactNode, createRef } from "react";
import { GLCanvas } from "@GLRender/GLCanvas";
import { Group } from "@Model/Group";
import { Entry } from "../Entry/Entry";
import "./Laboratory.scss";
class Test extends Component {
render(): ReactNode {
return <div className="main">
<div className="a">it work</div>
<div>ok</div>
</div>
class Laboratory extends Component {
private canvasContRef = createRef<HTMLDivElement>();
public render(): ReactNode {
return <div ref={this.canvasContRef} className="main-canvas"></div>
}
public componentDidMount() {
if (!this.canvasContRef.current) {
throw new Error("Laboratory: 无法获取到 Canvas 容器节点");
}
if (this.canvasContRef.current.getElementsByTagName("canvas").length > 0) {
throw new Error("Laboratory: 重复引用 canvas 节点");
}
const glCanvas = new GLCanvas(undefined, {
autoResize: true,
mouseEvent: true,
eventLog: false
});
glCanvas.dom.className = "canvas";
this.canvasContRef.current.appendChild(glCanvas.dom);
}
}
Entry.renderComponent(Test);
console.log(new Group);
Entry.renderComponent(Laboratory);