Add GLCanvas

This commit is contained in:
MrKBear 2022-02-06 11:50:29 +08:00
parent e0500fd6e8
commit 670e2e4d11
7 changed files with 525 additions and 15 deletions

11
package-lock.json generated
View File

@ -9,6 +9,7 @@
"version": "1.0.0",
"license": "GPL",
"dependencies": {
"@juggle/resize-observer": "^3.3.1",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
@ -143,6 +144,11 @@
"integrity": "sha512-82cpyJyKRoQoRi+14ibCeGPu0CwypgtBAdBhq1WfvagpCZNKqwXbKwXllYSMG91DhmG4jt9gN8eP6lGOtozuaw==",
"dev": true
},
"node_modules/@juggle/resize-observer": {
"version": "3.3.1",
"resolved": "https://registry.npmmirror.com/@juggle/resize-observer/-/resize-observer-3.3.1.tgz",
"integrity": "sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw=="
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -6803,6 +6809,11 @@
"integrity": "sha512-82cpyJyKRoQoRi+14ibCeGPu0CwypgtBAdBhq1WfvagpCZNKqwXbKwXllYSMG91DhmG4jt9gN8eP6lGOtozuaw==",
"dev": true
},
"@juggle/resize-observer": {
"version": "3.3.1",
"resolved": "https://registry.npmmirror.com/@juggle/resize-observer/-/resize-observer-3.3.1.tgz",
"integrity": "sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw=="
},
"@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",

View File

@ -36,6 +36,7 @@
"webpack-dev-server": "^4.7.2"
},
"dependencies": {
"@juggle/resize-observer": "^3.3.1",
"react": "^17.0.2",
"react-dom": "^17.0.2"
}

104
source/Common/Emitter.ts Normal file
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
source/GLRender/GLCanvas.ts Normal file
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);
});
}
}
}

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%;
}
}

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);

View File

@ -5,6 +5,7 @@
"strict": true,
"sourceMap": true,
"jsx": "react-jsx",
"target": "ES5",
"lib": [
"ESNext",
"DOM"
@ -19,6 +20,12 @@
],
"@Model/*": [
"./source/Model/*"
],
"@Common/*": [
"./source/Common/*"
],
"@GLRender/*": [
"./source/GLRender/*"
]
}
},