SCanvasView.ts 14 KB

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