This commit is contained in:
2026-09-24 16:25:22 +08:00
commit 7428184f01
1198 changed files with 314515 additions and 0 deletions
+839
View File
@@ -0,0 +1,839 @@
import { ShapeType, ICanvasEvent, ICanvasDomEvent, IEventListener, CanvasEventType, ICanvasOptional, IcanvasDomEventDetail } from './interface.uts';
// import { Shape } from './lib/shape.uts';
import { Shape } from '@/uni_modules/tmx-ui/core/canvas/lib/shape.uts';
export class ICanvas {
canvas : CanvasContext | null = null;
ctx : CanvasRenderingContext2D | null = null;
canvasNode : NodeInfo | null = null
width : number = 0;
height : number = 0;
boxWidth : number = 0;
boxHeight : number = 0;
dpr : number = 1;
/**
* 命中检测事件范围,默认只对clik点击进行命中测试,如果全放开会有性能风险
* 相当于是事件过滤。比如['click']那么元素只会触发click事件,其它事件不会响应
* 空数组响应所有事件
*/
checkPointInHitEvents = ['click'] as CanvasEventType[]
//点击时的位置
private touch_x = 0
private touch_y = 0
//移动时的位置
private touch_move_x = 0
private touch_move_y = 0
//结束时的位置
private touch_end_x = 0
private touch_end_y = 0
//结束时相对起始时之间的位置距离
private touch_end_len_x = 0
private touch_end_len_y = 0
//移动时相对上一次移动的距离
private touch_move_len_x = 0
private touch_move_len_y = 0
//点击时的时间
private touchStartTime = 0
//点击抬起时的结束时间.
private touchEndTime = 0
private touchTimeTid = 0
id = Math.random().toString(16).substring(4)
gaptimeId = 23
private shapes : ShapeType[] = [];
private eventListeners : Map<CanvasEventType, IEventListener[]> = new Map();
component : any | null = null;
canvasId : string;
constructor(config : ICanvasOptional) {
this.canvasId = config.canvasId;
this.component = config.component
}
init() : Promise<any | null> {
const _this = this;
return new Promise((res) => {
uni.createSelectorQuery()
.in(_this.component)
.select("#" + _this.canvasId)
.boundingClientRect()
.exec(result => {
if (result.length == 0) return;
let node = result[0]! as NodeInfo
uni.createCanvasContextAsync({
id: _this.canvasId,
component: _this.component as ComponentPublicInstance | null,
success: (context : CanvasContext) => {
_this.canvasNode = node
_this.canvas = context;
const dpr = uni.getWindowInfo().pixelRatio ?? 1;
const canvasContext = context.getContext('2d')!;
// #ifdef APP
canvasContext.resetTransform()
// #endif
const canvas = canvasContext.canvas;
canvas.width = canvas.offsetWidth * dpr;
canvas.height = canvas.offsetHeight * dpr;
canvasContext.scale(dpr, dpr);
_this.ctx = canvasContext as CanvasRenderingContext2D;
_this.dpr = dpr;
_this.ctx!.globalAlpha = 1;
_this.width = node.width!;
_this.height = node.height!;
_this.boxWidth = canvas.offsetWidth;
_this.boxHeight = canvas.offsetHeight;
res(_this)
}
})
})
})
}
private _resizePos() {
const _this = this;
uni.createSelectorQuery()
.in(_this.component)
.select("#" + _this.canvasId)
.boundingClientRect()
.exec(result => {
if (result.length == 0) return;
let node = result[0]! as NodeInfo
_this.canvasNode = node
})
}
clear() : ICanvas {
this.ctx!.clearRect(0, 0, this.width, this.height)
return this;
}
addShape(shape : ShapeType|ShapeType[]) : ICanvas {
if(Array.isArray(shape)){
let _this = this;
shape.forEach((el)=>{
_this.shapes.push(el);
})
}else{
this.shapes.push(shape);
}
this.shapes.sort((a, b ) : number => {
let ela = a as Shape
let elb = b as Shape
return ela.zIndex - elb.zIndex
})
return this;
}
removeShape(shape : ShapeType) : ICanvas {
for (let i = 0; i < this.shapes.length; i++) {
let itemshape = this.shapes[i]
if (itemshape instanceof Shape && shape instanceof Shape) {
if (itemshape.id == shape.id) {
this.shapes.splice(i, 1);
break;
}
}
}
return this;
}
render() : ICanvas {
this.clear();
for (let i = 0; i < this.shapes.length; i++) {
const shape = this.shapes[i];
if (shape instanceof Shape) {
shape.draw(this.ctx!);
}
}
return this;
}
update() : ICanvas {
let needsRender = false;
for (let i = 0; i < this.shapes.length; i++) {
const shape = this.shapes[i];
if (shape instanceof Shape) {
if (shape.needsUpdate) {
needsRender = true;
shape.update()
}
}
}
if (needsRender) {
this.render();
}
return this;
}
getImage():Promise<string>{
return new Promise((res,_rej)=>{
// #ifdef WEB||H5
try {
this.ctx.canvas.toBlob((blob)=>{
res(URL.createObjectURL(blob))
},'images/png',1)
} catch (error) {
console.error(error,'注意画布中的图片要同域')
res('')
}
// #endif
// #ifdef APP
let ele = uni.getElementById(this.canvasId) as UniElement
ele!.takeSnapshot({
success(result){
res(result.tempFilePath)
},
fail(){
res('')
}
})
// #endif
// #ifdef MP
uni.canvasToTempFilePath({
canvasId:this.canvasId,
success(result){
res(result.tempFilePath)
},
fail(){
res('')
}
},this.component)
// #endif
})
}
/** 将外部事件绑定到对象上,实现事件分发. **/
bindEvent(eventType : any | null) {
let _this = this;
clearTimeout(this.touchTimeTid)
if (eventType == null || this.canvasNode == null) return;
const winTop = uni.getWindowInfo().windowTop
const offsetTop = this.canvasNode!.top! + 0
const offsetLeft = this.canvasNode!.left!
//防止触摸和鼠标事件同时触发。
let isTouch = false;
// 移动端
// #ifdef APP-ANDROID||H5
if (eventType instanceof TouchEvent) {
// #endif
// #ifdef APP-IOS||APP-HARMONY
if (eventType instanceof UniTouchEvent) {
// #endif
// #ifdef MP
if (true) {
// #endif
eventType.stopPropagation()
eventType.preventDefault()
isTouch = true;
let changedTouches = eventType.changedTouches
// #ifndef APP
changedTouches = Array.from(changedTouches)
// #endif
const clientX = changedTouches[0].clientX - offsetLeft
const clientY = changedTouches[0].clientY - offsetTop
if (eventType.type == 'touchstart') {
this.touch_x = clientX
this.touch_y = clientY
this.touch_move_x = clientX
this.touch_move_y = clientY
this.touch_end_x = clientX
this.touch_end_y = clientY
this.touch_end_len_x = 0
this.touch_end_len_y = 0
this.touch_move_len_x = 0
this.touch_move_len_y = 0
this.touchStartTime = Date.now()
this.buildEvents('down', {
type: 'down',
x: changedTouches[0].clientX - offsetLeft,
y: changedTouches[0].clientY - offsetTop,
detail: [{
startX: this.touch_x,
startY: this.touch_y,
endX: this.touch_end_x,
endY: this.touch_end_y,
moveX: this.touch_move_x,
moveY: this.touch_move_y,
moveLenX: this.touch_move_len_x,
moveLenY: this.touch_move_len_y,
endLenX: this.touch_end_len_x,
endLenY: this.touch_end_len_y
}],
touches: changedTouches.map((el) : ICanvasDomEvent => {
return {
type: 'down',
x: el.clientX - offsetLeft,
y: el.clientY - offsetTop,
touches: [],
detail: [{
startX: this.touch_x,
startY: this.touch_y,
endX: this.touch_end_x,
endY: this.touch_end_y,
moveX: this.touch_move_x,
moveY: this.touch_move_y,
moveLenX: this.touch_move_len_x,
moveLenY: this.touch_move_len_y,
endLenX: this.touch_end_len_x,
endLenY: this.touch_end_len_y
}],
} as ICanvasDomEvent
})
} as ICanvasDomEvent)
this.touchTimeTid = setTimeout(() => {
_this.buildEvents('longpress', {
type: 'longpress',
x: changedTouches[0].clientX - offsetLeft,
y: changedTouches[0].clientY - offsetTop,
detail: [{
startX: this.touch_x,
startY: this.touch_y,
endX: this.touch_end_x,
endY: this.touch_end_y,
moveX: this.touch_move_x,
moveY: this.touch_move_y,
moveLenX: this.touch_move_len_x,
moveLenY: this.touch_move_len_y,
endLenX: this.touch_end_len_x,
endLenY: this.touch_end_len_y
}],
touches: changedTouches.map((el) : ICanvasDomEvent => {
return {
type: 'longpress',
x: el.clientX - offsetLeft,
y: el.clientY - offsetTop,
touches: [],
detail: [{
startX: this.touch_x,
startY: this.touch_y,
endX: this.touch_end_x,
endY: this.touch_end_y,
moveX: this.touch_move_x,
moveY: this.touch_move_y,
moveLenX: this.touch_move_len_x,
moveLenY: this.touch_move_len_y,
endLenX: this.touch_end_len_x,
endLenY: this.touch_end_len_y
}],
} as ICanvasDomEvent
})
} as ICanvasDomEvent)
}, 500)
} else if (eventType.type == 'touchmove') {
this.touch_move_len_x = this.touch_move_x - clientX
this.touch_move_len_y = this.touch_move_y - clientY
this.touch_end_len_x = clientX - this.touch_x
this.touch_end_len_y = clientY - this.touch_y
this.touch_move_x = clientX
this.touch_move_y = clientY
this.buildEvents('move', {
type: 'move',
x: changedTouches[0].clientX - offsetLeft,
y: changedTouches[0].clientY - offsetTop,
detail: [{
startX: this.touch_x,
startY: this.touch_y,
endX: this.touch_end_x,
endY: this.touch_end_y,
moveX: this.touch_move_x,
moveY: this.touch_move_y,
moveLenX: this.touch_move_len_x,
moveLenY: this.touch_move_len_y,
endLenX: this.touch_end_len_x,
endLenY: this.touch_end_len_y
}],
touches: changedTouches.map((el) : ICanvasDomEvent => {
return {
type: 'move',
x: el.clientX - offsetLeft,
y: el.clientY - offsetTop,
touches: [],
detail: [{
startX: this.touch_x,
startY: this.touch_y,
endX: this.touch_end_x,
endY: this.touch_end_y,
moveX: this.touch_move_x,
moveY: this.touch_move_y,
moveLenX: this.touch_move_len_x,
moveLenY: this.touch_move_len_y,
endLenX: this.touch_end_len_x,
endLenY: this.touch_end_len_y
}],
} as ICanvasDomEvent
})
} as ICanvasDomEvent)
} else if (eventType.type == 'touchend') {
this.touch_end_x = clientX
this.touch_end_y = clientY
this.touch_end_len_x = this.touch_end_x - this.touch_x
this.touch_end_len_y = this.touch_end_y - this.touch_y
let nowTIme = Date.now()
this.buildEvents('up', {
type: 'up',
x: changedTouches[0].clientX - offsetLeft,
y: changedTouches[0].clientY - offsetTop,
detail: [{
startX: this.touch_x,
startY: this.touch_y,
endX: this.touch_end_x,
endY: this.touch_end_y,
moveX: this.touch_move_x,
moveY: this.touch_move_y,
moveLenX: this.touch_move_len_x,
moveLenY: this.touch_move_len_y,
endLenX: this.touch_end_len_x,
endLenY: this.touch_end_len_y
}],
touches: changedTouches.map((el) : ICanvasDomEvent => {
return {
type: 'up',
x: el.clientX - offsetLeft,
y: el.clientY - offsetTop,
touches: [],
detail: [{
startX: this.touch_x,
startY: this.touch_y,
endX: this.touch_end_x,
endY: this.touch_end_y,
moveX: this.touch_move_x,
moveY: this.touch_move_y,
moveLenX: this.touch_move_len_x,
moveLenY: this.touch_move_len_y,
endLenX: this.touch_end_len_x,
endLenY: this.touch_end_len_y
}],
} as ICanvasDomEvent
})
} as ICanvasDomEvent)
// 判断是不是单击
const difftime = nowTIme - this.touchStartTime
if (difftime > 50 && difftime < 250) {
this.buildEvents('click', {
type: 'click',
x: changedTouches[0].clientX - offsetLeft,
y: changedTouches[0].clientY - offsetTop,
detail: [{
startX: this.touch_x,
startY: this.touch_y,
endX: this.touch_end_x,
endY: this.touch_end_y,
moveX: this.touch_move_x,
moveY: this.touch_move_y,
moveLenX: this.touch_move_len_x,
moveLenY: this.touch_move_len_y,
endLenX: this.touch_end_len_x,
endLenY: this.touch_end_len_y
}],
touches: changedTouches.map((el) : ICanvasDomEvent => {
return {
type: 'click',
x: el.clientX - offsetLeft,
y: el.clientY - offsetTop,
touches: [],
detail: [{
startX: this.touch_x,
startY: this.touch_y,
endX: this.touch_end_x,
endY: this.touch_end_y,
moveX: this.touch_move_x,
moveY: this.touch_move_y,
moveLenX: this.touch_move_len_x,
moveLenY: this.touch_move_len_y,
endLenX: this.touch_end_len_x,
endLenY: this.touch_end_len_y
}],
} as ICanvasDomEvent
})
} as ICanvasDomEvent)
}
// 判断是不是双击
const diffEndtime = nowTIme - this.touchEndTime
if (diffEndtime > 100 && diffEndtime < 280) {
this.buildEvents('dbclick', {
type: 'dbclick',
x: changedTouches[0].clientX - offsetLeft,
y: changedTouches[0].clientY - offsetTop,
detail: [{
startX: this.touch_x,
startY: this.touch_y,
endX: this.touch_end_x,
endY: this.touch_end_y,
moveX: this.touch_move_x,
moveY: this.touch_move_y,
moveLenX: this.touch_move_len_x,
moveLenY: this.touch_move_len_y,
endLenX: this.touch_end_len_x,
endLenY: this.touch_end_len_y
}],
touches: [] as ICanvasDomEvent[]
})
}
this.touchEndTime = nowTIme
} else if (eventType.type == 'touchcancel') {
this.touch_end_x = clientX
this.touch_end_y = clientY
this.touch_end_len_x = this.touch_end_x - this.touch_x
this.touch_end_len_y = this.touch_end_y - this.touch_y
const touchesList = changedTouches.map((el) : ICanvasDomEvent => {
return {
type: 'cancel',
x: el.clientX - offsetLeft,
y: el.clientY - offsetTop,
touches: [],
detail: [{
startX: this.touch_x,
startY: this.touch_y,
endX: this.touch_end_x,
endY: this.touch_end_y,
moveX: this.touch_move_x,
moveY: this.touch_move_y,
moveLenX: this.touch_move_len_x,
moveLenY: this.touch_move_len_y,
endLenX: this.touch_end_len_x,
endLenY: this.touch_end_len_y
} as IcanvasDomEventDetail ] as IcanvasDomEventDetail[],
} as ICanvasDomEvent
})
this.buildEvents('cancel', {
type: 'cancel',
x: changedTouches[0].clientX - offsetLeft,
y: changedTouches[0].clientY - offsetTop,
detail: [{
startX: this.touch_x,
startY: this.touch_y,
endX: this.touch_end_x,
endY: this.touch_end_y,
moveX: this.touch_move_x,
moveY: this.touch_move_y,
moveLenX: this.touch_move_len_x,
moveLenY: this.touch_move_len_y,
endLenX: this.touch_end_len_x,
endLenY: this.touch_end_len_y
} as IcanvasDomEventDetail ] as IcanvasDomEventDetail[],
touches: touchesList as ICanvasDomEvent[]
} as ICanvasDomEvent)
}
}
// 兼容PC
// #ifdef WEB
if (eventType instanceof MouseEvent && !(eventType instanceof PointerEvent) && !isTouch) {
eventType.stopPropagation()
const clientX = eventType.layerX
const clientY = eventType.layerY
if (eventType.type == 'mousedown') {
this.touch_x = clientX
this.touch_y = clientY
this.touch_move_x = clientX
this.touch_move_y = clientY
this.touch_end_x = clientX
this.touch_end_y = clientY
this.touch_end_len_x = 0
this.touch_end_len_y = 0
this.touch_move_len_x = 0
this.touch_move_len_y = 0
this.touchStartTime = Date.now()
window.ICnavansId = this.id
this.buildEvents('down', {
type: 'down',
x: clientX,
y: clientY,
detail: [{
startX: this.touch_x,
startY: this.touch_y,
endX: this.touch_end_x,
endY: this.touch_end_y,
moveX: this.touch_move_x,
moveY: this.touch_move_y,
moveLenX: this.touch_move_len_x,
moveLenY: this.touch_move_len_y,
endLenX: this.touch_end_len_x,
endLenY: this.touch_end_len_y
}],
touches: [] as ICanvasDomEvent[]
})
this.touchTimeTid = setTimeout(() => {
_this.buildEvents('longpress', {
type: 'longpress',
x: clientX,
y: clientY,
detail: [{
startX: this.touch_x,
startY: this.touch_y,
endX: this.touch_end_x,
endY: this.touch_end_y,
moveX: this.touch_move_x,
moveY: this.touch_move_y,
moveLenX: this.touch_move_len_x,
moveLenY: this.touch_move_len_y,
endLenX: this.touch_end_len_x,
endLenY: this.touch_end_len_y
}],
touches: [] as ICanvasDomEvent[]
} as ICanvasDomEvent)
}, 500)
}
if (eventType.type == 'mousemove' && window.ICnavansId === this.id) {
this.touch_move_len_x = this.touch_move_x - clientX
this.touch_move_len_y = this.touch_move_y - clientY
this.touch_end_len_x = clientX - this.touch_x
this.touch_end_len_y = clientY - this.touch_y
this.touch_move_x = clientX
this.touch_move_y = clientY
this.buildEvents('move', {
type: 'move',
x: clientX,
y: clientY,
detail: [{
startX: this.touch_x,
startY: this.touch_y,
endX: this.touch_end_x,
endY: this.touch_end_y,
moveX: this.touch_move_x,
moveY: this.touch_move_y,
moveLenX: this.touch_move_len_x,
moveLenY: this.touch_move_len_y,
endLenX: this.touch_end_len_x,
endLenY: this.touch_end_len_y
}],
touches: [] as ICanvasDomEvent[]
})
}
if ((eventType.type == 'mouseup') && window.ICnavansId === this.id) {
this.touch_end_x = clientX
this.touch_end_y = clientY
this.touch_end_len_x = this.touch_end_x - this.touch_x
this.touch_end_len_y = this.touch_end_y - this.touch_y
let nowTIme = Date.now()
window.ICnavansId = ""
this.buildEvents('up', {
type: 'up',
x: clientX,
y: clientY,
detail: [{
startX: this.touch_x,
startY: this.touch_y,
endX: this.touch_end_x,
endY: this.touch_end_y,
moveX: this.touch_move_x,
moveY: this.touch_move_y,
moveLenX: this.touch_move_len_x,
moveLenY: this.touch_move_len_y,
endLenX: this.touch_end_len_x,
endLenY: this.touch_end_len_y
}],
touches: [] as ICanvasDomEvent[]
})
// 判断是不是单击
const difftime = nowTIme - this.touchStartTime
if (difftime > 50 && difftime < 250) {
this.buildEvents('click', {
type: 'click',
x: clientX,
y: clientY,
detail: [{
startX: this.touch_x,
startY: this.touch_y,
endX: this.touch_end_x,
endY: this.touch_end_y,
moveX: this.touch_move_x,
moveY: this.touch_move_y,
moveLenX: this.touch_move_len_x,
moveLenY: this.touch_move_len_y,
endLenX: this.touch_end_len_x,
endLenY: this.touch_end_len_y
}],
touches: [] as ICanvasDomEvent[]
} as ICanvasDomEvent)
}
// 判断是不是双击
const diffEndtime = nowTIme - this.touchEndTime
if (diffEndtime > 100 && diffEndtime < 280) {
this.buildEvents('dbclick', {
type: 'dbclick',
x: clientX,
y: clientY,
detail: [],
touches: [] as ICanvasDomEvent[]
})
}
this.touchEndTime = nowTIme
}
if ((eventType.type == 'mouseleave') && window.ICnavansId === this.id) {
this.touch_end_x = clientX
this.touch_end_y = clientY
this.touch_end_len_x = this.touch_end_x - this.touch_x
this.touch_end_len_y = this.touch_end_y - this.touch_y
window.ICnavansId = ""
this.buildEvents('cancel', {
type: 'cancel',
x: clientX,
y: clientY,
detail: [{
startX: this.touch_x,
startY: this.touch_y,
endX: this.touch_end_x,
endY: this.touch_end_y,
moveX: this.touch_move_x,
moveY: this.touch_move_y,
moveLenX: this.touch_move_len_x,
moveLenY: this.touch_move_len_y,
endLenX: this.touch_end_len_x,
endLenY: this.touch_end_len_y
}],
touches: [] as ICanvasDomEvent[]
})
}
}
// #endif
}
addEventListener(eventName : CanvasEventType, listener : IEventListener) : ICanvas {
if (!this.eventListeners.has(eventName)) {
this.eventListeners.set(eventName, []);
}
this.eventListeners.get(eventName)!.push(listener);
return this;
}
removeEventListener(eventName : CanvasEventType, listener : IEventListener) : ICanvas {
const listeners = this.eventListeners.get(eventName);
if (listeners == null) return this;
const index = listeners.indexOf(listener);
if (index != -1) {
listeners.splice(index, 1);
}
return this;
}
private getTargetShape(eventName : CanvasEventType, x : number, y : number) : ShapeType[] {
let shapes = [] as ShapeType[]
// 只针对开命中事件进行检测
if (!this.checkPointInHitEvents.includes(eventName) && this.checkPointInHitEvents.length > 0) return shapes;
for (let i = this.shapes.length - 1; i >= 0; i--) {
const shape = this.shapes[i];
if (shape instanceof Shape) {
if (shape.isPointInPath(x, y, shape.id)) {
shapes.push(shape)
if (!shape.bubbleEvent) {
return shapes;
}
}
}
}
return shapes;
}
private buildEvents(eventName : CanvasEventType, eventDetail : ICanvasDomEvent) {
this._resizePos()
const listeners = this.eventListeners.get(eventName);
const target = this.getTargetShape(eventName, eventDetail.x, eventDetail.y);
const event : ICanvasEvent = {
type: eventName,
x: eventDetail.x,
y: eventDetail.y,
target: target,
detail: eventDetail.detail,
touches: eventDetail.touches.map((el : ICanvasDomEvent) : ICanvasEvent => {
return {
type: eventName,
x: el.x,
y: el.y,
target: target,
detail: el.detail,
touches: [] as ICanvasEvent[]
} as ICanvasEvent
})
};
if (listeners != null) {
for (let i = 0; i < listeners.length; i++) {
let children = listeners[i]
//执行画布的监听事件
children(event);
}
}
for (let i = 0; i < this.shapes.length; i++) {
const shape = this.shapes[i];
if (shape instanceof Shape) {
if (event.detail.length == 0) continue;
let startX = event.detail[0].startX
let startY = event.detail[0].startY
if (shape.isPointInPath(startX, startY, shape.id) && shape.draggable && eventName == 'down') {
shape.draggableing = true;
}
if (shape.draggable && (eventName == 'up' || eventName == 'cancel') && shape.draggableing) {
shape.draggableing = false;
}
if (shape.draggableing && eventName == 'move') {
shape.drag(eventName, event)
}
}
}
if (target.length > 0) {
let isUpdate = false;
for (let i = 0; i < target.length; i++) {
const shape = target[i];
if (shape instanceof Shape) {
shape.buildEvents(eventName, event)
if(shape.draggable&&!isUpdate){
isUpdate = true;
}
}
}
if(isUpdate){
this.render()
}
}
}
}