SCanvasView.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. import {
  2. SMouseButton,
  3. SMouseEvent,
  4. SNetUtil,
  5. SObject,
  6. STouchState
  7. } from "@saga-web/base/lib/";
  8. import { SSvgPaintEngine } from "./engines/SSvgPaintEngine";
  9. import { SPoint } from "./types/SPoint";
  10. import { SPainter } from "./SPainter";
  11. /**
  12. * Canvas视图基类
  13. *
  14. * @author 庞利祥(sybotan@126.com)
  15. */
  16. export class SCanvasView extends SObject {
  17. private _needDraw = true;
  18. /** canvas视图 */
  19. protected readonly canvasView: HTMLCanvasElement;
  20. get canvas(): CanvasRenderingContext2D | null {
  21. return this.canvasView.getContext("2d") as CanvasRenderingContext2D;
  22. } // Get canvas
  23. /** 宽度 */
  24. get width(): number {
  25. return this.canvasView.width;
  26. } // Get width()
  27. /** 高度 */
  28. get height(): number {
  29. return this.canvasView.height;
  30. } // Get height()
  31. /** 原点坐标 */
  32. private _origin = new SPoint();
  33. get origin(): SPoint {
  34. return this._origin;
  35. } // Get origin
  36. set origin(v: SPoint) {
  37. if (!this.moveable) {
  38. return;
  39. }
  40. this._origin.x = v.x;
  41. this._origin.y = v.y;
  42. this._needDraw = true;
  43. } // Set origin
  44. /** 可否进行平移操作 */
  45. moveable = true;
  46. /** 缩放比例 */
  47. private _scale: number = 1;
  48. get scale(): number {
  49. return this._scale;
  50. } // Get scale
  51. set scale(v: number) {
  52. if (!this.scalable) {
  53. return;
  54. }
  55. this._scale = v;
  56. this._needDraw = true;
  57. } // Set scale
  58. /** 可否执行绽放操作 */
  59. scalable = true;
  60. /** 鼠标滚轮缩放速度 */
  61. wheelZoom = 1.05;
  62. /** 最小缩放比例 */
  63. minScale = 0.000001;
  64. /** 最大缩放比例 */
  65. maxScale = 1000000;
  66. /** 最后一次更新时间 */
  67. private _lastFrameTime = 0;
  68. /** 鼠标中键按下时位置 */
  69. private _midKeyPos = new SPoint();
  70. /** 手执状态 */
  71. private _touchState = STouchState.None;
  72. /** 手指拖动点 */
  73. private _touchPoint = new SPoint();
  74. /** 未缩放时二指间距离 */
  75. private _beforeLength = 0;
  76. /** 缩放后二指间距离 */
  77. private _afterLength = 0;
  78. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  79. //
  80. /**
  81. * 构造函数
  82. *
  83. * @param id 画布对象ID
  84. */
  85. constructor(id: string) {
  86. super();
  87. this.canvasView = document.getElementById(id) as HTMLCanvasElement;
  88. this.bindEvent(this.canvasView);
  89. this.requestAnimationFrame(this.loop.bind(this));
  90. } // Constructor
  91. /**
  92. * 更新视图
  93. */
  94. update(): void {
  95. this._needDraw = true;
  96. } // Function update()
  97. /**
  98. * 保存图像
  99. *
  100. * @param name 名称
  101. * @param type 图像类型
  102. */
  103. saveImage(name: string, type: string): void {
  104. let url = this.canvasView.toDataURL(`image/${type}`);
  105. SNetUtil.downLoad(name, url);
  106. } // Function saveImage()
  107. /**
  108. * 图像的URL
  109. *
  110. * @param type 图像类型
  111. */
  112. imageUrl(type: string): string {
  113. return this.canvasView.toDataURL(`image/${type}`);
  114. } // Function imageUrl()
  115. /**
  116. * 视图内容保存为SVG文件
  117. *
  118. * @param name 文件名
  119. */
  120. saveSvg(name: string): void {
  121. let url = URL.createObjectURL(
  122. new Blob([this.svgData()], { type: "text/xml,charset=UTF-8" })
  123. );
  124. SNetUtil.downLoad(name, url);
  125. } // Function saveSvg()
  126. /**
  127. * 视图SVG图形的数据
  128. *
  129. * @return URL地址
  130. */
  131. svgData(): string {
  132. let engine = new SSvgPaintEngine(this.width, this.height);
  133. let painter = new SPainter(engine);
  134. this.onDraw(painter);
  135. return engine.toSvg();
  136. } // Function saveSvg()
  137. /**
  138. * 缩放视图时计算视图的位置与缩放比例
  139. *
  140. * @param zoom 缩放比例
  141. * @param x0 缩放计算的中心点X坐标
  142. * @param y0 缩放计算的中心点Y坐标
  143. */
  144. scaleByPoint(zoom: number, x0: number, y0: number): void {
  145. if (!this.scalable) {
  146. return;
  147. }
  148. let z = zoom;
  149. /**
  150. * 缩放比例在最小比例和最大比例范围内
  151. */
  152. if (this.scale * zoom >= this.maxScale) {
  153. z = this.maxScale / this.scale;
  154. this.scale = this.maxScale;
  155. } else if (this.scale * zoom <= this.minScale) {
  156. z = this.minScale / this.scale;
  157. this.scale = this.minScale;
  158. } else {
  159. this.scale *= zoom;
  160. }
  161. this.origin.x = x0 - (x0 - this.origin.x) * z;
  162. this.origin.y = y0 - (y0 - this.origin.y) * z;
  163. } // Function scaleByPoint()
  164. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  165. //
  166. /**
  167. * 循环
  168. */
  169. protected loop(): void {
  170. /** painter对象 */
  171. let ctx = this.canvas;
  172. if (null != ctx && this._needDraw) {
  173. this._needDraw = false;
  174. let painter = new SPainter(this);
  175. this.onDraw(painter);
  176. }
  177. this.requestAnimationFrame(this.loop.bind(this));
  178. } // Function loop()
  179. /**
  180. * 绘制视图
  181. *
  182. * @param painter painter对象
  183. */
  184. protected onDraw(painter: SPainter): void {} // Function onDraw()
  185. /**
  186. * 鼠标单击事件
  187. *
  188. * @param event 事件参数
  189. */
  190. protected onClick(event: MouseEvent): void {} // Function onClick()
  191. /**
  192. * 鼠标双击事件
  193. *
  194. * @param event 事件参数
  195. */
  196. protected onDoubleClick(event: MouseEvent): void {} // Function onDoubleClick()
  197. /**
  198. * 鼠标按下事件
  199. *
  200. * @param event 事件参数
  201. */
  202. protected onMouseDown(event: MouseEvent): void {
  203. let se = new SMouseEvent(event);
  204. if (se.buttons & SMouseButton.MidButton) {
  205. this._midKeyPos.x = se.x;
  206. this._midKeyPos.y = se.y;
  207. }
  208. } // Function onMouseDown()
  209. /**
  210. * 鼠标移动事件
  211. *
  212. * @param event 事件参数
  213. */
  214. protected onMouseMove(event: MouseEvent): void {
  215. // 如果可以移动
  216. if (this.moveable) {
  217. // 按中键移动
  218. let se = new SMouseEvent(event);
  219. if (se.buttons & SMouseButton.MidButton) {
  220. this.origin.x += se.x - this._midKeyPos.x;
  221. this.origin.y += se.y - this._midKeyPos.y;
  222. this._midKeyPos.x = se.x;
  223. this._midKeyPos.y = se.y;
  224. this.update();
  225. return;
  226. }
  227. }
  228. } // Function onMouseMove()
  229. /**
  230. * 鼠标松开事件
  231. *
  232. * @param event 事件参数
  233. */
  234. protected onMouseUp(event: MouseEvent): void {} // Function onMouseUp()
  235. /**
  236. * 上下文菜单事件
  237. *
  238. * @param event 事件参数
  239. */
  240. protected onContextMenu(event: MouseEvent): void {} // Function onContextMenu()
  241. /**
  242. * 鼠标滚轮事件
  243. *
  244. * @param event 事件参数
  245. */
  246. protected onMouseWheel(event: WheelEvent): void {
  247. if (event.deltaY < 0) {
  248. this.scaleByPoint(this.wheelZoom, event.offsetX, event.offsetY);
  249. } else {
  250. this.scaleByPoint(1 / this.wheelZoom, event.offsetX, event.offsetY);
  251. }
  252. } // Function onMouseWheel()
  253. /**
  254. * 按键按下事件
  255. *
  256. * @param event 事件参数
  257. */
  258. protected onKeyDown(event: KeyboardEvent): void {} // Function onKeydown()
  259. /**
  260. * 按键press事件
  261. *
  262. * @param event 事件参数
  263. */
  264. protected onKeyPress(event: KeyboardEvent): void {} // Function onKeypress()
  265. /**
  266. * 按键松开事件
  267. *
  268. * @param event 事件参数
  269. */
  270. protected onKeyUp(event: KeyboardEvent): void {} // Function onKeyup()
  271. /**
  272. * 开始触摸事件
  273. *
  274. * @param event 事件参数
  275. */
  276. protected onTouchStart(event: TouchEvent): void {
  277. let touches = event.touches;
  278. if (touches.length == 1) {
  279. this._touchPoint.x = event.touches[0].clientX;
  280. this._touchPoint.y = event.touches[0].clientY;
  281. }
  282. if (touches.length == 2) {
  283. this._touchState = STouchState.Zoom;
  284. this._beforeLength = this.getDistance(event);
  285. }
  286. } // Function onTouchStart()
  287. /**
  288. * 触摸移动事件
  289. *
  290. * @param event 事件参数
  291. */
  292. protected onTouchMove(event: TouchEvent): void {
  293. let touches = event.touches;
  294. if (touches.length == 1) {
  295. this.origin.x += event.touches[0].clientX - this._touchPoint.x;
  296. this.origin.y += event.touches[0].clientY - this._touchPoint.y;
  297. this._touchPoint.x = event.touches[0].clientX;
  298. this._touchPoint.y = event.touches[0].clientY;
  299. }
  300. if (touches.length == 2) {
  301. this.viewZoom(event);
  302. }
  303. } // Function onTouchMove()
  304. /**
  305. * 结束触摸事件
  306. *
  307. * @param event 事件参数
  308. */
  309. protected onTouchEnd(event: TouchEvent): void {
  310. this._touchState = STouchState.None;
  311. } // Function onTouchEnd()
  312. /**
  313. * View大小变更事件
  314. *
  315. * @param event 事件参数
  316. */
  317. protected onResize(event: UIEvent): void {} // Function onClick()
  318. /**
  319. * 绑定事件
  320. *
  321. * @param element 要绑定事件的元素
  322. */
  323. private bindEvent(element: HTMLElement): void {
  324. // 绑定鼠标事件
  325. element.onclick = this.onClick.bind(this);
  326. element.ondblclick = this.onDoubleClick.bind(this);
  327. element.onmousedown = this.onMouseDown.bind(this);
  328. element.onmousemove = this.onMouseMove.bind(this);
  329. element.onmouseup = this.onMouseUp.bind(this);
  330. element.oncontextmenu = this.onContextMenu.bind(this);
  331. element.onwheel = this.onMouseWheel.bind(this);
  332. // 绑定按键事件
  333. element.onkeydown = this.onKeyDown.bind(this);
  334. element.onkeypress = this.onKeyPress.bind(this);
  335. element.onkeyup = this.onKeyUp.bind(this);
  336. // 触摸事件
  337. element.ontouchstart = this.onTouchStart.bind(this);
  338. element.ontouchmove = this.onTouchMove.bind(this);
  339. element.ontouchend = this.onTouchEnd.bind(this);
  340. // 绑定窗口事件
  341. element.onresize = this.onResize.bind(this);
  342. } // Function bindEvent()
  343. /**
  344. * 启动动画帧(即指定时间执行回调函数)
  345. *
  346. * @param callback 回调函数
  347. */
  348. private requestAnimationFrame(callback: FrameRequestCallback): number {
  349. let currTime = new Date().getTime();
  350. let timeToCall = Math.max(0, 16 - (currTime - this._lastFrameTime));
  351. let id = setTimeout(function(): void {
  352. callback(currTime + timeToCall);
  353. }, timeToCall);
  354. this._lastFrameTime = currTime + timeToCall;
  355. return id;
  356. } // Function requestAnimationFrame()
  357. /**
  358. * 缩放视图
  359. *
  360. * @param event 触摸事件
  361. */
  362. private viewZoom(event: TouchEvent): boolean {
  363. if (this._touchState == STouchState.Zoom) {
  364. // 获取两点的距离
  365. this._afterLength = this.getDistance(event);
  366. // 变化的长度;
  367. let gapLenght = this._afterLength - this._beforeLength;
  368. if (Math.abs(gapLenght) > 5 && this._beforeLength != 0.0) {
  369. // 求的缩放的比例
  370. let tempScale = this._afterLength / this._beforeLength;
  371. let p = this.getMiddlePoint(event);
  372. // 重设置
  373. this.scaleByPoint(tempScale, p.x, p.y);
  374. this._beforeLength = this._afterLength;
  375. }
  376. }
  377. return true;
  378. } // Function onTouchMove()
  379. /**
  380. * 获取两手指之间的距离
  381. *
  382. * @param event 触摸事件
  383. * @return 两手指之间的距离
  384. */
  385. private getDistance(event: TouchEvent): number {
  386. if (event.touches.length < 2) {
  387. return 0;
  388. }
  389. let dx = event.touches[0].clientX - event.touches[1].clientX;
  390. let dy = event.touches[0].clientY - event.touches[1].clientY;
  391. return Math.sqrt(dx * dx + dy * dy);
  392. } // Function getDistance()
  393. /**
  394. * 获得两个手指触摸点的中点坐标
  395. *
  396. * @param event
  397. * @return 得到视图的x坐标中点
  398. */
  399. private getMiddlePoint(event: TouchEvent): SPoint {
  400. return new SPoint(
  401. (event.touches[0].clientX + event.touches[1].clientX) / 2,
  402. (event.touches[0].clientY + event.touches[1].clientY) / 2
  403. );
  404. } // Function getMiddlePoint()
  405. } // Class SCanvasView