js轻量 Canvas 实用类库
↑ 测试图
依赖类:
1 "use strict"; 2 3 var __emptyPoint = null, __emptyContext = null, __emptyPointA = null; 4 5 const ColorRefTable = { 6 "aliceblue": "#f0f8ff", 7 "antiquewhite": "#faebd7", 8 "aqua": "#00ffff", 9 "aquamarine": "#7fffd4", 10 "azure": "#f0ffff", 11 "beige": "#f5f5dc", 12 "bisque": "#ffe4c4", 13 "black": "#000000", 14 "blanchedalmond": "#ffebcd", 15 "blue": "#0000ff", 16 "blueviolet": "#8a2be2", 17 "brown": "#a52a2a", 18 "burlywood": "#deb887", 19 "cadetblue": "#5f9ea0", 20 "chartreuse": "#7fff00", 21 "chocolate": "#d2691e", 22 "coral": "#ff7f50", 23 "cornsilk": "#fff8dc", 24 "crimson": "#dc143c", 25 "cyan": "#00ffff", 26 "darkblue": "#00008b", 27 "darkcyan": "#008b8b", 28 "darkgoldenrod": "#b8860b", 29 "darkgray": "#a9a9a9", 30 "darkgreen": "#006400", 31 "darkgrey": "#a9a9a9", 32 "darkkhaki": "#bdb76b", 33 "darkmagenta": "#8b008b", 34 "firebrick": "#b22222", 35 "darkolivegreen": "#556b2f", 36 "darkorange": "#ff8c00", 37 "darkorchid": "#9932cc", 38 "darkred": "#8b0000", 39 "darksalmon": "#e9967a", 40 "darkseagreen": "#8fbc8f", 41 "darkslateblue": "#483d8b", 42 "darkslategray": "#2f4f4f", 43 "darkslategrey": "#2f4f4f", 44 "darkturquoise": "#00ced1", 45 "darkviolet": "#9400d3", 46 "deeppink": "#ff1493", 47 "deepskyblue": "#00bfff", 48 "dimgray": "#696969", 49 "dimgrey": "#696969", 50 "dodgerblue": "#1e90ff", 51 "floralwhite": "#fffaf0", 52 "forestgreen": "#228b22", 53 "fuchsia": "#ff00ff", 54 "gainsboro": "#dcdcdc", 55 "ghostwhite": "#f8f8ff", 56 "gold": "#ffd700", 57 "goldenrod": "#daa520", 58 "gray": "#808080", 59 "green": "#008000", 60 "greenyellow": "#adff2f", 61 "grey": "#808080", 62 "honeydew": "#f0fff0", 63 "hotpink": "#ff69b4", 64 "indianred": "#cd5c5c", 65 "indigo": "#4b0082", 66 "ivory": "#fffff0", 67 "khaki": "#f0e68c", 68 "lavender": "#e6e6fa", 69 "lavenderblush": "#fff0f5", 70 "lawngreen": "#7cfc00", 71 "lemonchiffon": "#fffacd", 72 "lightblue": "#add8e6", 73 "lightcoral": "#f08080", 74 "lightcyan": "#e0ffff", 75 "lightgoldenrodyellow": "#fafad2", 76 "lightgray": "#d3d3d3", 77 "lightgreen": "#90ee90", 78 "lightgrey": "#d3d3d3", 79 "lightpink": "#ffb6c1", 80 "lightsalmon": "#ffa07a", 81 "lightseagreen": "#20b2aa", 82 "lightskyblue": "#87cefa", 83 "lightslategray": "#778899", 84 "lightslategrey": "#778899", 85 "lightsteelblue": "#b0c4de", 86 "lightyellow": "#ffffe0", 87 "lime": "#00ff00", 88 "limegreen": "#32cd32", 89 "linen": "#faf0e6", 90 "magenta": "#ff00ff", 91 "maroon": "#800000", 92 "mediumaquamarine": "#66cdaa", 93 "mediumblue": "#0000cd", 94 "mediumorchid": "#ba55d3", 95 "mediumpurple": "#9370db", 96 "mediumseagreen": "#3cb371", 97 "mediumslateblue": "#7b68ee", 98 "mediumspringgreen": "#00fa9a", 99 "mediumturquoise": "#48d1cc", 100 "mediumvioletred": "#c71585", 101 "midnightblue": "#191970", 102 "mintcream": "#f5fffa", 103 "mistyrose": "#ffe4e1", 104 "moccasin": "#ffe4b5", 105 "navajowhite": "#ffdead", 106 "navy": "#000080", 107 "oldlace": "#fdf5e6", 108 "olive": "#808000", 109 "olivedrab": "#6b8e23", 110 "orange": "#ffa500", 111 "orangered": "#ff4500", 112 "orchid": "#da70d6", 113 "palegoldenrod": "#eee8aa", 114 "palegreen": "#98fb98", 115 "paleturquoise": "#afeeee", 116 "palevioletred": "#db7093", 117 "papayawhip": "#ffefd5", 118 "peachpuff": "#ffdab9", 119 "peru": "#cd853f", 120 "pink": "#ffc0cb", 121 "plum": "#dda0dd", 122 "powderblue": "#b0e0e6", 123 "purple": "#800080", 124 "red": "#ff0000", 125 "rosybrown": "#bc8f8f", 126 "royalblue": "#4169e1", 127 "saddlebrown": "#8b4513", 128 "salmon": "#fa8072", 129 "sandybrown": "#f4a460", 130 "seagreen": "#2e8b57", 131 "seashell": "#fff5ee", 132 "sienna": "#a0522d", 133 "silver": "#c0c0c0", 134 "skyblue": "#87ceeb", 135 "slateblue": "#6a5acd", 136 "slategray": "#708090", 137 "slategrey": "#708090", 138 "snow": "#fffafa", 139 "springgreen": "#00ff7f", 140 "steelblue": "#4682b4", 141 "tan": "#d2b48c", 142 "teal": "#008080", 143 "thistle": "#d8bfd8", 144 "tomato": "#ff6347", 145 "turquoise": "#40e0d0", 146 "violet": "#ee82ee", 147 "wheat": "#f5deb3", 148 "white": "#ffffff", 149 "whitesmoke": "#f5f5f5", 150 "yellow": "#ffff00", 151 "yellowgreen": "#9acd32" 152 }, 153 154 UTILS = { 155 156 emptyArray(arr){ 157 return !Array.isArray(arr) || arr.length === 0; 158 }, 159 160 createContext(w = 1, h = 1){ 161 const canvas = document.createElement("canvas"), 162 context = canvas.getContext("2d"); 163 canvas.width = w; 164 canvas.height = h; 165 return context; 166 }, 167 168 isObject(obj){ 169 170 return obj !== null && typeof obj === "object" && Array.isArray(obj) === false; 171 172 }, 173 174 isNumber(num){ 175 176 return typeof num === "number" && isNaN(num) === false; 177 178 }, 179 180 //获取最后一个点后面的字符 181 getFileType(string){ 182 let type = "", str = string.split('').reverse().join(''); 183 for(let k = 0, len = str.length; k < len; k++){ 184 if(str[k] === ".") break; 185 type += str[k]; 186 } 187 return type.split('').reverse().join(''); 188 }, 189 190 //删除 string 所有的空格 191 deleteSpaceAll(str){ 192 const len = str.length; 193 var result = ''; 194 for(let i = 0; i < len; i++){ 195 if(str[i] !== '') result += str[i] 196 } 197 198 return result 199 }, 200 201 //删除 string 两边空格 202 removeSpaceSides(string){ 203 204 return string.replace(/(^\s*)|(\s*$)/g, ""); 205 206 }, 207 208 //返回 num 与 num1 之间的随机数 209 random(num, num1){ 210 211 if(num < num1) return Math.random() * (num1 - num) + num; 212 213 else if(num > num1) return Math.random() * (num - num1) + num1; 214 215 else return num; 216 217 }, 218 219 //生成 UUID 220 generateUUID: function (){ 221 const _lut = []; 222 223 for ( let i = 0; i < 256; i ++ ) { 224 225 _lut[ i ] = ( i < 16 ? '0' : '' ) + ( i ).toString( 16 ); 226 227 } 228 229 return function (){ 230 const d0 = Math.random() * 0xffffffff | 0; 231 const d1 = Math.random() * 0xffffffff | 0; 232 const d2 = Math.random() * 0xffffffff | 0; 233 const d3 = Math.random() * 0xffffffff | 0; 234 const uuid = _lut[ d0 & 0xff ] + _lut[ d0 >> 8 & 0xff ] + _lut[ d0 >> 16 & 0xff ] + _lut[ d0 >> 24 & 0xff ] + '-' + 235 _lut[ d1 & 0xff ] + _lut[ d1 >> 8 & 0xff ] + '-' + _lut[ d1 >> 16 & 0x0f | 0x40 ] + _lut[ d1 >> 24 & 0xff ] + '-' + 236 _lut[ d2 & 0x3f | 0x80 ] + _lut[ d2 >> 8 & 0xff ] + '-' + _lut[ d2 >> 16 & 0xff ] + _lut[ d2 >> 24 & 0xff ] + 237 _lut[ d3 & 0xff ] + _lut[ d3 >> 8 & 0xff ] + _lut[ d3 >> 16 & 0xff ] + _lut[ d3 >> 24 & 0xff ]; 238 239 return uuid.toLowerCase(); //toLowerCase() 这里展平连接的字符串以节省堆内存空间 240 } 241 }(), 242 243 //欧几里得距离(两点的直线距离) 244 distance(x, y, x1, y1){ 245 246 return Math.sqrt(Math.pow(x - x1, 2) + Math.pow(y - y1, 2)); 247 248 }, 249 250 downloadFile(blob, fileName){ 251 const link = document.createElement("a"); 252 link.href = URL.createObjectURL(blob); 253 link.download = fileName; 254 link.click(); 255 }, 256 257 loadFileJSON(callback){ 258 const input = document.createElement("input"); 259 input.type = "file"; 260 input.accept = ".json"; 261 262 input.onchange = a => { 263 if(a.target.files.length === 0) return; 264 const fr = new FileReader(); 265 fr.onloadend = b => callback(b.target.result); 266 fr.readAsText(a.target.files[0]); //fr.readAsDataURL(a.target.files[0]); 267 } 268 269 input.click(); 270 }, 271 272 get emptyPoint(){ 273 if(__emptyPoint === null) __emptyPoint = new Point(); 274 return __emptyPoint; 275 }, 276 277 get emptyPointA(){ 278 if(__emptyPointA === null) __emptyPointA = new Point(); 279 return __emptyPointA; 280 }, 281 282 get emptyContext(){ 283 if(__emptyContext === null) __emptyContext = document.createElement("canvas").getContext('2d') 284 return __emptyContext; 285 }, 286 287 } 288 289 290 291 292 /** Ajax 293 parameter: 294 option = { 295 url: 可选, 默认 '' 296 method: 可选, post 或 get请求, 默认 post 297 asy: 可选, 是否异步执行, 默认 true 298 success: 可选, 成功回调, 默认 null 299 error: 可选, 超时或失败调用, 默认 null 300 change: 可选, 请求状态改变时调用, 默认 null 301 data: 可选, 如果定义则在初始化时自动执行.send(data)方法 302 } 303 304 demo: 305 const data = `email=${email}&password=${password}`, 306 307 //默认 post 请求: 308 ajax = new Ajax({ 309 url: './login', 310 data: data, 311 success: mes => console.log(mes), 312 }); 313 314 //get 请求: 315 ajax.method = "get"; 316 ajax.send(data); 317 */ 318 class Ajax{ 319 320 constructor(option = {}){ 321 this.url = option.url || ""; 322 this.method = option.method || "post"; 323 this.asy = typeof option.asy === "boolean" ? option.asy : true; 324 this.success = option.success || null; 325 this.error = option.error || null; 326 this.change = option.change || null; 327 328 //init XML 329 this.xhr = new XMLHttpRequest(); 330 331 this.xhr.onerror = this.xhr.ontimeout = option.error || null; 332 333 this.xhr.onreadystatechange = event => { 334 335 if(event.target.readyState === 4 && event.target.status === 200){ 336 337 if(this.success !== null) this.success(event.target.responseText, event); 338 339 } 340 341 else if(this.change !== null) this.change(event); 342 343 } 344 345 if(option.data) this.send(option.data); 346 } 347 348 send(data = ""){ 349 if(this.method === "get"){ 350 this.xhr.open(this.method, this.url+"?"+data, this.asy); 351 this.xhr.send(); 352 } 353 354 else if(this.method === "post"){ 355 this.xhr.open(this.method, this.url, this.asy); 356 this.xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); 357 this.xhr.send(data); 358 } 359 } 360 361 } 362 363 364 365 366 /* IndexedDB 本地数据库 367 368 parameter: 369 name: String; //需要打开的数据库名称(如果不存在则会新建一个) 必须 370 done: Function(IndexedDB); //链接数据库成功时的回调 默认 null 371 version: Number; //数据库版本(高版本数据库将覆盖低版本的数据库) 默认 1 372 373 attribute: 374 database: IndexedDB; //链接完成的数据库对象 375 transaction: IDBTransaction; //事务管理(读和写) 376 objectStore: IDBObjectStore; //当前的事务 377 378 method: 379 set(data, key, callback) //添加或更新 380 get(key, callback) //获取 381 delete(key, callback) //删除 382 383 traverse(callback) //遍历 384 getAll(callback) //获取全部 385 clear(callback) //清理所以数据 386 close() //关闭数据库链接 387 388 readOnly: 389 390 static: 391 indexedDB: Object; 392 393 demo: 394 395 new IndexedDB('TEST', db => { 396 397 conosle.log(db); 398 399 }); 400 401 */ 402 class IndexedDB{ 403 404 static indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB; 405 406 get objectStore(){ //每个事务只能使用一次, 所以每次都需要重新创建 407 return this.database.transaction(this.name, 'readwrite').objectStore(this.name); 408 } 409 410 constructor(name, done = null, version = 1){ 411 412 if(IndexedDB.indexedDB === undefined) return console.error("IndexedDB: 不支持IndexedDB"); 413 414 if(typeof name !== 'string') return console.warn('IndexedDB: 参数错误'); 415 416 this.name = name; 417 this.database = null; 418 419 const request = IndexedDB.indexedDB.open(name, version); 420 421 request.onupgradeneeded = (e)=>{ //数据库不存在 或 版本号不同时 触发 422 if(!this.database) this.database = e.target.result; 423 if(this.database.objectStoreNames.contains(name) === false) this.database.createObjectStore(name); 424 } 425 426 request.onsuccess = (e)=>{ 427 this.database = e.target.result; 428 if(typeof done === 'function') done(this); 429 } 430 431 request.onerror = (e)=>{ 432 console.error(e); 433 } 434 435 } 436 437 close(){ 438 439 return this.database.close(); 440 441 } 442 443 clear(callback){ 444 445 this.objectStore.clear().onsuccess = callback; 446 447 } 448 449 traverse(callback){ 450 451 this.objectStore.openCursor().onsuccess = callback; 452 453 } 454 455 set(data, key = 0, callback){ 456 457 this.objectStore.put(data, key).onsuccess = callback; 458 459 } 460 461 get(key = 0, callback){ 462 463 this.objectStore.get(key).onsuccess = callback; 464 465 } 466 467 del(key = 0, callback){ 468 469 this.objectStore.delete(key).onsuccess = callback; 470 471 } 472 473 getAll(callback){ 474 475 this.objectStore.getAll().onsuccess = callback; 476 477 } 478 479 } 480 481 482 483 484 /* TreeStruct 树结构基类 485 486 attribute: 487 parent: TreeStruct; 488 children: Array[TreeStruct]; 489 490 method: 491 add(v: TreeStruct): v; //v添加到自己的子集 492 remove(v: TreeStruct): v; //删除v, 前提v必须是自己的子集 493 export(): Array[Object]; //TreeStruct 转为 可导出的结构, 包括其所有的后代 494 495 getPath(v: TreeStruct): Array[TreeStruct]; //获取自己到v的路径 496 497 traverse(callback: Function): undefined; //迭代自己的每一个后代, 包括自己 498 callback(value: TreeStruct); //如返回 "continue" 则不在迭代其后代(不是结束迭代, 而是只结束当前节点的后代); 499 500 traverseUp(callback): undefined; //向上遍历每一个父, 包括自己 501 callback(value: TreeStruct); //如返回 "break" 立即停止遍历; 502 503 static: 504 import(arr: Array[Object]): TreeStruct; //.export() 返回的 arr 转为 TreeStruct 505 506 */ 507 class TreeStruct{ 508 509 static import(arr){ 510 511 //json = JSON.parse(json); 512 const len = arr.length; 513 514 for(let k = 0, v; k < len; k++){ 515 v = Object.assign(new TreeStruct(), arr[k]); 516 v.parent = arr[arr[k].parent] || null; 517 if(v.parent !== null) v.parent.add(v); 518 arr[k] = v; 519 } 520 521 return arr[0]; 522 523 } 524 525 constructor(){ 526 this.parent = null; 527 this.children = []; 528 } 529 530 getPath(v){ 531 532 var path; 533 534 const pathA = []; 535 this.traverseUp(tar => { 536 if(v === tar){ 537 path = pathA; 538 return "break"; 539 } 540 pathA.push(tar); 541 }); 542 543 if(path) return path; 544 545 const pathB = []; 546 v.traverseUp(tar => { 547 if(this === tar){ 548 path = pathB.reverse(); 549 return "break"; 550 } 551 else{ 552 let i = pathA.indexOf(tar); 553 if(i !== -1){ 554 pathA.splice(i); 555 pathA.push(tar); 556 path = pathA.concat(pathB.reverse()); 557 return "break"; 558 } 559 } 560 pathB.push(tar); 561 }); 562 563 return path; 564 565 } 566 567 add(v){ 568 v.parent = this; 569 if(this.children.includes(v) === false) this.children.push(v); 570 571 return v; 572 } 573 574 remove(v){ 575 const i = this.children.indexOf(v); 576 if(i !== -1) this.children.splice(i, 1); 577 v.parent = null; 578 579 return v; 580 } 581 582 traverse(callback, key = 0){ 583 584 if(callback(this, key) !== "continue"){ 585 586 for(let k = 0, len = this.children.length; k < len; k++){ 587 588 this.children[k].traverse(callback, k); 589 590 } 591 592 } 593 594 } 595 596 traverseUp(callback){ 597 598 var par = this.parent; 599 600 while(par !== null){ 601 if(callback(par) === "break") return; 602 par = par.parent; 603 } 604 605 } 606 607 export(){ 608 609 const result = [], arr = []; 610 var obj = null; 611 612 this.traverse(v => { 613 obj = Object.assign({}, v); 614 obj.parent = arr.indexOf(v.parent); 615 delete obj.children; 616 result.push(obj); 617 arr.push(v); 618 }); 619 620 return result; //JSON.stringify(result); 621 622 } 623 624 } 625 626 Object.defineProperties(TreeStruct.prototype, { 627 628 isTreeStruct: { 629 configurable: false, 630 enumerable: false, 631 writable: false, 632 value: true, 633 } 634 635 }); 636 637 638 639 640 /* Point 641 parameter: 642 x = 0, y = 0; 643 644 attribute 645 x, y: Number; 646 647 method: 648 set(x, y): this; 649 angle(): Number; 650 copy(point): this; 651 clone(): Point; 652 distance(point): Number; //获取欧几里得距离 653 distanceMHD(point): Number; //获取曼哈顿距离 654 distanceCompare(point): Number; //获取用于比较的距离(相对于.distance() 效率更高) 655 equals(point): Bool; //是否恒等 656 reverse(): this; //取反值 657 rotate(origin: Object{x,y}, angle): this; //旋转点 658 normalize(): this; //归一 659 */ 660 class Point{ 661 662 constructor(x = 0, y = 0){ 663 this.x = x; 664 this.y = y; 665 } 666 667 set(x = 0, y = 0){ 668 this.x = x; 669 this.y = y; 670 671 return this; 672 } 673 674 angle(){ 675 676 return Math.atan2(this.y, this.x); 677 678 } 679 680 copy(point){ 681 682 this.x = point.x; 683 this.y = point.y; 684 return this; 685 //return Object.assign(this, point); 686 687 } 688 689 clone(){ 690 691 return new this.constructor().copy(this); 692 //return Object.assign(new this.constructor(), this); 693 694 } 695 696 distance(point){ 697 698 return Math.sqrt(Math.pow(point.x - this.x, 2) + Math.pow(point.y - this.y, 2)); 699 700 } 701 702 distanceMHD(point){ 703 704 return Math.abs(this.x - point.x) + Math.abs(this.y - point.y); 705 706 } 707 708 distanceCompare(point){ 709 710 return Math.pow(this.x - point.x, 2) + Math.pow(this.y - point.y, 2); 711 712 } 713 714 equals(point){ 715 716 return point.x === this.x && point.y === this.y; 717 718 } 719 720 reverse(){ 721 this.x = -this.x; 722 this.y = -this.y; 723 724 return this; 725 } 726 727 rotate(origin, angle){ 728 const c = Math.cos(angle), s = Math.sin(angle), 729 x = this.x - origin.x, y = this.y - origin.y; 730 731 this.x = x * c - y * s + origin.x; 732 this.y = x * s + y * c + origin.y; 733 734 return this; 735 } 736 737 normalize(){ 738 const len = 1 / (Math.sqrt(this.x * this.x + this.y * this.y) || 1); 739 this.x *= len; 740 this.y *= len; 741 742 return this; 743 } 744 745 /* add(point){ 746 this.x += point.x; 747 this.y += point.y; 748 return this; 749 } 750 751 sub(point){ 752 this.x -= point.x; 753 this.y -= point.y; 754 return this; 755 } 756 757 multiply(point){ 758 this.x *= point.x; 759 this.y *= point.y; 760 return this; 761 } 762 763 divide(point){ 764 this.x /= point.x; 765 this.y /= point.y; 766 return this; 767 } */ 768 769 } 770 771 Object.defineProperties(Point.prototype, { 772 773 isPoint: { 774 configurable: false, 775 enumerable: false, 776 writable: false, 777 value: true, 778 } 779 780 }); 781 782 783 784 785 /* Rotate 786 parameter: 787 x, y, a = 0 788 789 attribute: 790 angle: Number; 791 origin: Point; 792 793 method: 794 set(x, y, a): this; 795 */ 796 class Rotate{ 797 798 constructor(x, y, a = 0){ 799 this.angle = a; 800 this.origin = new Point(x, y); 801 } 802 803 set(x, y, a){ 804 this.origin.x = x; 805 this.origin.y = y; 806 this.angle = a; 807 return this; 808 } 809 810 pos(x, y){ 811 this.origin.x = x; 812 this.origin.y = y; 813 return this; 814 } 815 816 copy(rotate){ 817 this.angle = rotate.angle; 818 this.origin.copy(rotate.origin); 819 return this; 820 } 821 822 clone(){ 823 return new this.constructor().copy(this); 824 } 825 826 toAngle(v){ 827 this.angle = v / 180 * Math.PI; 828 return this; 829 } 830 831 } 832 833 834 835 836 /* Line 837 parameter: x, y, x1, y1: Number; 838 attribute: x, y, x1, y1: Number; 839 method: 840 set(x, y, x1, y1): this; 841 copy(line): this; 842 clone(): Line; 843 containsPoint(x, y): Bool; //点是否在线上 844 intersectPoint(line: Line, point: Point): Point; //如果不相交则返回null, 否则返回交点Point 845 isIntersect(line): Bool; //this与line是否相交 846 */ 847 class Line{ 848 849 constructor(x = 0, y = 0, x1 = 0, y1 = 0){ 850 this.x = x; 851 this.y = y; 852 this.x1 = x1; 853 this.y1 = y1; 854 } 855 856 set(x = 0, y = 0, x1 = 0, y1 = 0){ 857 this.x = x; 858 this.y = y; 859 this.x1 = x1; 860 this.y1 = y1; 861 return this; 862 } 863 864 copy(line){ 865 this.x = line.x; 866 this.y = line.y; 867 this.x1 = line.x1; 868 this.y1 = line.y1; 869 return this; 870 //return Object.assign(this, line); 871 } 872 873 clone(){ 874 return new this.constructor().copy(this); 875 //return Object.assign(new this.constructor(), this); 876 } 877 878 containsPoint(x, y){ 879 return (x - this.x) * (x - this.x1) <= 0 && (y - this.y) * (y - this.y1) <= 0; 880 } 881 882 intersectPoint(line, point){ 883 //解线性方程组, 求线段交点 884 //如果分母为0则平行或共线, 不相交 885 var denominator = (this.y1 - this.y) * (line.x1 - line.x) - (this.x - this.x1) * (line.y - line.y1); 886 if(denominator === 0) return null; 887 888 //线段所在直线的交点坐标 (x , y) 889 const x = ((this.x1 - this.x) * (line.x1 - line.x) * (line.y - this.y) 890 + (this.y1 - this.y) * (line.x1 - line.x) * this.x 891 - (line.y1 - line.y) * (this.x1 - this.x) * line.x) / denominator; 892 893 const y = -((this.y1 - this.y) * (line.y1 - line.y) * (line.x - this.x) 894 + (this.x1 - this.x) * (line.y1 - line.y) * this.y 895 - (line.x1 - line.x) * (this.y1 - this.y) * line.y) / denominator; 896 897 //判断交点是否在两条线段上 898 if(this.containsPoint(x, y) && line.containsPoint(x, y)){ 899 point.x = x; 900 point.y = y; 901 return point; 902 } 903 904 return null; 905 } 906 907 isIntersect(line){ 908 //快速排斥: 909 //两个线段为对角线组成的矩形,如果这两个矩形没有重叠的部分,那么两条线段是不可能出现重叠的 910 911 //这里的确如此,这一步是判定两矩形是否相交 912 //1.线段ab的低点低于cd的最高点(可能重合) 913 //2.cd的最左端小于ab的最右端(可能重合) 914 //3.cd的最低点低于ab的最高点(加上条件1,两线段在竖直方向上重合) 915 //4.ab的最左端小于cd的最右端(加上条件2,两直线在水平方向上重合) 916 //综上4个条件,两条线段组成的矩形是重合的 917 //特别要注意一个矩形含于另一个矩形之内的情况 918 if(!(Math.min(this.x,this.x1)<=Math.max(line.x,line.x1) && Math.min(line.y,line.y1)<=Math.max(this.y,this.y1) && Math.min(line.x,line.x1)<=Math.max(this.x,this.x1) && Math.min(this.y,this.y1)<=Math.max(line.y,line.y1))) return false; 919 920 //跨立实验: 921 //如果两条线段相交,那么必须跨立,就是以一条线段为标准,另一条线段的两端点一定在这条线段的两段 922 //也就是说a b两点在线段cd的两端,c d两点在线段ab的两端 923 var u=(line.x-this.x)*(this.y1-this.y)-(this.x1-this.x)*(line.y-this.y), 924 v = (line.x1-this.x)*(this.y1-this.y)-(this.x1-this.x)*(line.y1-this.y), 925 w = (this.x-line.x)*(line.y1-line.y)-(line.x1-line.x)*(this.y-line.y), 926 z = (this.x1-line.x)*(line.y1-line.y)-(line.x1-line.x)*(this.y1-line.y); 927 928 return u*v <= 0.00000001 && w*z <= 0.00000001; 929 } 930 931 } 932 933 934 935 936 /* Box 矩形 937 parameter: 938 x = 0, y = 0, w = 0, h = 0; 939 940 attribute: 941 x,y: Number; 位置 942 w,h: Number; 大小 943 944 只读 945 mx, my: Number; // 946 947 method: 948 set(x, y, w, h): this; 949 pos(x, y): this; //设置位置 950 size(w, h): this; //设置大小 951 setFromRotate(rotate: Rotate): this; //通过 rotate 旋转自己 952 setFromShapeRect(shapeRect): this; //ShapeRect 转为 Box (忽略 ShapeRect 的旋转和缩放) 953 setFromCircle(circle, inner: Bool): this; // 954 setFromPolygon(polygon, inner = true): this;// 955 toArray(array: Array, index: Integer): this; 956 copy(box): this; //复制 957 clone(): Box; //克隆 958 center(box): this; //设置位置在box居中 959 distance(x, y): Number; //左上角原点 与 x,y 的直线距离 960 isEmpty(): Boolean; //.w.h是否小于等于零 961 maxX(): Number; //返回 max x(this.x+this.w); 962 maxY(): Number; //返回 max y(this.y+this.h); 963 expand(box): undefined; //扩容; 把box合并到this 964 equals(box): Boolean; //this与box是否恒等 965 intersectsBox(box): Boolean; //box与this是否相交(box在this内部也会返回true) 966 containsPoint(x, y): Boolean; //x,y点是否在this内 967 containsBox(box): Boolean; //box是否在this内(只是相交返回fasle) 968 computeOverflow(b: Box, r: Box): undefined; //this相对b超出的部分赋值到r; r的size小于或等于0的话说明完全超出 969 */ 970 class Box{ 971 972 get mx(){ 973 return this.x + this.w; 974 } 975 976 get my(){ 977 return this.y + this.h; 978 } 979 980 get cx(){ 981 return this.w / 2 + this.x; 982 } 983 984 get cy(){ 985 return this.h / 2 + this.y; 986 } 987 988 constructor(x = 0, y = 0, w = 0, h = 0){ 989 //this.set(x, y, w, h); 990 this.x = x; 991 this.y = y; 992 this.w = w; 993 this.h = h; 994 } 995 996 set(x, y, w, h){ 997 this.x = x; 998 this.y = y; 999 this.w = w; 1000 this.h = h; 1001 return this; 1002 } 1003 1004 pos(x, y){ 1005 this.x = x; 1006 this.y = y; 1007 return this; 1008 } 1009 1010 size(w, h){ 1011 this.w = w; 1012 this.h = h; 1013 return this; 1014 } 1015 1016 setFromRotate(rotate){ 1017 var minX = this.x, minY = this.y, maxX = 0, maxY = 0; 1018 const point = UTILS.emptyPoint, 1019 run = function (){ 1020 if(point.x < minX) minX = point.x; 1021 else if(point.x > maxX) maxX = point.x; 1022 if(point.y < minY) minY = point.y; 1023 else if(point.y > maxY) maxY = point.y; 1024 } 1025 1026 point.set(this.x, this.y).rotate(rotate.origin, rotate.angle); run(); 1027 point.set(this.mx, this.y).rotate(rotate.origin, rotate.angle); run(); 1028 point.set(this.mx, this.my).rotate(rotate.origin, rotate.angle); run(); 1029 point.set(this.x, this.my).rotate(rotate.origin, rotate.angle); run(); 1030 1031 this.x = minX; 1032 this.y = minY; 1033 this.w = maxX - minX; 1034 this.h = maxY - minY; 1035 1036 return this; 1037 } 1038 1039 setFromShapeRect(shapeRect){ 1040 this.width = shapeRect.width; 1041 this.height = shapeRect.height; 1042 this.x = shapeRect.position.x - this.width / 2; 1043 this.y = shapeRect.position.y - this.height / 2; 1044 return this; 1045 } 1046 1047 setFromCircle(circle, inner = true){ 1048 if(inner === true){ 1049 this.x = Math.sin(-135 / 180 * Math.PI) * circle.r + circle.x; 1050 this.y = Math.cos(-135 / 180 * Math.PI) * circle.r + circle.y; 1051 this.w = this.h = Math.sin(135 / 180 * Math.PI) * circle.r + circle.x - this.x; 1052 } 1053 1054 else{ 1055 this.x = circle.x - circle.r; 1056 this.y = circle.y - circle.r; 1057 this.w = this.h = circle.r * 2; 1058 } 1059 return this; 1060 } 1061 1062 setFromPolygon(polygon, inner = true){ 1063 if(inner === true){ 1064 console.warn('Box: 暂不支持第二个参数为true'); 1065 } 1066 1067 else{ 1068 const len = polygon.path.length; 1069 let x = Infinity, y = Infinity, mx = 0, my = 0; 1070 for(let k = 0, v; k < len; k+=2){ 1071 v = polygon.path[k]; 1072 if(v < x) x = v; 1073 else if(v > mx) mx = v; 1074 1075 v = polygon.path[k+1]; 1076 if(v < y) y = v; 1077 else if(v > my) my = v; 1078 1079 } 1080 1081 this.set(x, y, mx - x, my - y); 1082 1083 } 1084 return this; 1085 } 1086 1087 toArray(array, index){ 1088 array[index] = this.x; 1089 array[index+1] = this.y; 1090 array[index+2] = this.w; 1091 array[index+3] = this.h; 1092 1093 return this; 1094 } 1095 1096 copy(box){ 1097 this.x = box.x; 1098 this.y = box.y; 1099 this.w = box.w; 1100 this.h = box.h; 1101 return this; 1102 //return Object.assign(this, box); //this.set(box.x, box.y, box.w, box.h); 1103 } 1104 1105 clone(){ 1106 return new this.constructor().copy(this); 1107 //return Object.assign(new this.constructor(), this); 1108 } 1109 1110 center(box){ 1111 this.x = (box.w - this.w) / 2 + box.x; 1112 this.y = (box.h - this.h) / 2 + box.y; 1113 return this; 1114 } 1115 1116 distance(x, y){ 1117 return Math.sqrt(Math.pow(this.x - x, 2) + Math.pow(this.y - y, 2)); 1118 } 1119 1120 isEmpty(){ 1121 return this.w <= 0 || this.h <= 0; 1122 } 1123 1124 maxX(){ 1125 return this.x + this.w; 1126 } 1127 1128 maxY(){ 1129 return this.y + this.h; 1130 } 1131 1132 equals(box){ 1133 return this.x === box.x && this.w === box.w && this.y === box.y && this.h === box.h; 1134 } 1135 1136 expand(box){ 1137 var v = Math.min(this.x, box.x); 1138 this.w = Math.max(this.x + this.w - v, box.x + box.w - v); 1139 this.x = v; 1140 1141 v = Math.min(this.y, box.y); 1142 this.h = Math.max(this.y + this.h - v, box.y + box.h - v); 1143 this.y = v; 1144 } 1145 1146 intersectsBox(box){ 1147 return box.x + box.w < this.x || box.x > this.x + this.w || box.y + box.h < this.y || box.y > this.y + this.h ? false : true; 1148 } 1149 1150 containsPoint(x, y){ 1151 return x < this.x || x > this.x + this.w || y < this.y || y > this.y + this.h ? false : true; 1152 } 1153 1154 containsBox(box){ 1155 return this.x <= box.x && box.x + box.w <= this.x + this.w && this.y <= box.y && box.y + box.h <= this.y + this.h; 1156 } 1157 1158 computeOverflow(p, r){ 1159 r["copy"](this); 1160 1161 if(this["x"] < p["x"]){ 1162 r["x"] = p["x"]; 1163 r["w"] -= p["x"] - this["x"]; 1164 } 1165 1166 if(this["y"] < p["y"]){ 1167 r["y"] = p["y"]; 1168 r["h"] -= p["y"] - this["y"]; 1169 } 1170 1171 var m = p["x"] + p["w"]; 1172 if(r["x"] + r["w"] > m) r["w"] = m - r["x"]; 1173 1174 m = p["y"] + p["h"]; 1175 if(r["y"] + r["h"] > m) r["h"] = m - r["y"]; 1176 } 1177 1178 } 1179 1180 Object.defineProperties(Box.prototype, { 1181 1182 isBox: { 1183 configurable: false, 1184 enumerable: false, 1185 writable: false, 1186 value: true, 1187 }, 1188 1189 }); 1190 1191 1192 1193 1194 /* Circle 圆形 1195 parameter: 1196 attribute: 1197 x,y: Number; 中心点 1198 r: Number; 半径 1199 1200 //只读 1201 r2: Number; //返回直径 r*2 1202 1203 method: 1204 set(x, y, r): this; 1205 pos(x, y): this; 1206 copy(circle: Circle): this; 1207 clone(): Circle; 1208 distance(x, y): Number; 1209 equals(circle: Circle): Bool; 1210 containsPoint(x, y): Bool; 1211 intersectsCircle(circle: Circle): Bool; 1212 intersectsBox(box: Box): Bool; 1213 setFromBox(box, inner = true): this; 1214 1215 */ 1216 class Circle{ 1217 1218 get r2(){ 1219 return this.r * 2; 1220 } 1221 1222 constructor(x = 0, y = 0, r = -1){ 1223 //this.set(0, 0, -1); 1224 this.x = x; 1225 this.y = y; 1226 this.r = r; 1227 } 1228 1229 set(x, y, r){ 1230 this.x = x; 1231 this.y = y; 1232 this.r = r; 1233 1234 return this; 1235 } 1236 1237 setFromBox(box, world = true, inner = true){ 1238 this.x = box.w / 2 + (world === true ? box.x : 0); 1239 this.y = box.h / 2 + (world === true ? box.y : 0); 1240 this.r = inner === true ? Math.min(box.w, box.h) / 2 : UTILS.distance(0, 0, box.w, box.h) / 2; 1241 1242 return this; 1243 } 1244 1245 toArray(array, index){ 1246 array[index] = this.x; 1247 array[index+1] = this.y; 1248 array[index+2] = this.r; 1249 1250 return this; 1251 } 1252 1253 pos(x, y){ 1254 this.x = x; 1255 this.y = y; 1256 1257 return this; 1258 } 1259 1260 copy(circle){ 1261 this.r = circle.r; 1262 this.x = circle.x; 1263 this.y = circle.y; 1264 1265 return this; 1266 } 1267 1268 clone(){ 1269 1270 return new this.constructor().copy(this); 1271 1272 } 1273 1274 distance(x, y){ 1275 1276 return Math.sqrt(Math.pow(this.x - x, 2) + Math.pow(this.y - y, 2)); 1277 1278 } 1279 1280 equals(circle){ 1281 1282 return circle.x === this.x && circle.y === this.y && circle.r === this.r; 1283 1284 } 1285 1286 containsPoint(x, y){ 1287 1288 return (Math.pow(x - this.x, 2) + Math.pow(y - this.y, 2) <= Math.pow(this.r, 2)); 1289 1290 } 1291 1292 intersectsCircle(circle){ 1293 1294 return (Math.pow(circle.x - this.x, 2) + Math.pow(circle.y - this.y, 2) <= Math.pow(circle.r + this.r, 2)); 1295 1296 } 1297 1298 intersectsBox(box){ 1299 1300 return (Math.pow(Math.max(box.x, Math.min(box.x + box.w, this.x)) - this.x, 2) + Math.pow(Math.max(box.y, Math.min(box.y + box.h, this.y)) - this.y, 2) <= Math.pow(this.r, 2)); 1301 1302 } 1303 1304 } 1305 1306 Object.defineProperties(Circle.prototype, { 1307 1308 isCircle: { 1309 configurable: false, 1310 enumerable: false, 1311 writable: false, 1312 value: true, 1313 } 1314 1315 }); 1316 1317 1318 1319 1320 /* Polygon 多边形 1321 1322 parameter: 1323 path: Array[x, y]; 1324 1325 attribute: 1326 1327 //只读属性 1328 path: Array[x, y]; 1329 1330 method: 1331 add(x, y): this; //x,y添加至path; 1332 containsPoint(x, y): Bool; //x,y是否在多边形的内部(注意: 在路径上也返回 true) 1333 1334 */ 1335 class Polygon{ 1336 1337 #position = null; 1338 #path2D = null; 1339 1340 get path(){ 1341 1342 return this.#position; 1343 1344 } 1345 1346 constructor(path = []){ 1347 this.#position = path; 1348 1349 this.#path2D = new Path2D(); 1350 1351 var len = path.length; 1352 if(len >= 2){ 1353 if(len % 2 !== 0){ 1354 len -= 1; 1355 path.splice(len, 1); 1356 } 1357 1358 const con = this.#path2D; 1359 con.moveTo(path[0], path[1]); 1360 for(let k = 2; k < len; k+=2) con.lineTo(path[k], path[k+1]); 1361 1362 } 1363 1364 } 1365 1366 add(x, y){ 1367 this.#position.push(x, y); 1368 this.#path2D.lineTo(x, y); 1369 return this; 1370 } 1371 1372 containsPoint(x, y){ 1373 1374 return UTILS.emptyContext.isPointInPath(this.#path2D, x, y); 1375 1376 } 1377 1378 isInPolygon(checkPoint, polygonPoints) { 1379 var counter = 0; 1380 var i; 1381 var xinters; 1382 var p1, p2; 1383 var pointCount = polygonPoints.length; 1384 p1 = polygonPoints[0]; 1385 for (i = 1; i <= pointCount; i++) { 1386 p2 = polygonPoints[i % pointCount]; 1387 if ( 1388 checkPoint[0] > Math.min(p1[0], p2[0]) && 1389 checkPoint[0] <= Math.max(p1[0], p2[0]) 1390 ) { 1391 if (checkPoint[1] <= Math.max(p1[1], p2[1])) { 1392 if (p1[0] != p2[0]) { 1393 xinters = 1394 (checkPoint[0] - p1[0]) * 1395 (p2[1] - p1[1]) / 1396 (p2[0] - p1[0]) + 1397 p1[1]; 1398 if (p1[1] == p2[1] || checkPoint[1] <= xinters) { 1399 counter++; 1400 } 1401 } 1402 } 1403 } 1404 p1 = p2; 1405 } 1406 if (counter % 2 == 0) { 1407 return false; 1408 } else { 1409 return true; 1410 } 1411 } 1412 1413 containsPolygon(polygon){ 1414 const path = polygon.path, len = path.length; 1415 for(let k = 0; k < len; k += 2){ 1416 if(this.containsPoint(path[k], path[k+1]) === false) return false; 1417 } 1418 1419 return true; 1420 } 1421 1422 toPoints(){ 1423 const path = this.path, len = path.length, result = []; 1424 1425 for(let k = 0; k < len; k += 2) result.push(new Point(path[k], path[k+1])); 1426 1427 return result; 1428 } 1429 1430 toLines(){ 1431 const path = this.path, len = path.length, result = []; 1432 1433 for(let k = 0, x = NaN, y; k < len; k += 2){ 1434 1435 if(isNaN(x)){ 1436 x = path[k]; 1437 y = path[k+1]; 1438 continue; 1439 } 1440 1441 const line = new Line(x, y, path[k], path[k+1]); 1442 1443 x = line.x1; 1444 y = line.y1; 1445 1446 result.push(line); 1447 1448 } 1449 1450 return result; 1451 } 1452 1453 merge(polygon){ 1454 1455 const linesA = this.toLines(), linesB = polygon.toLines(), nodes = [], newLines = [], 1456 1457 pointA = new Point(), pointB = new Point(), 1458 1459 forEachNodes = (pathA, pathB, funcA = null, funcB = null) => { 1460 for(let k = 0, lenA = pathA.length, lenB = pathB.length; k < lenA; k++){ 1461 if(funcA !== null) funcA(pathA[k]); 1462 1463 for(let i = 0; i < lenB; i++){ 1464 if(funcB !== null) funcB(pathB[i], pathA[k]); 1465 } 1466 1467 } 1468 } 1469 1470 if(this.containsPolygon(polygon)){console.log('this -> polygon'); 1471 forEachNodes(linesA, linesB, lineA => newLines.push(lineA), (lineB, lineA) => { 1472 if(lineA.intersectPoint(lineB, pointA) === pointA) newLines.push(pointA.clone()); 1473 }); 1474 1475 return newLines; 1476 } 1477 1478 //收集所有的交点 (保存至 line) 1479 forEachNodes(linesA, linesB, lineA => lineA.nodes = [], (lineB, lineA) => { 1480 if(lineB.nodes === undefined) lineB.nodes = []; 1481 if(lineA.intersectPoint(lineB, pointA) === pointA){ 1482 const node = { 1483 lineA: lineA, 1484 lineB: lineB, 1485 point: pointA.clone(), 1486 disA: pointA.distanceCompare(pointB.set(lineA.x, lineA.y)), 1487 disB: pointA.distanceCompare(pointB.set(lineB.x, lineB.y)), 1488 } 1489 lineA.nodes.push(node); 1490 lineB.nodes.push(node); 1491 nodes.push(node); 1492 } 1493 }); 1494 1495 //交点以原点为目标排序 1496 for(let k = 0, sotr = function (a,b){return a.disA - b.disA;}, countA = linesA.length; k < countA; k++) linesA[k].nodes.sort(sotr); 1497 for(let k = 0, sotr = function (a,b){return a.disB - b.disB;}, countB = linesB.length; k < countB; k++) linesB[k].nodes.sort(sotr); 1498 1499 var _loopTypeA, _loopTypeB; 1500 const result_loop = { 1501 lines: null, 1502 loopType: '', 1503 line: null, 1504 count: 0, 1505 indexed: 0, 1506 }, 1507 1508 //遍历某条线 1509 loop = (lines, index, loopType) => { 1510 const length = lines.length, indexed = lines.length, model = lines === linesA ? polygon : this; 1511 1512 var line, i = 1; 1513 while(true){ 1514 if(loopType === 'next') index = index === length - 1 ? 0 : index + 1; 1515 else if(loopType === 'back') index = index === 0 ? length - 1 : index - 1; 1516 line = lines[index]; 1517 1518 result_loop.count = line.nodes.length; 1519 if(result_loop.count !== 0){ 1520 result_loop.lines = lines; 1521 result_loop.loopType = loopType; 1522 result_loop.line = line; 1523 result_loop.indexed = index; 1524 if(loopType === 'next') addLine(line, model); 1525 1526 return result_loop; 1527 } 1528 1529 addLine(line, model); 1530 if(indexed === i++) break; 1531 1532 } 1533 1534 }, 1535 1536 //更新或创建交点的索引 1537 setNodeIndex = (lines, index, loopType) => { 1538 const line = lines[index], count = line.nodes.length; 1539 if(loopType === undefined) loopType = lines === linesA ? _loopTypeA : _loopTypeB; 1540 1541 if(loopType === undefined) return; 1542 1543 if(line.nodeIndex === undefined){ 1544 line.nodeIndex = loopType === 'next' ? 0 : count - 1; 1545 line.nodeState = count === 1 ? 'end' : 'start'; 1546 1547 } 1548 1549 else{ 1550 if(line.nodeState === 'end' || line.nodeState === ''){ 1551 line.nodeState = ''; 1552 return; 1553 } 1554 1555 if(loopType === 'next'){ 1556 line.nodeIndex += 1; 1557 1558 if(line.nodeIndex === count - 1) line.nodeState = 'end'; 1559 else line.nodeState = 'run'; 1560 } 1561 1562 else if(loopType === 'back'){ 1563 line.nodeIndex -= 1; 1564 1565 if(line.nodeIndex === 0) line.nodeState = 'end'; 1566 else line.nodeState = 'run'; 1567 } 1568 1569 } 1570 1571 }, 1572 1573 //只有在跳线的时候才执行此方法, 如果跳线的话: 某条线上的交点必然在两端; 1574 getLoopType = (lines, index, nodePoint) => { 1575 const line = lines[index], lineNext = lines[index === lines.length - 1 ? 0 : index + 1], 1576 1577 model = lines === linesA ? polygon : this, 1578 isLineBack = newLines.includes(line) === false && model.containsPoint(line.x, line.y) === false, 1579 isLineNext = newLines.includes(lineNext) === false && model.containsPoint(lineNext.x, lineNext.y) === false; 1580 1581 if(isLineBack && isLineNext){ 1582 const len = line.nodes.length; 1583 if(len >= 2){ 1584 if(line.nodes[len - 1].point.equals(nodePoint)) return 'next'; 1585 else if(line.nodes[0].point.equals(nodePoint)) return 'back'; 1586 } 1587 1588 else console.warn('路径复杂', line); 1589 1590 } 1591 1592 else if(isLineNext){ 1593 return 'next'; 1594 } 1595 1596 else if(isLineBack){ 1597 return 'back'; 1598 } 1599 1600 return ''; 1601 }, 1602 1603 //添加线至新的形状数组 1604 addLine = (line, model) => { 1605 //if(newLines.includes(line) === false && model.containsPoint(line.x, line.y) === false) newLines.push(line); 1606 if(line.nodes.length === 0) newLines.push(line); 1607 else if(model.containsPoint(line.x, line.y) === false) newLines.push(line); 1608 1609 }, 1610 1611 //处理拥有交点的线 1612 computeNodes = v => { 1613 if(v === undefined || v.count === 0) return; 1614 1615 setNodeIndex(v.lines, v.indexed, v.loopType); 1616 1617 //添加交点 1618 const node = v.line.nodes[v.line.nodeIndex]; 1619 if(newLines.includes(node.point) === false) newLines.push(node.point); 1620 else return; 1621 1622 var lines = v.lines === linesA ? linesB : linesA, 1623 line = lines === linesA ? node.lineA : node.lineB, 1624 index = lines.indexOf(line); 1625 1626 setNodeIndex(lines, index); 1627 1628 //选择交点状态 1629 var nodeState = line.nodeState !== undefined ? line.nodeState : v.line.nodeState; 1630 if((v.count <= 2 && line.nodes.length > 2) || (v.count > 2 && line.nodes.length <= 2)){ 1631 if(line.nodeState === 'start'){ 1632 const backLine = v.loopType === 'next' ? v.lines[v.indexed === 0 ? v.lines.length-1 : v.indexed-1] : v.lines[v.indexed === v.lines.length-1 ? 0 : v.indexed+1]; 1633 1634 if(newLines.includes(backLine) && backLine.nodes.length === 0){ 1635 nodeState = 'run'; 1636 } 1637 1638 } 1639 else if(line.nodeState === 'end'){ 1640 const nextLine = v.loopType === 'next' ? v.lines[v.indexed === v.lines.length-1 ? 0 : v.indexed+1] : v.lines[v.indexed === 0 ? v.lines.length-1 : v.indexed-1]; 1641 const model = v.lines === linesA ? polygon : this; 1642 if(model.containsPoint(nextLine.x, nextLine.y) === false && nextLine.nodes.length === 0){ 1643 nodeState = 'run'; 1644 } 1645 1646 } 1647 } 1648 1649 switch(nodeState){ 1650 1651 //不跳线 1652 case 'run': 1653 if(v.loopType === 'back') addLine(v.line, v.lines === linesA ? polygon : this); 1654 return computeNodes(loop(v.lines, v.indexed, v.loopType)); 1655 1656 //跳线 1657 case 'start': 1658 case 'end': 1659 const loopType = getLoopType(lines, index, node.point); 1660 if(loopType !== ''){ 1661 if(lines === linesA) _loopTypeA = loopType; 1662 else _loopTypeB = loopType; 1663 if(loopType === 'back') addLine(line, v.lines === linesA ? this : polygon); 1664 return computeNodes(loop(lines, index, loopType)); 1665 } 1666 break; 1667 1668 } 1669 1670 } 1671 1672 //获取介入点 1673 var startLine = null; 1674 for(let k = 0, len = nodes.length, node; k < len; k++){ 1675 node = nodes[k]; 1676 if(node.lineA.nodes.length !== 0){ 1677 startLine = node.lineA; 1678 if(node.lineA.nodes.length === 1 && node.lineB.nodes.length > 1){ 1679 startLine = node.lineB.nodes[0].lineB; 1680 result_loop.lines = linesB; 1681 result_loop.loopType = _loopTypeB = 'next'; 1682 } 1683 else{ 1684 result_loop.lines = linesA; 1685 result_loop.loopType = _loopTypeA = 'next'; 1686 } 1687 result_loop.line = startLine; 1688 result_loop.count = startLine.nodes.length; 1689 result_loop.indexed = result_loop.lines.indexOf(startLine); 1690 break; 1691 } 1692 } 1693 1694 if(startLine === null){ 1695 console.warn('Polygon: 找不到介入点, 终止了合并'); 1696 return newLines; 1697 } 1698 1699 computeNodes(result_loop); 1700 1701 return newLines; 1702 } 1703 1704 } 1705 1706 1707 1708 1709 /* ShapeRect (一般2d矩形的原点在左上, 此矩形类的原点在中间) 1710 parameter: 1711 width = 0, height = 0 1712 1713 attribute: 1714 width, height: Number; //矩形的宽高 1715 position: Point; //位置(是旋转和缩放的中心点) 1716 rotation: Number; //旋转(绕 Z 轴旋转的弧度) 1717 scale: Point; //缩放 1718 1719 method: 1720 setFromBox(box): this; //Box 转为 ShapeRect 1721 compute(): undefined; //计算出矩形 1722 applyCanvas(context): undefined; //矩形应用到画布的上下文中 1723 1724 demo: 1725 const canvasRect = new ShapeRect(100, 150); console.log(canvasRect); 1726 1727 const canvas = document.createElement("canvas"); 1728 canvas.width = WORLD.width; 1729 canvas.height = WORLD.height; 1730 canvas.style = ` 1731 position: absolute; 1732 z-index: 9999; 1733 background: rgb(127,127,127); 1734 `; 1735 document.body.appendChild(canvas); 1736 1737 canvasRect.position.set(300, 300); 1738 canvasRect.rotation = UTILS.toAngle(45); 1739 canvasRect.scale.set(1.5, 1.5); 1740 canvasRect.compute(); 1741 1742 const context = canvas.getContext("2d"); 1743 canvasRect.applyCanvas(context); 1744 1745 context.strokeStyle = "red"; 1746 context.stroke(); 1747 */ 1748 class ShapeRect{ 1749 1750 #dots = []; 1751 #size = new Point(); 1752 #min = new Point(); 1753 #max = new Point(); 1754 1755 constructor(width = 0, height = 0){ 1756 this.width = width; 1757 this.height = height; 1758 this.position = new Point(); 1759 this.rotation = 0; 1760 this.scale = new Point(1, 1); 1761 } 1762 1763 setFromBox(box){ 1764 this.width = box.w; 1765 this.height = box.h; 1766 this.position.set(box.cx, box.cy); 1767 return this; 1768 } 1769 1770 compute(){ 1771 //scale 1772 this.#size.set(this.width * this.scale.x, this.height * this.scale.y); 1773 1774 //position 1775 this.#min.set(this.position.x - this.#size.x / 2, this.position.y - this.#size.y / 2); 1776 this.#max.set(this.#min.x + this.#size.x, this.#min.y + this.#size.y); 1777 1778 //rotation 1779 const point = UTILS.emptyPoint; 1780 this.#dots.length = 0; 1781 1782 point.set(this.#min.x, this.#min.y).rotate(this.position, this.rotation); 1783 this.#dots.push(point.x, point.y); 1784 1785 point.set(this.#max.x, this.#min.y).rotate(this.position, this.rotation); 1786 this.#dots.push(point.x, point.y); 1787 1788 point.set(this.#max.x, this.#max.y).rotate(this.position, this.rotation); 1789 this.#dots.push(point.x, point.y); 1790 1791 point.set(this.#min.x, this.#max.y).rotate(this.position, this.rotation); 1792 this.#dots.push(point.x, point.y); 1793 1794 //缩放旋转后的 min 与 max 1795 //this.#min.set(Math.min(this.#dots[0], this.#dots[2], this.#dots[4], this.#dots[6]), Math.min(this.#dots[1], this.#dots[3], this.#dots[5], this.#dots[7])); 1796 //this.#max.set(Math.max(this.#dots[0], this.#dots[2], this.#dots[4], this.#dots[6]), Math.max(this.#dots[1], this.#dots[3], this.#dots[5], this.#dots[7])) 1797 } 1798 1799 drawImage(img, context){ 1800 UTILS.emptyContext.canvas.width = this.#size.x; 1801 UTILS.emptyContext.canvas.height = this.#size.y; 1802 UTILS.emptyContext.drawImage(img, 0, 0, this.#size.x, this.#size.y); 1803 1804 const imageData = UTILS.emptyContext.getImageData(0, 0, this.#size.x, this.#size.y), 1805 width = imageData.width, height = imageData.height, 1806 len = width * height, point = UTILS.emptyPoint, center = UTILS.emptyPointA.set(width/2, height/2), 1807 1808 newImageData = UTILS.emptyContext.createImageData(width, height); 1809 1810 for(let k = 0; k < len; k++){ 1811 point.set(k % width, Math.floor(k / width)).rotate(center, this.rotation); 1812 if(point.x < 0 || point.y < 0 || point.x > newImageData.width-1 || point.y > newImageData.height-1) continue; 1813 1814 point.set((width * Math.round(point.y) + Math.round(point.x)) * 4, k * 4); 1815 newImageData.data[point.x] = imageData.data[point.y]; 1816 newImageData.data[point.x + 1] = imageData.data[point.y + 1]; 1817 newImageData.data[point.x + 2] = imageData.data[point.y + 2]; 1818 newImageData.data[point.x + 3] = imageData.data[point.y + 3]; 1819 } 1820 1821 context.putImageData(newImageData, 20, 20); 1822 } 1823 1824 applyCanvas(context){ 1825 context.beginPath(); 1826 context.moveTo(this.#dots[0], this.#dots[1]); 1827 1828 for(let k = 2, len = this.#dots.length; k < len; k += 2){ 1829 context.lineTo(this.#dots[k], this.#dots[k + 1]); 1830 } 1831 1832 context.closePath(); 1833 } 1834 1835 } 1836 1837 1838 1839 1840 /* RGBColor 1841 parameter: 1842 r, g, b 1843 1844 method: 1845 set(r, g, b: Number): this; //rgb: 0 - 255; 第一个参数可以为 css color 1846 setFormHex(hex: Number): this; // 1847 setFormHSV(h, s, v: Number): this; //h:0-360; s,v:0-100; 颜色, 明度, 暗度 1848 setFormString(str: String): Number; //str: 英文|css color; 返回的是透明度 (如果为 rgba 则返回a; 否则总是返回1) 1849 1850 copy(v: RGBColor): this; 1851 clone(): RGBColor; 1852 1853 getHex(): Number; 1854 getHexString(): String; 1855 getHSV(result: Object{h, s, v}): result; //result: 默认是一个新的Object 1856 getRGBA(alpha: Number): String; //alpha: 0 - 1; 默认 1 1857 getStyle() //.getRGBA()别名 1858 1859 stringToColor(str: String): String; //str 转为 css color; 如果str不是color格式则返回 "" 1860 1861 */ 1862 class RGBColor{ 1863 1864 constructor(r = 255, g = 255, b = 255){ 1865 this.r = r; 1866 this.g = g; 1867 this.b = b; 1868 1869 } 1870 1871 copy(v){ 1872 this.r = v.r; 1873 this.g = v.g; 1874 this.b = v.b; 1875 return this; 1876 } 1877 1878 clone(){ 1879 return new this.constructor().copy(this); 1880 } 1881 1882 set(r, g, b){ 1883 if(typeof r !== "string"){ 1884 this.r = r || 255; 1885 this.g = g || 255; 1886 this.b = b || 255; 1887 } 1888 1889 else this.setFormString(r); 1890 1891 return this; 1892 } 1893 1894 setFormHex(hex){ 1895 hex = Math.floor( hex ); 1896 1897 this.r = hex >> 16 & 255; 1898 this.g = hex >> 8 & 255; 1899 this.b = hex & 255; 1900 return this; 1901 } 1902 1903 setFormHSV(h, s, v){ 1904 h = h >= 360 ? 0 : h; 1905 var s=s/100; 1906 var v=v/100; 1907 var h1=Math.floor(h/60) % 6; 1908 var f=h/60-h1; 1909 var p=v*(1-s); 1910 var q=v*(1-f*s); 1911 var t=v*(1-(1-f)*s); 1912 var r,g,b; 1913 switch(h1){ 1914 case 0: 1915 r=v; 1916 g=t; 1917 b=p; 1918 break; 1919 case 1: 1920 r=q; 1921 g=v; 1922 b=p; 1923 break; 1924 case 2: 1925 r=p; 1926 g=v; 1927 b=t; 1928 break; 1929 case 3: 1930 r=p; 1931 g=q; 1932 b=v; 1933 break; 1934 case 4: 1935 r=t; 1936 g=p; 1937 b=v; 1938 break; 1939 case 5: 1940 r=v; 1941 g=p; 1942 b=q; 1943 break; 1944 } 1945 1946 this.r = Math.round(r*255); 1947 this.g = Math.round(g*255); 1948 this.b = Math.round(b*255); 1949 return this; 1950 } 1951 1952 setFormString(color){ 1953 if(typeof color !== "string") return 1; 1954 var _color = this.stringToColor(color); 1955 1956 if(_color[0] === "#"){ 1957 const len = _color.length; 1958 if(len === 4){ 1959 _color = _color.slice(1); 1960 this.setFormHex(parseInt("0x"+_color + "" + _color)); 1961 } 1962 else if(len === 7) this.setFormHex(parseInt("0x"+_color.slice(1))); 1963 1964 } 1965 1966 else if(_color[0] === "r" && _color[1] === "g" && _color[2] === "b"){ 1967 const arr = []; 1968 for(let k = 0, len = _color.length, v = "", is = false; k < len; k++){ 1969 1970 if(is === true){ 1971 if(_color[k] === "," || _color[k] === ")"){ 1972 arr.push(parseFloat(v)); 1973 v = ""; 1974 } 1975 else v += _color[k]; 1976 1977 } 1978 1979 else if(_color[k] === "(") is = true; 1980 1981 } 1982 1983 this.set(arr[0], arr[1], arr[2]); 1984 return arr[3] === undefined ? 1 : arr[3]; 1985 } 1986 1987 return 1; 1988 } 1989 1990 getHex(){ 1991 1992 return Math.max( 0, Math.min( 255, this.r ) ) << 16 ^ Math.max( 0, Math.min( 255, this.g ) ) << 8 ^ Math.max( 0, Math.min( 255, this.b ) ) << 0; 1993 1994 } 1995 1996 getHexString(){ 1997 1998 return '#' + ( '000000' + this.getHex().toString( 16 ) ).slice( - 6 ); 1999 2000 } 2001 2002 getHSV(result){ 2003 result = result || {} 2004 var r=this.r/255; 2005 var g=this.g/255; 2006 var b=this.b/255; 2007 var h,s,v; 2008 var min=Math.min(r,g,b); 2009 var max=v=Math.max(r,g,b); 2010 var l=(min+max)/2; 2011 var difference = max-min; 2012 2013 if(max==min){ 2014 h=0; 2015 }else{ 2016 switch(max){ 2017 case r: h=(g-b)/difference+(g < b ? 6 : 0);break; 2018 case g: h=2.0+(b-r)/difference;break; 2019 case b: h=4.0+(r-g)/difference;break; 2020 } 2021 h=Math.round(h*60); 2022 } 2023 if(max==0){ 2024 s=0; 2025 }else{ 2026 s=1-min/max; 2027 } 2028 s=Math.round(s*100); 2029 v=Math.round(v*100); 2030 result.h = h; 2031 result.s = s; 2032 result.v = v; 2033 return result; 2034 } 2035 2036 getStyle(){ 2037 return this.getRGBA(1); 2038 } 2039 2040 getRGBA(alpha){ 2041 alpha = typeof alpha === 'number' ? alpha : 1; 2042 return 'rgba('+this.r+','+this.g+','+this.b+','+alpha+')'; 2043 } 2044 2045 stringToColor(str){ 2046 var _color = ""; 2047 for(let k = 0, len = str.length; k < len; k++){ 2048 if(str[k] === " ") continue; 2049 _color += str[k]; 2050 } 2051 2052 if(_color[0] === "#" || (_color[0] === "r" && _color[1] === "g" && _color[2] === "b")) return _color; 2053 2054 return ColorRefTable[_color] || ""; 2055 } 2056 2057 } 2058 2059 Object.defineProperties(RGBColor.prototype, { 2060 2061 isRGBColor: { 2062 configurable: false, 2063 enumerable: false, 2064 writable: false, 2065 value: true, 2066 } 2067 2068 }); 2069 2070 2071 2072 2073 /* Timer 定时器 2074 2075 parameter: 2076 func: Function; //定时器运行时的回调; 默认 null, 如果定义构造器将自动调用一次.restart()方法 2077 speed: Number; //延迟多少毫秒执行一次 func; 默认 3000; 2078 step: Integer; //执行多少次: 延迟speed毫秒执行一次 func; 默认 Infinity; 2079 2080 attribute: 2081 func, speed, step; //这些属性可以随时更改; 2082 2083 //只读属性 2084 readyState: String; //定时器状态; 可能值: '', 'start', 'running', 'done'; ''表示定时器从未启动 2085 number: Number; //运行的次数 2086 2087 method: 2088 start(func, speed): this; //启动定时器 (如果定时器正在运行则什么都不会做) 2089 restart(): undefined; //重启定时器 2090 stop(): undefined; //停止定时器 2091 2092 demo: 2093 //每 3000 毫秒 打印一次 timer.number, 10次后停止 2094 new Timer(timer => { 2095 console.log(timer.number); 2096 if(timer.number === 10) timer.stop(); 2097 }, 3000); 2098 2099 */ 2100 class Timer{ 2101 2102 #restart = -1; 2103 #speed = 0; 2104 #isRun = false; 2105 #i = 0; 2106 #readyState = ''; //start|running 2107 2108 get number(){ 2109 return this.#i; 2110 } 2111 2112 get readyState(){ 2113 return this.#i >= this.step ? 'done' : this.#readyState; 2114 } 2115 2116 get running(){ 2117 return this.#isRun; 2118 } 2119 2120 constructor(func = null, speed = 3000, step = Infinity){ 2121 this.func = func; 2122 this.speed = speed; 2123 this.step = step; 2124 //this.onDone = null; 2125 2126 if(typeof this.func === "function") this.restart(); 2127 2128 } 2129 2130 start(func, time){ 2131 if(typeof func === 'function') this.func = func; 2132 if(UTILS.isNumber(time) === true) this.speed = time; 2133 this.restart(); 2134 2135 return this; 2136 } 2137 2138 restart(){ 2139 if(this.#isRun === false){ 2140 setTimeout(this._loop, this.speed); 2141 this.#isRun = true; 2142 this.#restart = -1; 2143 this.#i = 0; 2144 this.#readyState = 'start'; 2145 2146 } 2147 2148 else{ 2149 this.#restart = Date.now(); 2150 this.#speed = this.speed; 2151 2152 } 2153 2154 } 2155 2156 stop(){ 2157 if(this.#isRun === true){ 2158 this.#restart = -1; 2159 this.#i = this.step; 2160 } 2161 2162 } 2163 2164 _loop = () => { 2165 2166 //重启计时器 2167 if(this.#restart !== -1){ 2168 2169 let gone = Date.now() - this.#restart; 2170 this.#restart = -1; 2171 2172 if(gone >= this.#speed) gone = this.speed; 2173 else{ 2174 if(this.#speed === this.speed) gone = this.#speed - gone; 2175 else gone = (this.#speed - gone) / this.#speed * this.speed; 2176 } 2177 2178 setTimeout(this._loop, gone); 2179 2180 this.#i = 1; 2181 if(this.func !== null) this.func(this); 2182 2183 } 2184 2185 //正在运行 2186 else if(this.#i < this.step){ 2187 2188 setTimeout(this._loop, this.speed); 2189 2190 this.#i++; 2191 if(this.#readyState !== 'running') this.#readyState = 'running'; 2192 if(this.func !== null) this.func(this); 2193 2194 } 2195 2196 //完成 2197 else this.#isRun = false; 2198 2199 } 2200 2201 } 2202 2203 2204 2205 2206 /* SeekPath A*寻路 2207 2208 parameter: 2209 option: Object{ 2210 angle: Number, //8 || 16 2211 timeout: Number, //单位为毫秒 2212 size: Number, //每格的宽高 2213 lenX, lenY: Number, //长度 2214 disables: Array[0||1], 2215 heights: Array[Number], 2216 path: Array[], //存放寻路结果 默认创建一个空数组 2217 } 2218 2219 attribute: 2220 size: Number; //每个索引的大小 2221 lenX: Number; //最大长度x (设置此属性时, 你需要重新.initMap(heights);) 2222 lenY: Number; //最大长度y (设置此属性时, 你需要重新.initMap(heights);) 2223 2224 //此属性已废弃 range: Box; //本次的搜索范围, 默认: 0,0,lenX,lenY 2225 angle: Number; //8四方向 或 16八方向 默认 16 2226 timeout: Number; //超时毫秒 默认 500 2227 mapRange: Box; //地图box 2228 //此属性已废弃(run函数不在检测相邻的高) maxHeight: Number; //相邻可走的最大高 默认 6 2229 2230 //只读属性 2231 success: Bool; //只读; 本次搜索是否成功找到终点; (如果为false说明.run()返回的是 距终点最近的路径; 超时也会被判定为false) 2232 path: Array[x, y, z]; //存放.run()返回的路径 2233 map: Map; //地图的缓存数据 2234 2235 method: 2236 initMap(heights: Array[Number]): undefiend; //初始化类时自动调用一次; heights:如果你的场景存在高请定义此参数 2237 run(x, y, x1, y1: Number): Array[x, y, z]; //参数索引坐标 2238 getDots(x, y, a, r): Array[ix, iy]; //获取周围的点 x,y, a:8|16, r:存放结果数组 2239 getLegalPoints(ix, iy, count): Array[x, y, z]; //获取 ix, iy 周围 合法的, 相邻的 count 个点 2240 2241 demo: 2242 const sp = new SeekPath({ 2243 angle: 16, 2244 timeout: 500, 2245 //maxHeight: 6, 2246 size: 10, 2247 lenX: 1000, 2248 lenY: 1000, 2249 }), 2250 2251 path = sp.run(0, 0, 1000, 1000); 2252 2253 console.log(sp); 2254 2255 */ 2256 class SeekPath{ 2257 2258 static _open = [] 2259 static _dots = [] //.run() .getLegalPoints() 2260 static dots4 = []; //._check() 2261 static _sort = function (a, b){return a["f"] - b["f"];} 2262 2263 #map = null; 2264 #path = null; 2265 #success = true; 2266 #halfX = 50; 2267 #halfY = 50; 2268 2269 #size = 10; 2270 #lenX = 10; 2271 #lenY = 10; 2272 2273 constructor(option = {}){ 2274 this.angle = (option.angle === 8 || option.angle === 16) ? option.angle : 16; //8四方向 或 16八方向 2275 this.timeout = option.timeout || 500; //超时毫秒 2276 //this.maxHeight = option.maxHeight || 6; 2277 this.mapRange = new Box(); 2278 this.size = option.size || 10; 2279 this.lenX = option.lenX || 10; 2280 this.lenY = option.lenY || 10; 2281 this.#path = Array.isArray(option.path) ? option.path : []; 2282 this.initMap(option.disable, option.height); 2283 option = undefined 2284 } 2285 2286 //this.#map[x][y] = Object{height: 此点的高,默认0, is: 此点是否可走,默认1(disable)}; 2287 get map(){ 2288 return this.#map; 2289 } 2290 2291 //this.#path = Array[x,y,z] 2292 get path(){ 2293 return this.#path; 2294 } 2295 2296 get success(){ 2297 return this.#success; 2298 } 2299 2300 get size(){ 2301 return this.#size; 2302 } 2303 2304 set size(v){ 2305 this.#size = v; 2306 v = v / 2; 2307 this.#halfX = v * this.#lenX; 2308 this.#halfY = v * this.#lenY; 2309 } 2310 2311 get lenX(){ 2312 return this.#lenX; 2313 } 2314 2315 set lenX(v){ 2316 this.#lenX = v; 2317 v = this.#size / 2; 2318 this.#halfX = v * this.#lenX; 2319 this.#halfY = v * this.#lenY; 2320 2321 } 2322 2323 get lenY(){ 2324 return this.#lenY; 2325 } 2326 2327 set lenY(v){ 2328 this.#lenY = v; 2329 v = this.#size / 2; 2330 this.#halfX = v * this.#lenX; 2331 this.#halfY = v * this.#lenY; 2332 2333 } 2334 2335 toScene(n, v){ //n = "x|y" 2336 //n = n === "y" ? "lenY" : "lenX"; 2337 if(n === "y" || n === "z") return v * this.#size - this.#halfY; 2338 return v * this.#size - this.#halfX; 2339 2340 } 2341 2342 toIndex(n, v){ 2343 //n = n === "y" ? "lenY" : "lenX"; 2344 if(n === "y" || n === "z") return Math.round((this.#halfY + v) / this.#size); 2345 return Math.round((this.#halfX + v) / this.#size); 2346 2347 } 2348 2349 initMap(disable, height){ 2350 2351 disable = Array.isArray(disable) === true ? disable : null; 2352 height = Array.isArray(height) === true ? height : null; 2353 2354 const lenX = this.lenX, lenY = this.lenY; 2355 var getHeight = (ix, iy) => { 2356 if(height === null) return 0; 2357 ix = height[ix * lenY + iy]; 2358 if(ix === undefined) return 0; 2359 return ix; 2360 }, 2361 getDisable = (ix, iy) => { 2362 if(disable === null) return 1; 2363 ix = disable[ix * lenY + iy]; 2364 if(ix === undefined) return 0; 2365 return ix; 2366 }, 2367 2368 map = []//new Map(); 2369 2370 for(let x = 0, y, m; x < lenX; x++){ 2371 m = []//new Map(); 2372 for(y = 0; y < lenY; y++) m[y] = {x:x, y:y, height:getHeight(x, y), is:getDisable(x, y), g:0, h:0, f:0, p:null, id:""}//m.set(y, {x:x, y:y, height:getHeight(x, y), g:0, h:0, f:0, p:null, id:""}); 2373 map[x] = m;//map.set(x, m); 2374 } 2375 2376 this.#map = map; 2377 this._id = -1; 2378 this._updateID(); 2379 this.mapRange.set(0, 0, this.#lenX-1, this.#lenY-1); 2380 2381 map = disable = height = getHeight = undefined; 2382 2383 } 2384 2385 getLegalPoints(ix, iy, count, result = []){ 2386 const _dots = SeekPath._dots; 2387 result.length = 0; 2388 result[0] = this.#map[ix][iy]; 2389 count += 1; 2390 2391 while(result.length < count){ 2392 for(let k = 0, i, n, d, len = result.length; k < len; k++){ 2393 n = result[k]; 2394 this.getDots(n.x, n.y, this.angle, _dots); 2395 for(i = 0; i < this.angle; i += 2){ 2396 d = this.#map[_dots[i]][_dots[i+1]]; 2397 if(d.is === 1 && this.mapRange.containsPoint(d.x, d.y) && !result.includes(d)){ 2398 if(Math.abs(n.x - d.x) + Math.abs(n.y - d.y) === 2 && this._check(n, d)){ 2399 result.push(d); 2400 } 2401 } 2402 } 2403 } 2404 } 2405 2406 result.splice(0, 1); 2407 return result; 2408 } 2409 2410 getLinePoints(now, next, count, result = []){ 2411 if(count === 1) return result[0] = next; 2412 if(count % 2 === 0) count += 1; 2413 2414 const len = Math.floor(count / 2), 2415 nowPoint = UTILS.emptyPoint, 2416 angle90 = UTILS.toAngle(90); 2417 2418 var i, ix, iy, n, nn = next; 2419 2420 nowPoint.set(now.x, now.y).rotate(next, angle90); //now 以 next 为原点顺时针旋转 90 度 2421 var disX = nowPoint.x - next.x, 2422 disY = nowPoint.y - next.y; 2423 2424 for(i = 0; i < len; i++){ 2425 ix = disX + disX * i + next.x; 2426 iy = disY + disY * i + next.y; 2427 2428 n = this.#map[ix][iy]; 2429 if(n.is === 1) nn = n; 2430 result[len-1-i] = nn; 2431 } 2432 2433 result[len] = next; 2434 nn = next 2435 2436 nowPoint.set(now.x, now.y).rotate(next, -angle90); 2437 disX = nowPoint.x - next.x; 2438 disY = nowPoint.y - next.y; 2439 2440 for(i = 0; i < len; i++){ 2441 ix = disX + disX * i + next.x; 2442 iy = disY + disY * i + next.y; 2443 2444 n = this.#map[ix][iy]; 2445 if(n.is === 1) nn = n; 2446 result[len+1+i] = nn; 2447 } 2448 2449 return result; 2450 } 2451 2452 getDots(x, y, a, r = []){ //获取周围的点 x,y, a:8|16, r:存放结果数组 2453 r.length = 0; 2454 const x_1 = x-1, x1 = x+1, y_1 = y-1, y1 = y+1; 2455 if(a === 16) r.push(x_1, y_1, x, y_1, x1, y_1, x_1, y, x1, y, x_1, y1, x, y1, x1, y1); 2456 else r.push(x, y_1, x, y1, x_1, y, x1, y); 2457 } 2458 2459 _updateID(){ //更新标记 2460 this._id++; 2461 this._openID = "o_"+this._id; 2462 this._closeID = "c_"+this._id; 2463 } 2464 2465 _check(dotA, dotB){ //检测 a 是否能到 b 2466 //获取 dotB 周围的4个点 并 遍历这4个点 2467 this.getDots(dotB.x, dotB.y, 8, SeekPath.dots4); 2468 for(let k = 0, x, y; k < 8; k += 2){ 2469 x = SeekPath.dots4[k]; 2470 y = SeekPath.dots4[k+1]; 2471 if(this.mapRange.containsPoint(x, y) === false) continue; 2472 2473 //找出 dotA 与 dotB 相交的两个点: 2474 if(Math.abs(dotA.x - x) + Math.abs(dotA.y - y) === 1){ 2475 //如果其中一个交点是不可走的则 dotA 到 dotB 不可走, 既返回 false 2476 if(this.#map[x][y].is === 0) return false; 2477 } 2478 2479 } 2480 2481 return true; 2482 } 2483 2484 run(x, y, x1, y1, path = this.#path){ 2485 path.length = 0; 2486 if(this.#map === null || this.mapRange.containsPoint(x, y) === false) return path; 2487 2488 var _n = this.#map[x][y]; 2489 if(_n.is === 0) return path; 2490 2491 const _sort = SeekPath._sort, 2492 _open = SeekPath._open, 2493 _dots = SeekPath._dots, 2494 time = Date.now(); 2495 2496 //var isDot = true, 2497 var suc = _n, k, mhd, g, h, f, _d; 2498 2499 _n.g = 0; 2500 _n.h = _n.h = Math.abs(x1 - x) * 10 + Math.abs(y1 - y) * 10; 2501 _n.f = _n.h; 2502 _n.p = null; 2503 this._updateID(); 2504 _n.id = this._openID; 2505 _open.push(_n); 2506 2507 while(_open.length !== 0){ 2508 if(Date.now() - time > this.timeout) break; 2509 2510 _open.sort(_sort); 2511 _n = _open.shift(); 2512 if(_n.x === x1 && _n.y === y1){ 2513 suc = _n; 2514 break; 2515 } 2516 2517 if(suc.h > _n.h) suc = _n; 2518 _n.id = this._closeID; 2519 this.getDots(_n.x, _n.y, this.angle, _dots); 2520 2521 for(k = 0; k < this.angle; k += 2){ 2522 2523 _d = this.#map[_dots[k]][_dots[k+1]]; 2524 if(_d.id === this._closeID || _d.is === 0 || this.mapRange.containsPoint(_d.x, _d.y) === false) continue; 2525 2526 mhd = Math["abs"](_n["x"] - _d.x) + Math["abs"](_n["y"] - _d.y); 2527 g = _n["g"] + (mhd === 1 ? 10 : 14); 2528 h = Math["abs"](x1 - _d.x) * 10 + Math["abs"](y1 - _d.y) * 10; 2529 f = g + h; 2530 2531 if(_d.id !== this._openID){ 2532 //如果是斜角和8方向: 2533 if(mhd !== 1 && this.angle === 16){ 2534 if(this._check(_n, _d)){ 2535 _d.g = g; 2536 _d.h = h; 2537 _d.f = f; 2538 _d.p = _n; 2539 _d.id = this._openID; 2540 _open.push(_d); 2541 } 2542 }else{ 2543 _d.g = g; 2544 _d.h = h; 2545 _d.f = f; 2546 _d.p = _n; 2547 _d.id = this._openID; 2548 _open.push(_d); 2549 } 2550 } 2551 2552 else if(g < _d.g){ 2553 _d.g = g; 2554 _d.f = g + _d.h; 2555 _d.p = _n; 2556 } 2557 2558 } 2559 } 2560 2561 this.#success = suc === _n; 2562 2563 while(suc !== null){ 2564 path.unshift(suc); 2565 //path.unshift(this.toScene("x", suc["x"]), suc["height"], this.toScene("y", suc["y"])); 2566 suc = suc["p"]; 2567 } 2568 2569 _open.length = _dots.length = 0; 2570 2571 return path; 2572 } 2573 2574 } 2575 2576 2577 2578 2579 /* RunningList 2580 如果触发过程中删除(回调函数中删除正在遍历的数组), 不仅 len 没有变(遍历前定义的len没有变, 真实的len随之减少), 而且还会漏掉一个key; 2581 2582 */ 2583 class RunningList{ 2584 2585 /* static getProxy(runName){ 2586 2587 return new Proxy(new RunningList(runName), { 2588 2589 get(tar, key){ 2590 2591 }, 2592 2593 set(tar, key, val){ 2594 2595 } 2596 2597 }); 2598 2599 } */ 2600 2601 constructor(runName = 'update'){ 2602 this._running = false; 2603 this._list = []; 2604 this._delList = []; 2605 this._runName = runName; 2606 } 2607 2608 get length(){ 2609 2610 return this._list.length; 2611 2612 } 2613 2614 add(v){ 2615 2616 if(!this._list.includes(v)) this._list.push(v); 2617 2618 } 2619 2620 clear(){ 2621 this._list.length = 0; 2622 this._delList.length = 0; 2623 } 2624 2625 push(...v){ 2626 2627 v.forEach(_v => this._list.push(_v)); 2628 2629 } 2630 2631 splice(v){ 2632 if(this._running === true){ 2633 if(!this._delList.includes(v)) this._delList.push(v); 2634 } 2635 2636 else{ 2637 const i = this._list.indexOf(v); 2638 if(i !== -1) this._list.splice(i, 1); 2639 } 2640 2641 } 2642 2643 update(){ 2644 2645 var k, len = this._list.length; 2646 2647 this._running = true; 2648 if(this._runName !== ''){ 2649 for(k = 0; k < len; k++) this._list[k][this._runName](); 2650 }else{ 2651 for(k = 0; k < len; k++) this._list[k](); 2652 } 2653 this._running = false; 2654 2655 var i; 2656 len = this._delList.length; 2657 for(k = 0; k < len; k++){ 2658 //this.splice(this._delList[k]); 2659 i = this._list.indexOf(this._delList[k]); 2660 if(i !== -1) this._list.splice(i, 1); 2661 } 2662 this._delList.length = 0; 2663 2664 } 2665 2666 } 2667 2668 2669 2670 2671 /* TweenValue (从 原点 以规定的时间到达 终点) 2672 2673 parameter: origin, end, time, onUpdate, onEnd; 2674 2675 attribute: 2676 origin: Object; //原点(起点) 2677 end: Object; //终点 2678 time: Number; //origin 到 end 花费的时间 2679 onUpdate: Function; //更新回调; 一个回调参数 origin; 默认null; 2680 onEnd: Function; //结束回调; 没有回调参数; 默认null; (如果返回的是"restart"将不从队列删除, 你可以在onEnd中更新.end不间断的继续补间) 2681 2682 method: 2683 reset(origin, end: Object): undefined; //更换 .origin, .end; 它会清除其它对象的缓存属性 2684 reverse(): undefined; //this.end 复制 this.origin 的原始值 2685 update(): undefined; //Tween 通过此方法统一更新 TweenValue 2686 2687 demo: 2688 //init Tween: 2689 const tween = new Tween(), 2690 animate = function (){ 2691 requestAnimationFrame(animate); 2692 tween.update(); 2693 } 2694 2695 //init TweenValue: 2696 const v1 = new Tween.Value({x:0, y:0}, {x:5, y:10}, 1000, v => console.log(v)); 2697 2698 animate(); 2699 tween.start(v1); 2700 2701 */ 2702 class TweenValue{ 2703 2704 constructor(origin = {}, end = {}, time = 500, onUpdate = null, onEnd = null, onStart = null){ 2705 this.origin = origin; 2706 this.end = end; 2707 this.time = time; 2708 2709 this.onUpdate = onUpdate; 2710 this.onEnd = onEnd; 2711 this.onStart = onStart; 2712 2713 //以下属性不能直接设置 2714 this._r = null; 2715 this._t = 0; 2716 this._v = Object.create(null); 2717 2718 } 2719 2720 _start(){ 2721 var v = ""; 2722 for(v in this.origin) this._v[v] = this.origin[v]; 2723 2724 this._t = Date.now(); 2725 //this.update(); 2726 2727 } 2728 2729 reset(origin, end){ 2730 this.origin = origin; 2731 this.end = end; 2732 this._v = Object.create(null); 2733 2734 } 2735 2736 reverse(){ 2737 var n = ""; 2738 for(n in this.origin) this.end[n] = this._v[n]; 2739 2740 } 2741 2742 update(){ 2743 2744 if(this["_r"] !== null){ 2745 2746 var ted = Date["now"]() - this["_t"]; 2747 2748 if(ted >= this["time"]){ 2749 2750 for(ted in this["origin"]) this["origin"][ted] = this["end"][ted]; 2751 2752 if(this["onEnd"] !== null){ 2753 2754 if(this["onEnd"](this) === "restart"){ 2755 if(this["onUpdate"] !== null) this["onUpdate"](this["origin"]); 2756 this["_start"](); 2757 } 2758 2759 else this["_r"]["stop"](this); 2760 2761 } 2762 2763 else this["_r"]["stop"](this); 2764 2765 } 2766 2767 else{ 2768 ted = ted / this["time"]; 2769 let n = ""; 2770 for(n in this["origin"]) this["origin"][n] = ted * (this["end"][n] - this["_v"][n]) + this["_v"][n]; 2771 if(this["onUpdate"] !== null) this["onUpdate"](this["origin"]); 2772 } 2773 2774 } 2775 2776 } 2777 2778 } 2779 2780 Object.defineProperties(TweenValue.prototype, { 2781 2782 isTweenValue: { 2783 configurable: false, 2784 enumerable: false, 2785 writable: false, 2786 value: true, 2787 } 2788 2789 }); 2790 2791 2792 2793 2794 /* TweenAlone (相对于 TweenValue 此类可以独立补间, 不需要 Tween) 2795 2796 demo: 2797 const v1 = new TweenAlone({x:0, y:0}, {x:5, y:10}, 1000, v => console.log(v)), 2798 animate = function (){ 2799 requestAnimationFrame(animate); 2800 v1.update(); 2801 } 2802 2803 animate(); 2804 v1.start(); 2805 2806 */ 2807 class TweenAlone extends TweenValue{ 2808 2809 constructor(origin, end, time, onUpdate, onEnd, onStart){ 2810 super(origin, end, time, onUpdate, onEnd, onStart); 2811 2812 } 2813 2814 start(){ 2815 if(this.onStart !== null) this.onStart(); 2816 this._r = this; 2817 this._start(); 2818 2819 } 2820 2821 stop(){ 2822 this._r = null; 2823 2824 } 2825 2826 } 2827 2828 2829 2830 2831 2832 /* Tween 动画补间 (TweenValue 的root, 可以管理多个TweenValue) 2833 2834 parameter: 2835 attribute: 2836 method: 2837 start(value: TweenValue): undefined; 2838 stop(value: TweenValue): undefined; 2839 2840 static: 2841 Value: TweenValue; 2842 2843 demo: 2844 //init Tween: 2845 const tween = new Tween(), 2846 animate = function (){ 2847 requestAnimationFrame(animate); 2848 tween.update(); 2849 } 2850 2851 //init TweenValue: 2852 const v2 = new Tween.Value({x:0, y:0}, {x:5, y:10}, 1000, v => console.log(v), v => { 2853 v2.reverse(); //v2.end 复制起始值 2854 return "restart"; //返回"restart"表示不删除队列, 需要继续补间 2855 }); 2856 2857 animate(); 2858 tween.start(v2); 2859 2860 */ 2861 class Tween extends RunningList{ 2862 2863 static Value = TweenValue; 2864 2865 constructor(){ 2866 super(); 2867 2868 } 2869 2870 start(value){ 2871 if(value.onStart !== null) value.onStart(); 2872 if(value._r === null) this.push(value); 2873 value._r = this; 2874 value._start(this); 2875 2876 } 2877 2878 stop(value){ 2879 if(value._r !== null) this.splice(value); 2880 value._r = null; 2881 2882 } 2883 2884 } 2885 2886 2887 2888 2889 /* TweenTarget 朝着轴插值(有效的跟踪动态目标, 注意此类需要配合 RunningList 类使用, 因为此类在任何情况下都没有阻止你调用.update()方法) 2890 parameter: 2891 v1 = {x: 0}, 2892 v2 = {x: 100}, 2893 distance = 1, //每次移动的距离 2894 onUpdate = null, // 2895 onEnd = null 2896 2897 attribute: 2898 v1: Object; //起点 2899 v2: Object; //终点 2900 onUpdate: Function; // 2901 onEnd: Function; // 2902 2903 method: 2904 update(): undefined; //一般在动画循环里执行此方法 2905 updateAxis(): undefined; //更新v1至v2的方向轴 (初始化时构造器自动调用一次) 2906 setDistance(distance: Number): undefined; //设置每次移动的距离 (初始化时构造器自动调用一次) 2907 2908 demo: 2909 const ttc = new TweenTarget({x:0, y:0}, {x:100, y:100}, 10), 2910 2911 //计时器模拟动画循环函数, 每秒执行一次.update() 2912 timer = new Timer(() => { 2913 ttc.update(); 2914 console.log('update: ', ttc.v1); 2915 2916 }, 1000, Infinity); 2917 2918 ttc.onEnd = v => { 2919 timer.stop(); 2920 console.log('end: ', v); 2921 } 2922 2923 timer.start(); 2924 console.log(ttc); 2925 2926 */ 2927 class TweenTarget{ 2928 2929 #distance = 1; 2930 #distancePow2 = 1; 2931 #axis = {}; 2932 2933 constructor(v1 = {x: 0}, v2 = {x: 100}, distance, onUpdate = null, onEnd = null){ 2934 this.v1 = v1; 2935 this.v2 = v2; 2936 this.onUpdate = onUpdate; 2937 this.onEnd = onEnd; 2938 2939 this.setDistance(distance); 2940 this.updateAxis(); 2941 } 2942 2943 setDistance(v = 1){ 2944 this.#distance = v; 2945 this.#distancePow2 = Math.pow(v, 2); 2946 } 2947 2948 updateAxis(){ 2949 var n, v, len = 0; 2950 2951 for(n in this.v1){ 2952 v = this.v2[n] - this.v1[n]; 2953 len += v * v; 2954 this.#axis[n] = v; 2955 2956 } 2957 2958 len = Math.sqrt(len); 2959 2960 if(len !== 0){ 2961 2962 for(n in this.v1) this.#axis[n] *= 1 / len; 2963 2964 } 2965 } 2966 2967 update(){ 2968 var n, len = 0; 2969 2970 for(n in this.v1) len += Math.pow(this.v1[n] - this.v2[n], 2); 2971 2972 if(len > this.#distancePow2){ 2973 2974 for(n in this.v1) this.v1[n] += this.#axis[n] * this.#distance; 2975 if(this.onUpdate !== null) this.onUpdate(this.v1); 2976 2977 } 2978 2979 else{ 2980 2981 for(n in this.v1) this.v1[n] = this.v2[n]; 2982 if(this.onEnd !== null) this.onEnd(this.v1); 2983 2984 } 2985 } 2986 2987 } 2988 2989 2990 2991 2992 /* EventDispatcher 自定义事件管理器 2993 parameter: 2994 attribute: 2995 2996 method: 2997 clearEvents(eventName): undefined; //清除eventName列表, 如果 eventName 未定义清除所有事件 2998 customEvents(eventName, eventParam): this; //创建自定义事件 eventParam 可选 默认{} 2999 getParam(eventName): eventParam; 3000 trigger(eventName, callback): undefined; //触发 (callback: 可选) 3001 register(eventName, callback): undefined; // 3002 deleteEvent(eventName, callback): undefined; // 3003 3004 demo: 3005 const eventDispatcher = new EventDispatcher(); 3006 eventDispatcher.customEvents("test", {name: "test"}); 3007 3008 eventDispatcher.register("test", eventParam => { 3009 console.log(eventParam) //Object{name: "test"} 3010 }); 3011 3012 eventDispatcher.trigger("test"); 3013 3014 */ 3015 class EventDispatcher{ 3016 3017 constructor(){ 3018 this._eventsList = {}; 3019 this.__eventsList = []; 3020 this.__trigger = ""; 3021 3022 } 3023 3024 clearEvents(eventName){ 3025 3026 //if(this.__trigger === eventName) return console.warn("EventDispatcher: 清除事件失败"); 3027 if(this._eventsList[eventName] !== undefined) this._eventsList[eventName].func = [] 3028 3029 else this._eventsList = {} 3030 3031 } 3032 3033 customEvents(eventName, eventParam = {}){ 3034 //if(typeof eventName !== "string") return console.warn("EventDispatcher: 注册自定义事件失败"); 3035 if(this._eventsList[eventName] !== undefined) return console.warn("EventDispatcher: "+eventName+" 已存在"); 3036 //eventParam = eventParam || {} 3037 //if(eventParam.type === undefined) eventParam.type = eventName; 3038 this._eventsList[eventName] = {func: [], param: eventParam} 3039 return this; 3040 } 3041 3042 getParam(eventName){ 3043 return this._eventsList[eventName]["param"]; 3044 } 3045 3046 trigger(eventName, callback){ 3047 //if(this._eventsList[eventName] === undefined) return; 3048 3049 const obj = this._eventsList[eventName]; 3050 var k, len = obj.func.length; 3051 3052 if(len !== 0){ 3053 if(typeof callback === "function") callback(obj["param"]); //更新参数(eventParam) 3054 3055 //触发过程(如果触发过程中删除事件, 不仅 len 没有变, 而且还会漏掉一个key, 所以在触发过程中删除的事件要特殊处理) 3056 this.__trigger = eventName; 3057 for(k = 0; k < len; k++) obj["func"][k](obj["param"]); 3058 this.__trigger = ""; 3059 //触发过程结束 3060 3061 //删除在触发过程中要删除的事件 3062 len = this.__eventsList.length; 3063 for(k = 0; k < len; k++) this.deleteEvent(eventName, this.__eventsList[k]); 3064 this.__eventsList.length = 0; 3065 3066 } 3067 3068 } 3069 3070 register(eventName, callback){ 3071 if(this._eventsList[eventName] === undefined) return console.warn("EventDispatcher: "+eventName+" 不存在"); 3072 const obj = this._eventsList[eventName]; 3073 //if(obj.func.includes(callback) === false) obj.func.push(callback); 3074 //else console.warn("EventDispatcher: 回调函数重复"); 3075 obj.func.push(callback); 3076 } 3077 3078 deleteEvent(eventName, callback){ 3079 if(this._eventsList[eventName] === undefined) return console.warn("EventDispatcher: "+eventName+" 不存在"); 3080 3081 if(this.__trigger === eventName) this.__eventsList.push(callback); 3082 else{ 3083 const obj = this._eventsList[eventName], i = obj.func.indexOf(callback); 3084 if(i !== -1) obj.func.splice(i, 1); 3085 } 3086 3087 } 3088 3089 } 3090 3091 3092 3093 3094 export { 3095 UTILS, 3096 ColorRefTable, 3097 Ajax, 3098 IndexedDB, 3099 TreeStruct, 3100 Point, 3101 Line, 3102 Box, 3103 Circle, 3104 Polygon, 3105 RGBColor, 3106 Timer, 3107 SeekPath, 3108 RunningList, 3109 TweenValue, 3110 TweenAlone, 3111 Tween, 3112 TweenTarget, 3113 EventDispatcher, 3114 Rotate, 3115 ShapeRect, 3116 }
Canvas 实用类库:
1 "use strict"; 2 3 import { 4 UTILS, 5 Box, 6 Rotate, 7 } from './Utils.js'; 8 9 10 /* CanvasImageEvent 11 parameter: 12 domElement: CanvasImageRender.domElement; //必须 13 box: CanvasImageRender.domElementRect; //必须 14 ||或者第一个参数为 CanvasImageRender 15 16 与.initEvent(domElement, box)参数一样 17 18 attribute: 19 domElement //CanvasImageRender.domElement 20 box: Box; //忽略box以外的ca, 通常为 CanvasImageRender.domElementRect 的引用 21 22 method: 23 add(ca: CanvasImage, eventName: String, callback: Function): ca; //ca添加事件 24 remove(ca: CanvasImage, eventName: String, callback: Function): ca; //ca删除事件 25 eventName: 可能的值为 CanvasImageEvent.canvasEventsList 的属性名 26 callback: 参数 event, ca 27 28 clear(ca: CanvasImage, eventName: String): ca; //ca 必须; eventName 可选, 如果未定义则清空ca的所有事件; 29 disposeEvent(eventName): this; //.initEvent(domElement, box)调用一次此方法 30 initEvent(domElement, box): this; //每次更换 domElement, box 后应调用此方法; (CanvasAnimateEvent初始化时自动调用一次) 31 setScale(x, y: Number): undefiend; // 32 33 event: 34 (如果注册了 "out"|"over" 事件, 在弃用时(CanvasImageRender 不在使用): 35 必需要调用.disposeEvent(eventName)方法清理注册的dom事件, 36 因为这两个事件用的是 pointermove 而不是 onpointermove); 37 38 CanvasImageEvent.canvasEventsList 39 40 demo: 41 const car = new CanvasImageRender({width: 100, height: 100}), 42 cae = new CanvasImageEvent(car), 43 ca = car.add(new CanvasImage(image)); 44 45 //ca添加点击事件 46 cae.add(ca, 'click', (event, target) => console.log(event, target)); 47 48 car.render(); 49 50 */ 51 class CanvasImageEvent{ 52 53 static bind(obj, is){ 54 obj._eventList = {} 55 56 if(is === true){ 57 let k, evns = CanvasImageEvent.canvasEventsList; 58 for(k in evns) obj._eventList[k] = []; 59 } 60 61 } 62 63 static canvasEventsList = { 64 down: "onpointerdown", 65 move: "onpointermove", 66 up: "onpointerup", 67 click: "onclick", 68 wheel: "onmousewheel", 69 out: "pointermove", //移出 70 over: "pointermove", //移入 71 } 72 73 static isEventName(eventName){ 74 75 return CanvasImageEvent.canvasEventsList[eventName] !== undefined; 76 77 } 78 79 static emptyBox = new Box(); 80 static emptyRotate = new Rotate(); 81 82 constructor(domElement, box){ 83 this._running = ""; 84 this._delList = []; 85 this.initEvent(domElement, box); 86 CanvasImageEvent.bind(this); 87 } 88 89 initEvent(domElement, box){ 90 this.disposeEvent(); 91 92 if(CanvasImageRender.prototype.isPrototypeOf(domElement)){ 93 this.domElement = domElement.domElement; 94 this.box = domElement.domElementRect; 95 } 96 97 else{ 98 this.domElement = domElement; 99 this.box = box; 100 } 101 102 if(this._eventList !== undefined){ 103 for(let evn in this._eventList){ 104 if(this._eventList[evn] !== undefined) this._createEvent(evn); 105 } 106 107 } 108 109 return this; 110 } 111 112 add(ca, eventName, callback){ 113 if(CanvasImageEvent.canvasEventsList[eventName] === undefined) return console.warn("CanvasImageEvent: 参数错误 "+ eventName); 114 if(typeof callback !== "function") return console.warn("CanvasImageEvent: 事件添加失败,参数错误"); 115 116 this._add(ca, eventName); 117 this._addCA(ca, eventName, callback); 118 119 return ca; 120 } 121 122 remove(ca, eventName, callback){ 123 if(CanvasImageEvent.canvasEventsList[eventName] === undefined) return console.warn("CanvasImageEvent: 参数错误 "+ eventName); 124 if(typeof callback !== "function") return console.warn("CanvasImageEvent: 事件添加失败,参数错误"); 125 if(this._running !== eventName){ 126 this._remove(ca, eventName); 127 this._removeCA(ca, eventName, callback); 128 } 129 130 else this._delList.push(ca, eventName, callback); 131 132 return ca; 133 } 134 135 disposeEvent(eventName){ 136 if(!this.domElement) return this; 137 138 if(eventName === "over" || eventName === "out"){ 139 140 if(typeof this["_"+eventName] === "function"){ 141 this.domElement.removeEventListener(CanvasImageEvent.canvasEventsList[eventName], this["_"+eventName]); 142 delete this["_"+eventName]; 143 } 144 145 } 146 147 else{ 148 149 if(typeof this["_over"] === "function"){ 150 this.domElement.removeEventListener(CanvasImageEvent.canvasEventsList["over"], this["_over"]); 151 delete this["_over"]; 152 } 153 154 if(typeof this["_out"] === "function"){ 155 this.domElement.removeEventListener(CanvasImageEvent.canvasEventsList["out"], this["_out"]); 156 delete this["_out"]; 157 } 158 159 } 160 161 return this; 162 } 163 164 clear(ca, eventName){ 165 if(eventName === undefined){ 166 var k; for(k in this._eventList){ 167 this._remove(ca, k); 168 } 169 170 if(ca._eventList !== undefined) delete ca._eventList; //CanvasImageEvent.bind(ca, true); 171 172 } 173 174 else if(CanvasImageEvent.canvasEventsList[eventName] !== undefined){ 175 this._remove(ca, eventName); 176 177 if(ca._eventList !== undefined) ca._eventList[eventName].length = 0; 178 179 } 180 181 return ca; 182 } 183 184 _addCA(ca, eventName, callback){ 185 if(ca._eventList === undefined) CanvasImageEvent.bind(ca); 186 if(ca._eventList[eventName] === undefined) ca._eventList[eventName] = []; 187 ca._eventList[eventName].push(callback); 188 189 } 190 191 _removeCA(ca, eventName, callback){ 192 if(ca._eventList !== undefined && ca._eventList[eventName] !== undefined){ 193 for(let k = 0, len = ca._eventList[eventName].length; k < len; k++){ 194 if(ca._eventList[eventName][k] === callback){ 195 ca._eventList[eventName].splice(k, 1); 196 break; 197 } 198 } 199 } 200 201 } 202 203 _add(ca, eventName){ 204 if(this._eventList[eventName] === undefined){ 205 this._eventList[eventName] = []; 206 this._createEvent(eventName); 207 208 } 209 210 //if(this._eventList[eventName].includes(ca) === false) this._eventList[eventName].push(ca); 211 this._eventList[eventName].push(ca); 212 } 213 214 _remove(ca, eventName){ 215 if(this._eventList[eventName] !== undefined){ 216 let key = this._eventList[eventName].indexOf(ca); 217 if(key !== -1) this._eventList[eventName].splice(key, 1); 218 if(key === 0){ 219 if(eventName == "over" || eventName === "out") this.disposeEvent(eventName); 220 else this.domElement[CanvasImageEvent.canvasEventsList[eventName]] = null; 221 delete this._eventList[eventName]; 222 } 223 224 } 225 226 } 227 228 _createEvent(evn){ 229 var k, len, ca, arr, tar = null, oldTar = null, _run = null; 230 231 const _box = CanvasImageEvent.emptyBox, _rotate = CanvasImageEvent.emptyRotate, 232 233 run = event => { 234 len = this["_eventList"][evn].length; 235 236 if(len !== 0 && this.box.containsPoint(event.pageX, event.pageY)){ 237 tar = null; 238 for(k = 0; k < len; k++){ 239 ca = this["_eventList"][evn][k]; 240 _box.copy(ca.box); 241 242 //计算旋转后的box 243 if(ca.rotate !== null) _box.setFromRotate(_rotate.set(ca.x+ca.rotate.rotate.origin.x, ca.y+ca.rotate.origin.y, ca.rotate.angle)); 244 245 if(ca["visible"] === true && _box.containsPoint(event["pageX"] - this["box"]["x"], event["pageY"] - this["box"]["y"])){ 246 247 if(tar === null || tar["index"] < ca["index"]) tar = ca; 248 249 } 250 251 } 252 253 if(_run !== null) _run(); 254 if(tar !== null){ 255 this._running = evn; 256 arr = tar["_eventList"][evn]; 257 len = arr.length; 258 for(k = 0; k < len; k++) arr[k](event, tar); 259 tar = null; 260 261 len = this._delList.length; 262 for(k = 0; k < len; k += 3){ 263 this._remove(this._delList[k], this._delList[k+1]); 264 this._removeCA(this._delList[k], this._delList[k+1], this._delList[k+2]); 265 } 266 this._running = ""; 267 this._delList.length = 0; 268 } 269 270 } 271 272 } 273 274 if(evn === "over" || evn === "out"){ 275 this.domElement.addEventListener(CanvasImageEvent.canvasEventsList[evn], run); 276 this["_"+evn] = run; 277 if(evn === "over"){ 278 _run = ()=>{ 279 if(tar !== null){ 280 if(oldTar !== null){ 281 if(oldTar !== tar) oldTar = tar; 282 else tar = null; 283 } 284 else oldTar = tar; 285 } 286 else if(oldTar !== null) oldTar = null; 287 288 } 289 290 } 291 292 else{ 293 let _tar = null; 294 _run = ()=>{ 295 if(tar !== null){ 296 if(oldTar !== null){ 297 if(oldTar !== tar){ 298 _tar = tar; 299 tar = oldTar; 300 oldTar = _tar; 301 } 302 else tar = null; 303 } 304 else{ 305 oldTar = tar; 306 tar = null; 307 } 308 } 309 else if(oldTar !== null){ 310 tar = oldTar; 311 oldTar = null; 312 } 313 314 } 315 316 } 317 318 /* _run = ()=>{ 319 if(tar !== null){ 320 if(oldTar !== null){ 321 322 if(oldTar !== tar){ 323 if(evn === "over") oldTar = tar; 324 else{ 325 let _tar = tar; 326 tar = oldTar; 327 oldTar = _tar; 328 } 329 } 330 331 else tar = null; 332 333 } 334 335 else{ 336 oldTar = tar; 337 if(evn === "out") tar = null; 338 339 } 340 341 } 342 343 else{ 344 if(oldTar !== null){ 345 if(evn === "out") tar = oldTar; 346 oldTar = null; 347 } 348 349 } 350 351 } */ 352 353 } 354 355 else this.domElement[CanvasImageEvent.canvasEventsList[evn]] = run; 356 357 } 358 359 } 360 361 362 363 364 /* CanvasImageRender (渲染 CanvasImage) 365 注意: 366 this.box = new Box(); //本类绘制时用这box检测 (ca的box是否与这个box相交, 如果相交才有可能绘制这个ca) 367 this.domElementRect = new Box(); //CanvasImageEvent 用这个box检测 (鼠标的位置是否在这个box范围内, 如果在才有可能触发这个ca的事件) 368 369 ImageData: 370 length: w * h * 4 371 r: 0 - 255 372 g: 0 - 255 373 b: 0 - 255 374 a: 0 - 255 Math.round(255 * a) 375 遍历: 376 const data = context.getImageData(0, 0, canvas.width, canvas.height).data; 377 for(let k = 0, x, y, r, g, b, a, i; k < len; k++){ 378 x = k % width; 379 y = Math.floor(k / width); 380 381 i = k*4; 382 r = data[i] 383 g = data[i+1] 384 b = data[i+2] 385 a = data[i+3] 386 387 console.log(x, y, r, g, b, a, i); 388 } 389 390 parameter: 391 option = { 392 canvas //默认新的canvas 393 width, height //默认1 394 id 395 } 396 397 attribute: 398 list: Array[CanvasImage]; //渲染队列 399 box: Box; //canvas的位置和范围(.x.y是0; .w.h是canvas的宽高, 在渲染时检测ca的box是否与此box相交); 400 context: CanvasContext; 401 domElement: Canvas; 402 403 method: 404 isDraw(ca): Bool; //ca是否满足绘制条件 405 isCanvasImage(img: Object): Bool; //img是否是canvas可绘制的图片; 406 add(ca): ca; //添加ca (添加多个: const len = this.list.length; this.list.push(caA, caB); this.initList(len)) 407 remove(ca): ca; //删除ca (删除多个: this.list.splice(i, len); this.initList(i)); 408 updateCanvas(): this; //更新canvas的box, canvas添加到dom树后才有效; (如果你没有用.pos(x, y)和.size(w, h, setElem)设置canvas的位置和宽高的话, 那么你需要调用一次此方法, 否则 事件错误 等异常) 409 initList(index): this; //初始化ca列表; (如果你没有用.add(ca)添加 或 .remove(cd)删除 那么你需要调用一次此方法, 否则 删除错误, 事件错误 等异常) 410 pos(x, y): this; //设置canvas的位置和this.box的位置 411 size(w, h: Number, setElem: Bool): this; //设置this.box的宽高; setElem: 是否同时设置canvas的宽高; 默认true 412 render(parentElem): this; //绘制一次画布, 并把画布添加到parentElem, parentElem 默认 body, 然后调用一次 this.updateCanvas() 方法; 413 clear(): undefiend; //清除画布 414 draw(): undefiend; //绘制画布 415 redraw(): this; //清除并绘制画布; 一般在动画循环里面调用此方法 416 clearTarget(ca): undefiend; //清除 ca; 适应静态视图 417 drawTarget(ca): undefiend; //绘制 ca; 适应静态视图 418 computeOverlaps(ca) 419 420 //以下方法即将弃用 421 shear(ca, canvas, x, y): canvas; //this.canvas 的 ca 范围剪切到 canvas 的 x,y位置; ca默认this; canvas模型新的canvas; x, y默认0; 422 getData(box) 423 putData(data, x, y) 424 425 //参数名: 426 ca: CanvasImage; 包括它的子类 427 box: Box; 428 429 demo: 430 431 //更新ca.box(注意执行顺序, 这很重要): 432 root.clearTarget(ca); 433 ca.box.set(10, 50, 100, 100); 434 root.drawTarget(ca); 435 436 //显示: 437 ca.visible = false; 438 root.clearTarget(ca); 439 440 ca.visible = true; 441 root.drawTarget(ca); 442 443 //或者定义一个空的ca: 444 const emptyCA = new CanvasImage(); 445 ca.visible = true; 446 emptyCA.box.copy(ca.box); 447 root.drawTarget(ca); 448 449 以以上方法绘制ca的利与弊: 450 利: 比.redraw()效率更高; .redraw()每次绘制全部可绘制的ca, 而此方法每次只绘制与ca.box重叠的ca数; 451 弊: 如果目标ca的box覆盖了整个canvas那么像上面这样绘制反而会比.redraw()慢(因为多了一步计算重叠的时间); 452 453 更多例子: class CanvasAnimateUI 使用此方法更新画布 454 455 */ 456 class CanvasImageRender{ 457 458 static emptyArrA = [] 459 static emptyArrB = [] 460 static paramCon = {alpha: true} 461 462 static defaultStyles = { 463 filter: "none", 464 globalAlpha: 1, 465 globalCompositeOperation: "source-over", 466 imageSmoothingEnabled: true, 467 miterLimit: 10, 468 font: "12px SimSun, Songti SC", 469 textAlign: "left", 470 textBaseline: "top", 471 lineCap: "butt", 472 lineJoin: "miter", 473 lineDashOffset: 0, 474 lineWidth: 1, 475 shadowColor: "rgba(0, 0, 0, 0)", 476 shadowBlur: 0, 477 shadowOffsetX: 0, 478 shadowOffsetY: 0, 479 fillStyle: "#000000", 480 strokeStyle: "#ffffff", 481 } 482 483 static setDefaultStyles(context){ 484 let k = "", styles = CanvasImageRender.defaultStyles; 485 for(k in styles){ 486 if(context[k] !== styles[k]) context[k] = styles[k]; 487 } 488 } 489 490 static getContext(canvas, className, id){ 491 if(CanvasImageRender.isCanvas(canvas) === false) canvas = document.createElement("canvas"); 492 const context = canvas.getContext("2d", CanvasImageRender.paramCon); 493 494 if(typeof className === "string") canvas.className = className; 495 if(typeof id === "string") canvas.setAttribute('id', id); 496 497 return context; 498 } 499 500 static downloadImage(func){ 501 const input = document.createElement("input"); 502 input.type = "file"; 503 input.multiple = "multiple"; 504 input.accept = ".png, .jpg, .jpeg, .bmp, .gif"; 505 506 input.onchange = e => { 507 if(e.target.files.length === 0) return; 508 const fr = new FileReader(); 509 fr.onloadend = e1 => { 510 const img = new Image(); 511 img.onload = () => func(img); 512 img.src = e1.target.result; 513 } 514 515 fr.readAsDataURL(e.target.files[0]); 516 } 517 518 input.click(); 519 } 520 521 static isCanvasImage(img){ //OffscreenCanvas:ImageBitmap; CSSImageValue: 522 523 return ImageBitmap["prototype"]["isPrototypeOf"](img) || 524 HTMLImageElement["prototype"]["isPrototypeOf"](img) || 525 HTMLCanvasElement["prototype"]["isPrototypeOf"](img) || 526 CanvasRenderingContext2D["prototype"]["isPrototypeOf"](img) || 527 HTMLVideoElement["prototype"]["isPrototypeOf"](img); 528 529 } 530 531 static isCanvas(canvas){ 532 533 return HTMLCanvasElement["prototype"]["isPrototypeOf"](canvas); 534 535 } 536 537 constructor(option = {}){ 538 this.list = []; 539 this.box = new Box(); 540 this.domElementRect = new Box(); 541 this.context = CanvasImageRender.getContext(option.canvas, option.className, option.id); 542 this.domElement = this.context.canvas; 543 544 this.size(option.width, option.height); 545 } 546 547 isDraw(ca){ 548 return ca["visible"] === true && ca["image"] !== null && this["box"]["intersectsBox"](ca["box"]); 549 } 550 551 updateCanvasRect(){ 552 const rect = this.domElement.getBoundingClientRect(); 553 this.domElementRect.pos(rect.x, rect.y); 554 } 555 556 pos(x = 0, y = 0){ 557 this.domElement.style.left = x + "px"; 558 this.domElement.style.top = y + "px"; 559 if(this.domElement.parentElement !== null) this.updateCanvasRect(); 560 return this; 561 } 562 563 size(w = 1, h = 1){ 564 this.domElement.width = w; 565 this.domElement.height = h; 566 CanvasImageRender.setDefaultStyles(this.context); 567 this.box.size(w, h); 568 this.domElementRect.size(w, h); 569 return this; 570 } 571 572 add(ca){ 573 if(CanvasImage.prototype.isPrototypeOf(ca) && !this.list.includes(ca)){ 574 const len = this.list.length; 575 576 if(this.list[ca.index] === undefined){ 577 ca.index = len; 578 this.list.push(ca); 579 580 } 581 582 else{ 583 const arr = this.list.splice(ca.index); 584 this.list.push(ca); 585 for(let k = 0, c = arr.length; k < c; k++){ 586 this.list.push(arr[k]); 587 arr[k].index++; 588 } 589 590 } 591 592 } 593 594 return ca; 595 } 596 597 remove(ca){ 598 var i = ca.index; 599 600 if(this.list[i] !== ca) i = this.list.indexOf(ca); 601 602 if(i !== -1){ 603 this.list.splice(i, 1); 604 for(let k = i, len = this.list.length; k < len; k++) this.list[k].index -= 1; 605 } 606 607 return ca; 608 } 609 610 render(parentElem = document.body){ 611 this.redraw(); 612 parentElem.appendChild(this.domElement); 613 this.updateCanvasRect(); 614 return this; 615 } 616 617 initList(i){ 618 if(i === undefined || i < 0) i = 0; 619 for(let k = i, len = this.list.length; k < len; k++) this.list[k].index = k; 620 return this; 621 } 622 623 clear(){ 624 625 this['context']['clearRect'](0, 0, this['box']['w'], this['box']['h']); 626 627 } 628 629 draw(){ 630 const len = this["list"]["length"]; 631 for(let k = 0, ca; k < len; k++){ 632 ca = this["list"][k]; 633 if(this["isDraw"](ca) === true) this["_draw"](ca); 634 } 635 } 636 637 computeOverlap(tar){ 638 //1 如果检索到与box相交的ca时, 合并其box执行以下步骤: 639 //2 检索 已检索过的 并且 没有相交的ca 640 //3 如果存在相交的ca就合并box并继续第 2 步; 如果不存在继续第 1 步 641 if(tar["overlap"] === null) tar["overlap"] = {box: new Box(), draw: false}; 642 const _list = CanvasImageRender.emptyArrA, list = CanvasImageRender.emptyArrB, len = this.list.length, box = tar["overlap"]["box"]["copy"](tar["box"]); 643 644 for(let k = 0, i = 0, c = 0, _c = 0, a = _list, b = list, loop = false; k < len; k++){ 645 tar = this["list"][k]; 646 if(tar["overlap"] === null) tar["overlap"] = {box: new Box(), draw: false}; 647 648 if(this.isDraw(tar) === false){ 649 if(tar["overlap"]["draw"] !== false) tar["overlap"]["draw"] = false; 650 continue; 651 } 652 653 if(box["intersectsBox"](tar["box"]) === true){ 654 if(tar["overlap"]["draw"] !== true) tar["overlap"]["draw"] = true; 655 box["expand"](tar["box"]); 656 loop = true; 657 658 while(loop === true){ 659 b["length"] = 0; 660 loop = false; 661 c = _c; 662 663 for(i = 0; i < c; i++){ 664 tar = a[i]; 665 666 if(box["intersectsBox"](tar["box"]) === true){ 667 if(tar["overlap"]["draw"] !== true) tar["overlap"]["draw"] = true; 668 box["expand"](tar["box"]); 669 loop = true; _c--; 670 } 671 672 else b.push(tar); 673 674 } 675 676 a = a === _list ? list : _list; 677 b = b === _list ? list : _list; 678 679 } 680 681 } 682 683 else{ 684 _c++; 685 a["push"](tar); 686 if(tar["overlap"]["draw"] !== false) tar["overlap"]["draw"] = false; 687 } 688 689 } 690 691 _list.length = list.length = 0; 692 693 } 694 695 clearTarget(ca){ 696 this["computeOverlap"](ca); 697 ca["overlap"]["draw"] = false; 698 this["_drawTarget"](ca); 699 } 700 701 drawTarget(ca){ 702 this["computeOverlap"](ca); 703 this["_drawTarget"](ca); 704 } 705 706 redraw(){ 707 this.clear(); 708 this.draw(); 709 } 710 711 //限内部使用 712 _drawImage(ca, ox = 0, oy = 0){ 713 if(ca["opacity"] === 1){ 714 if(ca.isScale === false) this["context"]["drawImage"](ca["image"], ca["box"]["x"]+ox, ca["box"]["y"]+oy); 715 else if(ca.scale === null) this["context"]["drawImage"](ca["image"], ca["box"]["x"]+ox, ca["box"]["y"]+oy, ca.box.w, ca.box.h); 716 //else this["context"]["drawImage"](ca["image"], ca.scale.x+ca.box.x, ca.scale.y+ca.box.y, ca.scale.w, ca.scale.h); 717 } 718 719 else{ 720 this["context"]["globalAlpha"] = ca["opacity"]; 721 if(ca.isScale === false) this["context"]["drawImage"](ca["image"], ca["box"]["x"]+ox, ca["box"]["y"]+oy); 722 else if(ca.scale === null) this["context"]["drawImage"](ca["image"], ca["box"]["x"]+ox, ca["box"]["y"]+oy, ca.box.w, ca.box.h); 723 //else this["context"]["drawImage"](ca["image"], ca.scale.x, ca.scale.y, ca.scale.w, ca.scale.h); 724 this["context"]["globalAlpha"] = 1; 725 } 726 } 727 728 _draw(ca){ 729 if(ca.rotate === null) this._drawImage(ca); 730 else{ 731 const ox = ca.rotate.origin.x + ca.box.x, oy = ca.rotate.origin.y + ca.box.y; 732 this.context.translate(ox, oy); 733 this.context.rotate(ca.rotate.angle); 734 this._drawImage(ca, -ox, -oy); 735 this.context.rotate(-ca.rotate.angle); 736 this.context.translate(-ox, -oy); 737 } 738 } 739 740 _drawTarget(ca){ 741 const len = this["list"]["length"]; 742 this["context"]["clearRect"](ca["overlap"]["box"]["x"], ca["overlap"]["box"]["y"], ca["overlap"]["box"]["w"], ca["overlap"]["box"]["h"]); 743 744 for(let k = 0; k < len; k++){ 745 ca = this["list"][k]; 746 if(ca["overlap"]["draw"] === true) this._draw(ca); 747 748 } 749 750 } 751 752 } 753 754 755 756 757 /* CanvasImage 758 parameter: 759 image 760 761 attribute: 762 opacity: Float; //透明度; 值0至1之间; 默认1; 763 visible: Boolean; //默认true; 完全隐藏(既不绘制视图, 也不触发绑定的事件) 764 box: Box; //.x.y 图像的位置, .w.h 图像的宽高; 765 rotate: Roate; //旋转; 默认 null (注意: 旋转的原点是相对于 box.x.y 的) 766 此属性暂未实现//scale: Box; //缩放; .x.y中心点, .w.h缩放; 默认 null; (注意: 缩放的原点是相对于 box.x.y 的) 767 x, y: Number; //this.box 的 .x.y 768 769 //以下属性不建议直接修改 770 overlap: Object; //CanvasImageRender.computeOverlaps(ca) 方法更新 771 index: Integer; //CanvasImageRender.index(ca) 修改 772 773 //只读 774 width, height, image; isRotate, isScale 775 776 method: 777 setImage(image): this; //设置图像 (image 如果是 CanvasImage 并它正在加载图像, 则会在它加载完成时自动设置 image); 778 load(src, onload): this; //加载并设置图像 (onload 如果是 CanvasImageRender 则加载完后自动调用一次 redraw 或 render 方法); 779 pos(x, y): this; //设置位置; x 可以是: Number, Object{x,y} 780 scale(w, h): this; //设置缩放; w 可以是: Number, Box, CanvasImage 781 782 demo: 783 //CanvasImageRender 784 const cir = new CanvasImageRender({width: WORLD.width, height: WORLD.height}); 785 cir.domElement.style = ` 786 position: absolute; 787 z-index: 9999; 788 background: rgb(127,127,127); 789 `; 790 791 //values 792 const ciA = cir.add(new CanvasImage()).pos(59, 59).load("view/examples/img/test.png", cir); 793 ciA.opacity = 0.2; //设置透明度 794 795 const ciB = cir.add(new CanvasImage(ciA)).pos(59, 120); 796 ciB.rotate = new Rotate().toAngle(45); //旋转45度 797 798 //event 799 const cie = new CanvasImageEvent(cir); //注意: CanvasImage 的缩放和旋转都会影响到 CanvasImageEvent 800 cie.add(ciB, "click", event => { //ciB 添加 点击事件 801 console.log("click ciB: ", event); 802 }); 803 */ 804 class CanvasImage{ 805 806 #image = null; 807 #isLoadImage = false; 808 809 get image(){return this.#image;} 810 get isScale(){return this.#image.width !== this.box.w || this.#image.height !== this.box.h;} 811 get width(){return this.#image.width;} 812 get height(){return this.#image.height;} 813 get isLoadImage(){return this.#isLoadImage;} 814 815 get x(){return this.box.x;} 816 get y(){return this.box.y;} 817 set x(v){this.box.x = v;} 818 set y(v){this.box.y = v;} 819 820 constructor(image){ 821 this.opacity = 1; 822 this.visible = true; 823 this.box = new Box(); 824 this.rotate = null; 825 //this.scale = null; 826 827 //以下属性不建议直接修改 828 this.overlap = null; 829 this.index = -1; 830 //this.loadImage_cis = undefined; 831 832 this.setImage(image); 833 } 834 835 pos(x, y){ 836 if(UTILS.isNumber(x)){ 837 this.box.x = x; 838 this.box.y = y; 839 } 840 else if(UTILS.isObject(x)){ 841 this.box.x = x.x; 842 this.box.y = x.y; 843 } 844 return this; 845 } 846 847 load(src, onload){ 848 this.#isLoadImage = true; 849 const image = new Image(); 850 851 image.onload = () => { 852 this.setImage(image); 853 this.#isLoadImage = false; 854 855 if(Array.isArray(this.loadImage_cis)){ 856 this.loadImage_cis.forEach(ci => ci.setImage(image)); 857 delete this.loadImage_cis; 858 } 859 860 if(typeof onload === "function") onload(image); 861 else if(CanvasImageRender.prototype.isPrototypeOf(onload)){ 862 if(onload.domElement.parentElement !== null) onload.redraw(); 863 else onload.render(); 864 } 865 } 866 867 image.src = src; 868 return this; 869 } 870 871 setImage(image){ 872 if(CanvasImageRender.isCanvasImage(image)){ 873 this.box.size(image.width, image.height); 874 this.#image = image; 875 } 876 else if(CanvasImage.prototype.isPrototypeOf(image)){ 877 if(image.isLoadImage){ 878 if(Array.isArray(image.loadImage_cis)) image.loadImage_cis.push(this); 879 else image.loadImage_cis = [this]; 880 } 881 else this.setImage(image.image); 882 } 883 else{ 884 this.box.size(0, 0); 885 this.#image = null; 886 } 887 return this; 888 } 889 890 } 891 892 893 894 /* CanvasAnimateExtend 895 896 demo: 897 //CanvasImageRender 898 const cir = new CanvasImageRender({width: WORLD.width, height: WORLD.height}); 899 cir.domElement.style = ` 900 position: absolute; 901 z-index: 9999; 902 background: rgb(127,127,127); 903 `; 904 905 //values 906 cir.add(new CanvasImageExtend()) 907 .pos(59, 180) 908 .load("view/examples/img/test.png", cir); 909 910 cir.add(new CanvasImageExtend()) 911 .pos(59, 180) 912 .text("value", "red") 913 .rect().stroke() 914 915 */ 916 class CanvasImageExtend extends CanvasImage{ 917 918 constructor(canvas){ 919 super(canvas); 920 } 921 922 setImage(image){ 923 if(CanvasImageRender.isCanvas(image)){ //image 如果是画布 924 super.setImage(image); 925 this.context = CanvasImageRender.getContext(image); 926 this.size(image.width, image.height); 927 } 928 else{ 929 if(CanvasImageRender.isCanvasImage(image)){ //image 如果是图像 930 this.context = CanvasImageRender.getContext(); 931 super.setImage(this.context.canvas); 932 this.size(image.width, image.height); 933 this.context.drawImage(image, 0, 0); 934 }else{ //image 如果是其它对象 935 if(image) super.setImage(image); 936 else{ 937 this.context = CanvasImageRender.getContext(); 938 super.setImage(this.context.canvas); 939 this.size(this.width, this.height); 940 } 941 } 942 } 943 944 return this; 945 } 946 947 clear(){ 948 this.context.clearRect(0, 0, this.width, this.height); 949 return this; 950 } 951 952 size(w, h){ 953 this.box.size(w, h); 954 this.image.width = w; 955 this.image.height = h; 956 CanvasImageRender.setDefaultStyles(this.context); 957 this.fontSize = parseFloat(CanvasImageRender.defaultStyles.font); 958 this.fillStyle = CanvasImageRender.defaultStyles.fillStyle; 959 this.strokeStyle = CanvasImageRender.defaultStyles.strokeStyle; 960 this.shadowColor = CanvasImageRender.defaultStyles.shadowColor 961 return this; 962 } 963 964 shadow(shadowColor = "rgba(0,0,0,0)", shadowBlur, shadowOffsetX, shadowOffsetY){ 965 const con = this.context; 966 if(typeof shadowColor === "string" && this.shadowColor !== shadowColor) this.shadowColor = con.shadowColor = shadowColor; 967 if(con.shadowBlur !== shadowBlur && typeof shadowBlur === "number") con.shadowBlur = shadowBlur; 968 if(con.shadowOffsetX !== shadowOffsetX && typeof shadowOffsetX === "number") con.shadowOffsetX = shadowOffsetX; 969 if(con.shadowOffsetY !== shadowOffsetY && typeof shadowOffsetY === "number") con.shadowOffsetY = shadowOffsetY; 970 return this; 971 } 972 973 line(x, y, x1, y1){ 974 this.context.beginPath(); 975 this.context.moveTo(x, y); 976 this.context.lineTo(x1, y1); 977 return this; 978 } 979 980 path(arr, close = false){ 981 const con = this.context; 982 con.beginPath(); 983 con.moveTo(arr[0], arr[1]); 984 for(let k = 2, len = arr.length; k < len; k+=2) con.lineTo(arr[k], arr[k+1]); 985 if(close === true) con.closePath(); 986 return this; 987 } 988 989 rect(r){ 990 const con = this.context, s = con.lineWidth; 991 var x = s / 2, 992 y = s / 2, 993 w = this.box.w - s, 994 h = this.box.h - s; 995 996 if(UTILS.isNumber(r) === false || r <= 0) r = Math.min(this.box.w, this.box.h) * 0.05; 997 998 const _x = x + r, 999 _y = y + r, 1000 mx = x + w, 1001 my = y + h, 1002 _mx = mx - r, 1003 _my = my - r; 1004 1005 //上 1006 con.moveTo(_x, y); 1007 con.lineTo(_mx, y); 1008 con.arcTo(mx, y, mx, _y, r); 1009 1010 //右 1011 con.lineTo(mx, _y); 1012 con.lineTo(mx, _my); 1013 con.arcTo(mx, my, _x, my, r); 1014 1015 //下 1016 con.lineTo(_x, my); 1017 con.lineTo(_mx, my); 1018 con.arcTo(x, my, x, _my, r); 1019 1020 //左 1021 con.lineTo(x, _y); 1022 con.lineTo(x, _my); 1023 con.arcTo(x, y, _x, y, r); 1024 1025 return this; 1026 } 1027 1028 stroke(color, lineWidth){ 1029 if(typeof color === "string" && this.strokeStyle !== color) this.strokeStyle = this.context.strokeStyle = color; 1030 if(UTILS.isNumber(lineWidth) && this.context.lineWidth !== lineWidth) this.context.lineWidth = lineWidth; 1031 this.context.stroke(); 1032 return this; 1033 } 1034 1035 fill(color){ 1036 if(typeof color === "string" && this.fillStyle !== color) this.fillStyle = this.context.fillStyle = color; 1037 this.context.fill(); 1038 return this; 1039 } 1040 1041 text(value, color, fontSize = 12, x = -1, y = -1){ 1042 if(this.fontSize !== fontSize){ 1043 this.fontSize = fontSize; 1044 this.context.font = fontSize+"px SimSun, Songti SC"; 1045 } 1046 1047 const textWidth = this.context.measureText(value).width; 1048 if(textWidth < this.box.w || this.fontSize < this.box.h){ 1049 this.size(textWidth+4, this.fontSize+4); 1050 this.fontSize = fontSize; 1051 this.context.font = fontSize+"px SimSun, Songti SC"; 1052 } 1053 1054 if(x === -1) x = (this.box.w - textWidth) / 2; 1055 if(y === -1) y = (this.box.h - this.fontSize) / 2; 1056 1057 if(typeof color === "string" && this.fillStyle !== color) this.fillStyle = this.context.fillStyle = color; 1058 this.context.fillText(value, x, y); 1059 1060 return this; 1061 } 1062 1063 } 1064 1065 1066 1067 export { 1068 CanvasImageEvent, 1069 CanvasImageRender, 1070 CanvasImage, 1071 CanvasImageExtend, 1072 }