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