123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481 |
- /*
- * *********************************************************************************************************************
- *
- * !!
- * .F88X
- * X8888Y
- * .}888888N;
- * i888888N; .:! .I$WI:
- * R888888I .'N88~ i8}+8Y&8"l8i$8>8W~'>W8}8]KW+8IIN"8&
- * .R888888I .;N8888~ .X8' "8I.!,/8" !%NY8`"8I8~~8>,88I
- * +888888N; .8888888Y "&&8Y.}8,
- * ./888888N; .R888888Y .'}~ .>}'.`+> i}! "i' +/' .'i~ !11,.:">, .~]! .i}i
- * ~888888%: .I888888l .]88~`1/iY88Ii+1'.R$8$8]"888888888> Y8$ W8E X8E W8888'188Il}Y88$*
- * 18888888 E8888881 .]W%8$`R8X'&8%++N8i,8N%N8+l8%` .}8N:.R$RE%N88N%N$K$R 188,FE$8%~Y88I
- * .E888888I .i8888888' .:$8I;88+`E8R:/8N,.>881.`$8E/1/]N8X.Y8N`"KF&&FK!'88*."88K./$88%RN888+~
- * 8888888I .,N888888~ ~88i"8W,!N8*.I88.}888%F,i$88"F88" 888:E8X.>88!i88>`888*.}Fl1]*}1YKi'
- * i888888N' I888Y ]88;/EX*IFKFK88X K8R .l8W 88Y ~88}'88E&%8W.X8N``]88!.$8K .:W8I
- * .i888888N; I8Y .&8$ .X88! i881.:%888>I88 ;88] +88+.';;;;:.Y88X 18N.,88l .+88/
- * .:R888888I
- * .&888888I Copyright (c) 2009-2020. 博锐尚格科技股份有限公司
- * ~8888'
- * .!88~ All rights reserved.
- *
- * *********************************************************************************************************************
- */
- import {
- SMouseButton,
- SMouseEvent,
- SNetUtil,
- SObject,
- STouchState
- } from "@persagy-web/base";
- import { SSvgPaintEngine } from "./engines/SSvgPaintEngine";
- import { SPoint } from "./types/SPoint";
- import { SPainter } from "./SPainter";
- /**
- * Canvas 视图基类
- *
- * @author 庞利祥 <sybotan@126.com>
- */
- export class SCanvasView extends SObject {
- /** 需要绘制 */
- private _needDraw = true;
- /** canvas 视图 */
- protected readonly canvasView: HTMLCanvasElement;
- get canvas(): CanvasRenderingContext2D | null {
- return this.canvasView.getContext("2d") as CanvasRenderingContext2D;
- } // Get canvas
- /** 宽度 */
- get width(): number {
- return this.canvasView.width;
- } // Get width()
- /** 高度 */
- get height(): number {
- return this.canvasView.height;
- } // Get height()
- /** 原点坐标 */
- private _origin = new SPoint();
- get origin(): SPoint {
- return this._origin;
- } // Get origin
- set origin(v: SPoint) {
- if (!this.moveable) {
- return;
- }
- this._origin.x = v.x;
- this._origin.y = v.y;
- this._needDraw = true;
- } // Set origin
- /** 可否进行平移操作 */
- moveable = true;
- /** 缩放比例 */
- private _scale: number = 1;
- get scale(): number {
- return this._scale;
- } // Get scale
- set scale(v: number) {
- if (!this.scalable) {
- return;
- }
- this._scale = v;
- this._needDraw = true;
- } // Set scale
- /** 可否执行绽放操作 */
- scalable = true;
- /** 鼠标滚轮缩放速度 */
- wheelZoom = 1.05;
- /** 最小缩放比例 */
- minScale = 0.000001;
- /** 最大缩放比例 */
- maxScale = 1000000;
- /** 最后一次更新时间 */
- private _lastFrameTime = 0;
- /** 鼠标中键按下时位置 */
- private _midKeyPos = new SPoint();
- /** 手执状态 */
- private _touchState = STouchState.None;
- /** 手指拖动点 */
- private _touchPoint = new SPoint();
- /** 未缩放时二指间距离 */
- private _beforeLength = 0;
- /** 缩放后二指间距离 */
- private _afterLength = 0;
- /** 鼠标样式 */
- set cursor(v: string) {
- this.canvasView.style.cursor = v;
- } // Set cursor
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- //
- /**
- * 构造函数
- *
- * @param id 画布对象 ID
- */
- constructor(id: string) {
- super();
- this.canvasView = document.getElementById(id) as HTMLCanvasElement;
- this.bindEvent(this.canvasView);
- this.requestAnimationFrame(this.loop.bind(this));
- } // Constructor
- /**
- * 更新视图
- */
- update(): void {
- this._needDraw = true;
- } // Function update()
- /**
- * 保存图像
- *
- * @param name 名称
- * @param type 图像类型
- */
- saveImage(name: string, type: string): void {
- let url = this.canvasView.toDataURL(`image/${type}`);
- SNetUtil.downLoad(name, url);
- } // Function saveImage()
- /**
- * 图像的 URL
- *
- * @param type 图像类型
- * @return 图像的 URL
- */
- imageUrl(type: string): string {
- return this.canvasView.toDataURL(`image/${type}`);
- } // Function imageUrl()
- /**
- * 视图内容保存为 SVG 文件
- *
- * @param name 文件名
- */
- saveSvg(name: string): void {
- let url = URL.createObjectURL(
- new Blob([this.svgData()], { type: "text/xml,charset=UTF-8" })
- );
- SNetUtil.downLoad(name, url);
- } // Function saveSvg()
- /**
- * 视图 SVG 图形的数据
- *
- * @return URL 地址
- */
- svgData(): string {
- let engine = new SSvgPaintEngine(this.width, this.height);
- let painter = new SPainter(engine);
- this.onDraw(painter);
- return engine.toSvg();
- } // Function saveSvg()
- /**
- * 缩放视图时计算视图的位置与缩放比例
- *
- * @param zoom 缩放比例
- * @param x0 缩放计算的中心点 X 坐标
- * @param y0 缩放计算的中心点 Y 坐标
- */
- scaleByPoint(zoom: number, x0: number, y0: number): void {
- if (!this.scalable) {
- return;
- }
- let z = zoom;
- /**
- * 缩放比例在最小比例和最大比例范围内
- */
- if (this.scale * zoom >= this.maxScale) {
- z = this.maxScale / this.scale;
- this.scale = this.maxScale;
- } else if (this.scale * zoom <= this.minScale) {
- z = this.minScale / this.scale;
- this.scale = this.minScale;
- } else {
- this.scale *= zoom;
- }
- this.origin.x = x0 - (x0 - this.origin.x) * z;
- this.origin.y = y0 - (y0 - this.origin.y) * z;
- } // Function scaleByPoint()
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- //
- /**
- * 循环
- */
- protected loop(): void {
- /** painter 对象 */
- let ctx = this.canvas;
- if (null != ctx && this._needDraw) {
- this._needDraw = false;
- let painter = new SPainter(this);
- this.onDraw(painter);
- }
- this.requestAnimationFrame(this.loop.bind(this));
- } // Function loop()
- /**
- * 绘制视图
- *
- * @param painter painter 对象
- */
- protected onDraw(painter: SPainter): void {} // Function onDraw()
- /**
- * 鼠标单击事件
- *
- * @param event 事件参数
- */
- protected onClick(event: MouseEvent): void {} // Function onClick()
- /**
- * 鼠标双击事件
- *
- * @param event 事件参数
- */
- protected onDoubleClick(event: MouseEvent): void {} // Function onDoubleClick()
- /**
- * 鼠标按下事件
- *
- * @param event 事件参数
- */
- protected onMouseDown(event: MouseEvent): void {
- let se = new SMouseEvent(event);
- if (se.buttons & SMouseButton.MidButton) {
- this._midKeyPos.x = se.x;
- this._midKeyPos.y = se.y;
- }
- } // Function onMouseDown()
- /**
- * 鼠标移动事件
- *
- * @param event 事件参数
- */
- protected onMouseMove(event: MouseEvent): void {
- // 如果可以移动
- if (this.moveable) {
- // 按中键移动
- let se = new SMouseEvent(event);
- if (se.buttons & SMouseButton.MidButton) {
- this.origin.x += se.x - this._midKeyPos.x;
- this.origin.y += se.y - this._midKeyPos.y;
- this._midKeyPos.x = se.x;
- this._midKeyPos.y = se.y;
- this.update();
- return;
- }
- }
- } // Function onMouseMove()
- /**
- * 鼠标松开事件
- *
- * @param event 事件参数
- */
- protected onMouseUp(event: MouseEvent): void {} // Function onMouseUp()
- /**
- * 上下文菜单事件
- *
- * @param event 事件参数
- */
- protected onContextMenu(event: MouseEvent): void {} // Function onContextMenu()
- /**
- * 鼠标滚轮事件
- *
- * @param event 事件参数
- */
- protected onMouseWheel(event: WheelEvent): void {
- if (event.deltaY < 0) {
- this.scaleByPoint(this.wheelZoom, event.offsetX, event.offsetY);
- } else {
- this.scaleByPoint(1 / this.wheelZoom, event.offsetX, event.offsetY);
- }
- } // Function onMouseWheel()
- /**
- * 按键按下事件
- *
- * @param event 事件参数
- */
- protected onKeyDown(event: KeyboardEvent): void {} // Function onKeydown()
- /**
- * 按键press事件
- *
- * @param event 事件参数
- */
- protected onKeyPress(event: KeyboardEvent): void {} // Function onKeypress()
- /**
- * 按键松开事件
- *
- * @param event 事件参数
- */
- protected onKeyUp(event: KeyboardEvent): void {} // Function onKeyup()
- /**
- * 开始触摸事件
- *
- * @param event 事件参数
- */
- protected onTouchStart(event: TouchEvent): void {
- let touches = event.touches;
- if (touches.length == 1) {
- this._touchPoint.x = event.touches[0].clientX;
- this._touchPoint.y = event.touches[0].clientY;
- }
- if (touches.length == 2) {
- this._touchState = STouchState.Zoom;
- this._beforeLength = this.getDistance(event);
- }
- } // Function onTouchStart()
- /**
- * 触摸移动事件
- *
- * @param event 事件参数
- */
- protected onTouchMove(event: TouchEvent): void {
- let touches = event.touches;
- if (touches.length == 1) {
- this.origin.x += event.touches[0].clientX - this._touchPoint.x;
- this.origin.y += event.touches[0].clientY - this._touchPoint.y;
- this._touchPoint.x = event.touches[0].clientX;
- this._touchPoint.y = event.touches[0].clientY;
- }
- if (touches.length == 2) {
- this.viewZoom(event);
- }
- } // Function onTouchMove()
- /**
- * 结束触摸事件
- *
- * @param event 事件参数
- */
- protected onTouchEnd(event: TouchEvent): void {
- this._touchState = STouchState.None;
- } // Function onTouchEnd()
- /**
- * View大小变更事件
- *
- * @param event 事件参数
- */
- protected onResize(event: UIEvent): void {} // Function onClick()
- /**
- * 绑定事件
- *
- * @param element 要绑定事件的元素
- */
- private bindEvent(element: HTMLElement): void {
- // 绑定鼠标事件
- element.onclick = this.onClick.bind(this);
- element.ondblclick = this.onDoubleClick.bind(this);
- element.onmousedown = this.onMouseDown.bind(this);
- element.onmousemove = this.onMouseMove.bind(this);
- element.onmouseup = this.onMouseUp.bind(this);
- element.oncontextmenu = this.onContextMenu.bind(this);
- element.onwheel = this.onMouseWheel.bind(this);
- // 绑定按键事件
- element.onkeydown = this.onKeyDown.bind(this);
- element.onkeypress = this.onKeyPress.bind(this);
- element.onkeyup = this.onKeyUp.bind(this);
- // 触摸事件
- element.ontouchstart = this.onTouchStart.bind(this);
- element.ontouchmove = this.onTouchMove.bind(this);
- element.ontouchend = this.onTouchEnd.bind(this);
- // 绑定窗口事件
- element.onresize = this.onResize.bind(this);
- } // Function bindEvent()
- /**
- * 启动动画帧(即指定时间执行回调函数)
- *
- * @param callback 回调函数
- */
- private requestAnimationFrame(callback: FrameRequestCallback): number {
- let currTime = new Date().getTime();
- let timeToCall = Math.max(0, 16 - (currTime - this._lastFrameTime));
- let id = setTimeout(function(): void {
- callback(currTime + timeToCall);
- }, timeToCall);
- this._lastFrameTime = currTime + timeToCall;
- return id;
- } // Function requestAnimationFrame()
- /**
- * 缩放视图
- *
- * @param event 触摸事件
- */
- private viewZoom(event: TouchEvent): boolean {
- if (this._touchState == STouchState.Zoom) {
- // 获取两点的距离
- this._afterLength = this.getDistance(event);
- // 变化的长度;
- let gapLenght = this._afterLength - this._beforeLength;
- if (Math.abs(gapLenght) > 5 && this._beforeLength != 0.0) {
- // 求的缩放的比例
- let tempScale = this._afterLength / this._beforeLength;
- let p = this.getMiddlePoint(event);
- // 重设置
- this.scaleByPoint(tempScale, p.x, p.y);
- this._beforeLength = this._afterLength;
- }
- }
- return true;
- } // Function onTouchMove()
- /**
- * 获取两手指之间的距离
- *
- * @param event 触摸事件
- * @return 两手指之间的距离
- */
- private getDistance(event: TouchEvent): number {
- if (event.touches.length < 2) {
- return 0;
- }
- let dx = event.touches[0].clientX - event.touches[1].clientX;
- let dy = event.touches[0].clientY - event.touches[1].clientY;
- return Math.sqrt(dx * dx + dy * dy);
- } // Function getDistance()
- /**
- * 获得两个手指触摸点的中点坐标
- *
- * @param event 触摸事件
- * @return 得到视图的 x 坐标中点
- */
- private getMiddlePoint(event: TouchEvent): SPoint {
- return new SPoint(
- (event.touches[0].clientX + event.touches[1].clientX) / 2,
- (event.touches[0].clientY + event.touches[1].clientY) / 2
- );
- } // Function getMiddlePoint()
- } // Class SCanvasView
|